diff --git a/DEPS b/DEPS
index f930921..3f7ab06 100644
--- a/DEPS
+++ b/DEPS
@@ -310,11 +310,11 @@
   # 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': '396f77db4a39189f39b63d0a00f103bcc7bc4491',
+  'skia_revision': 'de3f3987a8365b51e4f0546442f8d990a376e192',
   # 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': '4bd7c5b7d0c7fbfcf9cf9e54a92518150a7e544c',
+  'v8_revision': 'deda839adfed890bdcfbbd0a73fd6b1daa94eb23',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
@@ -326,7 +326,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': '7c7a6087e09e1a344984a6d0c5fbc2af36eca7ea',
+  'pdfium_revision': '4282836f334034e852b8bc4e38459fdf8ae644db',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -357,7 +357,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': '225f880e2de2a2d2b33fbdad55ca27eca3cdc103',
+  'nacl_revision': 'fb7f990c4d1d25abfd5bad2ed2b00b12dacbca14',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling freetype
   # and whatever else without interference from each other.
@@ -385,7 +385,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling chromium_variations
   # and whatever else without interference from each other.
-  'chromium_variations_revision': '63ae3c8064cb6e2bbf5d4557efc83d3fe82bb35a',
+  'chromium_variations_revision': 'a7a1e5eaa6ed285b51271a665bfda91bc8fe7a3f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling CrossBench
   # and whatever else without interference from each other.
@@ -401,7 +401,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '31ec4cf8104601c847e622449d9ec27abcaab832',
+  'devtools_frontend_revision': '294f601c1dc3f2eeafe0c65278cc65668eb2c865',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -477,7 +477,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.
-  'libcxxabi_revision':    '0c4e8fac5c5b369eb27093c0bdeae5dfad1aaf5c',
+  'libcxxabi_revision':    'fb278689d948a9a02e9c5ff4bdc9d524be2d4e82',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -825,7 +825,7 @@
 
   'src/clank': {
     'url': Var('chrome_git') + '/clank/internal/apps.git' + '@' +
-    '21c15c8a9fe3638c84faf6515795d2da19042214',
+    'f1d153819784da755404355b06bf9d91d1789b71',
     'condition': 'checkout_android and checkout_src_internal',
   },
 
@@ -1162,7 +1162,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' + '@' + 'c5ddc55e66f3478f313ab0c369f96dd6f01c16fd',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '56df863a202389def3bba8925ba93bb2bbca7eda',
       'condition': 'checkout_chromeos',
   },
 
@@ -1197,13 +1197,13 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'af6eabff5313cfdce7e4ff358e3490d1571ea4cd',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '0696c428b04513254d3b3e0b1fba5e5afdb11cf4',
 
   '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' + '@' + 'f75ab7f452a6bfa2cb62be2dd570071cb2f2d9e9',
+      'url': Var('chrome_git') + '/devtools/devtools-internal.git' + '@' + 'abdf93162b756e81b7d5d51ffa58bdf210cb4dde',
     'condition': 'checkout_src_internal',
   },
 
@@ -1663,7 +1663,7 @@
     Var('pdfium_git') + '/pdfium.git' + '@' +  Var('pdfium_revision'),
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'dd3f5e97f346774ecd3e49a2679f82cbc23fff98',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '3deb2358149574207c12407c7a6b2a885a5cef3b',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '8ef97ff3b7332e38e61b347a2fbed425a4617151',
@@ -1848,7 +1848,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'e082b08475761a2ba6a3349dfea72f704c8b68d4',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '35fe95802df52d8d2d6fdbee4278663076caaac7',
+    Var('webrtc_git') + '/src.git' + '@' + '1df269099fcb3c8e7474786a21bcdd32335c43ed',
 
   # 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.
@@ -3973,7 +3973,7 @@
 
   'src/components/optimization_guide/internal': {
       'url': Var('chrome_git') + '/chrome/components/optimization_guide.git' + '@' +
-        'ac7f78fc61731de5281b728b345dbf1bf95f0318',
+        'a1431631ca51a2ed211beb9b2438e260fed5143d',
       'condition': 'checkout_src_internal',
   },
 
@@ -4033,7 +4033,7 @@
 
   'src/ios_internal':  {
       'url': Var('chrome_git') + '/chrome/ios_internal.git' + '@' +
-        'b8db84491e44b069aebdf9afd1c1118efc806d05',
+        'e0f7bfa6413ecdadaf5626cfe53f3ef58d23c102',
       'condition': 'checkout_ios and checkout_src_internal',
   },
 
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index d6f8188..a9fccb96 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -6282,10 +6282,10 @@
         """
         valid_types = {
             'plural': (frozenset(
-                ['=0', '=1', 'zero', 'one', 'two', 'few', 'many',
+                ['=0', '=1', '=2', '=3', 'zero', 'one', 'two', 'few', 'many',
                  'other']), frozenset(['=1', 'other'])),
             'selectordinal': (frozenset(
-                ['=0', '=1', 'zero', 'one', 'two', 'few', 'many',
+                ['=0', '=1', '=2', '=3', 'zero', 'one', 'two', 'few', 'many',
                  'other']), frozenset(['one', 'other'])),
             'select': (frozenset(), frozenset(['other'])),
         }
diff --git a/PRESUBMIT_test.py b/PRESUBMIT_test.py
index 8a6980b..f28d7a2 100755
--- a/PRESUBMIT_test.py
+++ b/PRESUBMIT_test.py
@@ -3389,6 +3389,23 @@
              </release>
            </grit>
         """.splitlines()
+  # A grd file with multiple ICU syntax messages without syntax errors.
+  NEW_GRD_CONTENTS_ICU_SYNTAX_OK3 = """<?xml version="1.0" encoding="UTF-8"?>
+           <grit latest_public_release="1" current_release="1">
+             <release seq="1">
+               <messages>
+                 <message name="IDS_TEST1">
+                   {NUM, plural,
+                    =0 {New test text for numeric zero}
+                    =1 {Different test text for numeric one}
+                    =2 {New test text for numeric two}
+                    =3 {New test text for numeric three}
+                    other {Different test text for plural with {NUM} as number}}
+                 </message>
+               </messages>
+             </release>
+           </grit>
+        """.splitlines()
   # A grd file with one ICU syntax message with syntax errors (misses a comma).
   NEW_GRD_CONTENTS_ICU_SYNTAX_ERROR = """<?xml version="1.0" encoding="UTF-8"?>
            <grit latest_public_release="1" current_release="1">
@@ -3481,9 +3498,22 @@
             'other {Different test text for plural with {NUM} as number}}',
         '</message>',
     '</grit-part>')
+  # A grdp file with multiple ICU syntax messages without syntax errors.
+  NEW_GRDP_CONTENTS_ICU_SYNTAX_OK3 = (
+    '<?xml version="1.0" encoding="utf-8"?>',
+      '<grit-part>',
+        '<message name="IDS_PART_TEST1">',
+           '{NUM, plural,',
+            '=0 {New test text for numeric zero}',
+            '=1 {Different test text for numeric one}',
+            '=2 {New test text for numeric two}',
+            '=3 {New test text for numeric three}',
+            'other {Different test text for plural with {NUM} as number}}',
+        '</message>',
+    '</grit-part>')
 
-  # A grdp file with one ICU syntax message with syntax errors (superfluent
-  # whitespace).
+  # A grdp file with one ICU syntax message with syntax errors (superfluous
+  # space).
   NEW_GRDP_CONTENTS_ICU_SYNTAX_ERROR = (
     '<?xml version="1.0" encoding="utf-8"?>',
       '<grit-part>',
@@ -3889,6 +3919,18 @@
         if e.message == self.ICU_SYNTAX_ERROR_MESSAGE]
     self.assertEqual(0, len(icu_errors))
 
+    # Valid changes in ICU syntax. Should not raise an error.
+    input_api = self.makeInputApi([
+      MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK3,
+                       self.NEW_GRD_CONTENTS_ICU_SYNTAX_OK1, action='M'),
+      MockAffectedFile('part.grdp', self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK3,
+                       self.NEW_GRDP_CONTENTS_ICU_SYNTAX_OK1, action='M')])
+    results = PRESUBMIT.CheckStrings(input_api, MockOutputApi())
+    # We expect no ICU syntax errors.
+    icu_errors = [e for e in results
+        if e.message == self.ICU_SYNTAX_ERROR_MESSAGE]
+    self.assertEqual(0, len(icu_errors))
+
     # Add invalid ICU syntax strings. Should raise two errors.
     input_api = self.makeInputApi([
       MockAffectedFile('test.grd', self.NEW_GRD_CONTENTS_ICU_SYNTAX_ERROR,
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 dea4473..2f55008 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
@@ -500,6 +500,7 @@
                 "If enabled, reads and decodes navigation body data off the main thread."),
         Flag.baseFeature(BlinkFeatures.SPARSE_OBJECT_PAINT_PROPERTIES),
         Flag.baseFeature(BlinkFeatures.HIT_TEST_OPAQUENESS),
+        Flag.baseFeature(CcFeatures.USE_RECORDED_BOUNDS_FOR_TILING),
         Flag.baseFeature(BlinkFeatures.DYNAMIC_SCROLL_CULL_RECT_EXPANSION),
         Flag.baseFeature(BlinkFeatures.INTERSECTION_OPTIMIZATION),
         Flag.baseFeature(BlinkFeatures.EXPAND_COMPOSITED_CULL_RECT),
@@ -828,10 +829,6 @@
                 "When enabled, the network service will send TransferSizeUpdatedIPC IPC only when"
                         + " DevTools is attached or the request is for an ad request."),
         Flag.baseFeature(
-                BaseFeatures.USE_NEW_JOB_IMPLEMENTATION,
-                "Uses a thread pool job implementation which leverages atomics to minimize lock"
-                        + " contention."),
-        Flag.baseFeature(
                 AwFeatures.WEBVIEW_BACK_FORWARD_CACHE,
                 "Controls if back/forward cache is enabled."),
         Flag.baseFeature(
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index a9361db..a063051 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -2442,6 +2442,8 @@
     "wallpaper/wallpaper_time_of_day_scheduler.h",
     "wallpaper/wallpaper_utils/scored_sample.cc",
     "wallpaper/wallpaper_utils/scored_sample.h",
+    "wallpaper/wallpaper_utils/sea_pen_metadata_utils.cc",
+    "wallpaper/wallpaper_utils/sea_pen_metadata_utils.h",
     "wallpaper/wallpaper_utils/wallpaper_calculated_colors.cc",
     "wallpaper/wallpaper_utils/wallpaper_calculated_colors.h",
     "wallpaper/wallpaper_utils/wallpaper_color_calculator.cc",
@@ -2932,6 +2934,7 @@
     "//ash/quick_pair/scanning",
     "//ash/quick_pair/ui",
     "//ash/style",
+    "//ash/webui/common/mojom:sea_pen",
     "//ash/webui/diagnostics_ui/mojom:mojom",
     "//ash/webui/eche_app_ui:eche_connection_status",
     "//ash/webui/eche_app_ui:system_info_provider",
@@ -3862,6 +3865,7 @@
     "wallpaper/wallpaper_controller_unittest.cc",
     "wallpaper/wallpaper_drag_drop_delegate_unittest.cc",
     "wallpaper/wallpaper_pref_manager_unittest.cc",
+    "wallpaper/wallpaper_utils/sea_pen_metadata_utils_unittest.cc",
     "wallpaper/wallpaper_utils/wallpaper_color_calculator_unittest.cc",
     "wallpaper/wallpaper_utils/wallpaper_ephemeral_user_unittest.cc",
     "wallpaper/wallpaper_utils/wallpaper_file_utils_unittest.cc",
@@ -4010,6 +4014,7 @@
     "//ash/strings",
     "//ash/style",
     "//ash/style/mojom:color_scheme_shared_cpp_sources",
+    "//ash/webui/common/mojom:sea_pen",
     "//ash/webui/diagnostics_ui/mojom",
     "//ash/webui/personalization_app/mojom:mojom_shared_cpp_sources",
     "//ash/webui/personalization_app/proto",
@@ -4224,6 +4229,7 @@
     "ambient/ui/ambient_slideshow_pixeltest.cc",
     "app_list/views/app_list_item_view_pixeltest.cc",
     "app_list/views/app_list_view_pixeltest.cc",
+    "app_list/views/search_result_view_pixeltest.cc",
     "fullscreen_pixeltest.cc",
     "glanceables/glanceables_pixeltest.cc",
     "glanceables/tasks/glanceables_task_view_pixeltest.cc",
@@ -4232,6 +4238,7 @@
     "scalable_iph/scalable_iph_pixeltest.cc",
     "shelf/login_shelf_view_pixeltest.cc",
     "shelf/scrollable_shelf_view_pixeltest.cc",
+    "shelf/shelf_app_button_pixeltest.cc",
     "shelf/shelf_layout_manager_pixeltest.cc",
     "system/accessibility/accessibility_detailed_view_pixeltest.cc",
     "system/audio/audio_detailed_view_pixeltest.cc",
diff --git a/ash/accelerators/accelerator_alias_converter.cc b/ash/accelerators/accelerator_alias_converter.cc
index 968b21f..6cfaf497 100644
--- a/ash/accelerators/accelerator_alias_converter.cc
+++ b/ash/accelerators/accelerator_alias_converter.cc
@@ -17,6 +17,7 @@
 #include "base/functional/bind.h"
 #include "base/functional/callback_forward.h"
 #include "base/notreached.h"
+#include "chromeos/constants/devicetype.h"
 #include "components/prefs/pref_service.h"
 #include "ui/base/accelerators/accelerator.h"
 #include "ui/events/ash/keyboard_capability.h"
@@ -291,6 +292,8 @@
             CreateTopRowAliases(*priority_external_keyboard, accelerator);
         alias) {
       aliases_set.insert(*alias);
+      // Always add the original accelerator if an external keyboard is present.
+      aliases_set.insert(accelerator);
     }
   }
   if (internal_keyboard) {
diff --git a/ash/accelerators/accelerator_alias_converter_unittest.cc b/ash/accelerators/accelerator_alias_converter_unittest.cc
index 6d2b1123..5ec7b979 100644
--- a/ash/accelerators/accelerator_alias_converter_unittest.cc
+++ b/ash/accelerators/accelerator_alias_converter_unittest.cc
@@ -54,6 +54,17 @@
   std::vector<ui::Accelerator> expected_accelerators_;
 };
 
+struct ActionKeyAliasConverterTestData {
+  // All currently connected keyboards' connection type, e.g.
+  // INPUT_DEVICE_INTERNAL.
+  std::vector<ui::InputDeviceType> keyboard_connection_type_;
+  // All currently connected keyboards' layout types.
+  std::vector<std::string> keyboard_layout_types_;
+  std::vector<bool> top_row_are_fkeys_;
+  ui::Accelerator accelerator_;
+  std::vector<ui::Accelerator> expected_accelerators_;
+};
+
 class FakeDeviceManager {
  public:
   FakeDeviceManager() = default;
@@ -383,9 +394,10 @@
                                             ui::EF_NONE};
   std::vector<ui::Accelerator> accelerator_aliases =
       accelerator_alias_converter_.CreateAcceleratorAlias(refresh_accelerator);
-  ASSERT_EQ(1u, accelerator_aliases.size());
+  ASSERT_EQ(2u, accelerator_aliases.size());
   EXPECT_EQ(ui::Accelerator(ui::VKEY_F3, ui::EF_COMMAND_DOWN),
             accelerator_aliases[0]);
+  EXPECT_EQ(refresh_accelerator, accelerator_aliases[1]);
 
   settings.suppress_meta_fkey_rewrites = true;
   Shell::Get()->input_device_settings_controller()->SetKeyboardSettings(
@@ -393,7 +405,8 @@
 
   accelerator_aliases =
       accelerator_alias_converter_.CreateAcceleratorAlias(refresh_accelerator);
-  ASSERT_EQ(0u, accelerator_aliases.size());
+  ASSERT_EQ(1u, accelerator_aliases.size());
+  EXPECT_EQ(refresh_accelerator, accelerator_aliases[0]);
 
   ui::KeyboardDevice fake_internal_keyboard(
       /*id=*/2, /*type=*/ui::InputDeviceType::INPUT_DEVICE_INTERNAL,
@@ -410,9 +423,10 @@
 
   accelerator_aliases =
       accelerator_alias_converter_.CreateAcceleratorAlias(refresh_accelerator);
-  ASSERT_EQ(1u, accelerator_aliases.size());
+  ASSERT_EQ(2u, accelerator_aliases.size());
+  EXPECT_EQ(refresh_accelerator, accelerator_aliases[0]);
   EXPECT_EQ(ui::Accelerator(ui::VKEY_BROWSER_REFRESH, ui::EF_COMMAND_DOWN),
-            accelerator_aliases[0]);
+            accelerator_aliases[1]);
 }
 
 class TopRowAliasTest : public AcceleratorAliasConverterTest,
@@ -465,7 +479,8 @@
         {{ui::InputDeviceType::INPUT_DEVICE_USB},
          {kKbdTopRowLayoutUnspecified},
          ui::Accelerator{ui::VKEY_BROWSER_BACK, ui::EF_ALT_DOWN},
-         {ui::Accelerator{ui::VKEY_F1, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN}}},
+         {ui::Accelerator{ui::VKEY_F1, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN},
+          ui::Accelerator{ui::VKEY_BROWSER_BACK, ui::EF_ALT_DOWN}}},
 
         // For internal keyboard only, we shows icon + meta.
         {{ui::InputDeviceType::INPUT_DEVICE_INTERNAL},
@@ -536,13 +551,15 @@
         {{ui::InputDeviceType::INPUT_DEVICE_BLUETOOTH},
          {kKbdTopRowLayoutUnspecified},
          ui::Accelerator{ui::VKEY_BROWSER_FORWARD, ui::EF_ALT_DOWN},
-         {ui::Accelerator{ui::VKEY_F2, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN}}},
+         {ui::Accelerator{ui::VKEY_F2, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN},
+          ui::Accelerator{ui::VKEY_BROWSER_FORWARD, ui::EF_ALT_DOWN}}},
 
         // For TopRowLayout1: [Alt] + [Zoom] -> [Alt] + [Search] + [F4].
         {{ui::InputDeviceType::INPUT_DEVICE_USB},
          {kKbdTopRowLayoutUnspecified},
          ui::Accelerator{ui::VKEY_ZOOM, ui::EF_ALT_DOWN},
-         {ui::Accelerator{ui::VKEY_F4, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN}}},
+         {ui::Accelerator{ui::VKEY_F4, ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN},
+          {ui::Accelerator{ui::VKEY_ZOOM, ui::EF_ALT_DOWN}}}},
 
         // For TopRowLayout2: [Alt] + [Shift] + [Back] -> [Alt] + [Shift] +
         // [Search] + [Back].
@@ -611,7 +628,8 @@
          {kKbdTopRowLayout1Tag, kKbdTopRowLayoutUnspecified},
          ui::Accelerator{ui::VKEY_BROWSER_FORWARD, ui::EF_NONE},
          {ui::Accelerator{ui::VKEY_F2, ui::EF_COMMAND_DOWN},
-          ui::Accelerator{ui::VKEY_BROWSER_FORWARD, ui::EF_COMMAND_DOWN}}},
+          ui::Accelerator{ui::VKEY_BROWSER_FORWARD, ui::EF_COMMAND_DOWN},
+          ui::Accelerator{ui::VKEY_BROWSER_FORWARD, ui::EF_NONE}}},
 
         // Since the external keyboard uses Layout1 by default, it should map to
         // F2, even if the most recently connected keyboard (which is internal)
@@ -620,7 +638,8 @@
           ui::InputDeviceType::INPUT_DEVICE_INTERNAL},
          {kKbdTopRowLayoutUnspecified, kKbdTopRowLayout2Tag},
          ui::Accelerator{ui::VKEY_BROWSER_FORWARD, ui::EF_NONE},
-         {ui::Accelerator{ui::VKEY_F2, ui::EF_COMMAND_DOWN}}},
+         {ui::Accelerator{ui::VKEY_F2, ui::EF_COMMAND_DOWN},
+          ui::Accelerator{ui::VKEY_BROWSER_FORWARD, ui::EF_NONE}}},
     }));
 
 TEST_P(TopRowAliasTest, CheckTopRowAlias) {
@@ -1397,4 +1416,111 @@
   EXPECT_EQ(expected_accelerator, accelerator_alias[1]);
 }
 
+class ActionKeyboardVariantsTest
+    : public AcceleratorAliasConverterTest,
+      public testing::WithParamInterface<ActionKeyAliasConverterTestData> {
+ public:
+  void SetUp() override {
+    AcceleratorAliasConverterTest::SetUp();
+    ActionKeyAliasConverterTestData test_data = GetParam();
+    keyboard_connection_type_ = test_data.keyboard_connection_type_;
+    keyboard_layout_types_ = test_data.keyboard_layout_types_;
+    top_row_are_fkeys_ = test_data.top_row_are_fkeys_;
+    accelerator_ = test_data.accelerator_;
+    expected_accelerators_ = test_data.expected_accelerators_;
+    fake_keyboard_manager_ = std::make_unique<FakeDeviceManager>();
+  }
+
+ protected:
+  std::vector<ui::InputDeviceType> keyboard_connection_type_;
+  std::vector<std::string> keyboard_layout_types_;
+  std::vector<bool> top_row_are_fkeys_;
+  ui::Accelerator accelerator_;
+  std::vector<ui::Accelerator> expected_accelerators_;
+  std::unique_ptr<FakeDeviceManager> fake_keyboard_manager_;
+};
+
+INSTANTIATE_TEST_SUITE_P(
+    // Empty to simplify gtest output
+    ,
+    ActionKeyboardVariantsTest,
+    testing::ValuesIn(std::vector<ActionKeyAliasConverterTestData>{
+        {{ui::InputDeviceType::INPUT_DEVICE_INTERNAL},
+         {kKbdTopRowLayout1Tag},
+         {/*treat_top_row_as_fkeys=*/false},
+         ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_NONE},
+         {ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_NONE}}},
+        {{ui::InputDeviceType::INPUT_DEVICE_INTERNAL},
+         {kKbdTopRowLayout1Tag},
+         {/*treat_top_row_as_fkeys=*/true},
+         ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_NONE},
+         {ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_COMMAND_DOWN}}},
+        {{ui::InputDeviceType::INPUT_DEVICE_USB},
+         {kKbdTopRowLayoutUnspecified},
+         {/*treat_top_row_as_fkeys=*/false},
+         ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_NONE},
+         {ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_NONE},
+          ui::Accelerator{ui::VKEY_F3, ui::EF_NONE}}},
+        {{ui::InputDeviceType::INPUT_DEVICE_USB},
+         {kKbdTopRowLayoutUnspecified},
+         {/*treat_top_row_as_fkeys=*/true},
+         ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_NONE},
+         {ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_NONE},
+          ui::Accelerator{ui::VKEY_F3, ui::EF_COMMAND_DOWN}}},
+        {{ui::InputDeviceType::INPUT_DEVICE_INTERNAL, ui::INPUT_DEVICE_USB},
+         {kKbdTopRowLayout1Tag, kKbdTopRowLayoutUnspecified},
+         {/*treat_top_row_as_fkeys=*/false, /*treat_top_row_as_fkeys=*/false},
+         ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_NONE},
+         {ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_NONE},
+          ui::Accelerator{ui::VKEY_F3, ui::EF_NONE}}},
+        {{ui::InputDeviceType::INPUT_DEVICE_INTERNAL, ui::INPUT_DEVICE_USB},
+         {kKbdTopRowLayout1Tag, kKbdTopRowLayoutUnspecified},
+         {/*treat_top_row_as_fkeys=*/false, /*treat_top_row_as_fkeys=*/true},
+         ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_NONE},
+         {ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_NONE},
+          ui::Accelerator{ui::VKEY_F3, ui::EF_COMMAND_DOWN}}},
+        {{ui::InputDeviceType::INPUT_DEVICE_INTERNAL, ui::INPUT_DEVICE_USB},
+         {kKbdTopRowLayout1Tag, kKbdTopRowLayoutUnspecified},
+         {/*treat_top_row_as_fkeys=*/true, /*treat_top_row_as_fkeys=*/false},
+         ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_NONE},
+         {ui::Accelerator{ui::VKEY_F3, ui::EF_NONE},
+          ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_NONE},
+          ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_COMMAND_DOWN}}},
+        {{ui::InputDeviceType::INPUT_DEVICE_INTERNAL, ui::INPUT_DEVICE_USB},
+         {kKbdTopRowLayout1Tag, kKbdTopRowLayoutUnspecified},
+         {/*treat_top_row_as_fkeys=*/true, /*treat_top_row_as_fkeys=*/true},
+         ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_NONE},
+         {ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_NONE},
+          ui::Accelerator{ui::VKEY_BROWSER_REFRESH, ui::EF_COMMAND_DOWN},
+          ui::Accelerator{ui::VKEY_F3, ui::EF_COMMAND_DOWN}}},
+    }));
+
+TEST_P(ActionKeyboardVariantsTest, CheckTopRowAlias) {
+  // Add fake keyboards based on layout type.
+  fake_keyboard_manager_->RemoveAllDevices();
+  for (int i = 0; const std::string& layout : keyboard_layout_types_) {
+    ui::KeyboardDevice fake_keyboard(
+        /*id=*/i, /*type=*/keyboard_connection_type_[i],
+        /*name=*/layout);
+    fake_keyboard.sys_path = base::FilePath("path" + layout);
+    fake_keyboard.vendor_id = i;
+    fake_keyboard.product_id = i;
+    fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard, layout);
+    SetTopRowAsFKeysForKeyboard(fake_keyboard, top_row_are_fkeys_[i]);
+    i++;
+  }
+
+  AcceleratorAliasConverter accelerator_alias_converter_;
+
+  std::vector<ui::Accelerator> accelerator_alias =
+      accelerator_alias_converter_.CreateAcceleratorAlias(accelerator_);
+  base::ranges::sort(accelerator_alias);
+  base::ranges::sort(expected_accelerators_);
+
+  ASSERT_EQ(expected_accelerators_.size(), accelerator_alias.size());
+  for (size_t i = 0; i < expected_accelerators_.size(); i++) {
+    EXPECT_EQ(expected_accelerators_[i], accelerator_alias[i]);
+  }
+}
+
 }  // namespace ash
diff --git a/ash/app_list/views/app_list_bubble_view.cc b/ash/app_list/views/app_list_bubble_view.cc
index d4e1c8948..7f6a3e11 100644
--- a/ash/app_list/views/app_list_bubble_view.cc
+++ b/ash/app_list/views/app_list_bubble_view.cc
@@ -234,8 +234,7 @@
                        : views::HighlightBorder::Type::kHighlightBorder1,
       /*insets_type=*/views::HighlightBorder::InsetsType::kHalfInsets));
 
-  views::FillLayout* layout =
-      SetLayoutManager(std::make_unique<views::FillLayout>());
+  SetLayoutManager(std::make_unique<views::FillLayout>());
   a11y_announcer_ = std::make_unique<AppListA11yAnnouncer>(
       AddChildView(std::make_unique<views::View>()));
   InitContentsView(drag_and_drop_host);
@@ -248,7 +247,7 @@
 
   InitFolderView(drag_and_drop_host);
   // Folder view is laid out manually based on its contents.
-  layout->SetChildViewIgnoredByLayout(folder_view_, true);
+  folder_view_->SetProperty(views::kViewIgnoredByLayoutKey, true);
 
   AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE));
   AddAccelerator(ui::Accelerator(ui::VKEY_BROWSER_BACK, ui::EF_NONE));
diff --git a/ash/app_list/views/continue_task_container_view.cc b/ash/app_list/views/continue_task_container_view.cc
index d4135e6..b7dd6c4 100644
--- a/ash/app_list/views/continue_task_container_view.cc
+++ b/ash/app_list/views/continue_task_container_view.cc
@@ -33,6 +33,7 @@
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/flex_layout.h"
 #include "ui/views/layout/table_layout.h"
+#include "ui/views/view_class_properties.h"
 
 using views::BoxLayout;
 using views::FlexLayout;
@@ -125,10 +126,10 @@
   DCHECK(!update_callback_.is_null());
 
   if (tablet_mode_) {
-    InitializeFlexLayout();
+    InitializeTabletLayout();
   } else {
     columns_ = columns;
-    InitializeTableLayout();
+    InitializeClamshellLayout();
   }
   GetViewAccessibility().OverrideRole(ax::mojom::Role::kList);
   GetViewAccessibility().OverrideName(
@@ -481,11 +482,7 @@
 
 void ContinueTaskContainerView::RemoveViewFromLayout(ContinueTaskView* view) {
   view->SetEnabled(false);
-  if (table_layout_) {
-    table_layout_->SetChildViewIgnoredByLayout(view, true);
-  } else if (flex_layout_) {
-    flex_layout_->SetChildViewIgnoredByLayout(view, true);
-  }
+  view->SetProperty(views::kViewIgnoredByLayoutKey, true);
 }
 
 void ContinueTaskContainerView::ScheduleUpdate() {
@@ -498,13 +495,12 @@
   }
 }
 
-void ContinueTaskContainerView::InitializeFlexLayout() {
+void ContinueTaskContainerView::InitializeTabletLayout() {
   DCHECK(tablet_mode_);
-  DCHECK(!table_layout_);
   DCHECK(!columns_);
 
-  flex_layout_ = SetLayoutManager(std::make_unique<FlexLayout>());
-  flex_layout_->SetOrientation(views::LayoutOrientation::kHorizontal)
+  SetLayoutManager(std::make_unique<FlexLayout>())
+      ->SetOrientation(views::LayoutOrientation::kHorizontal)
       .SetMainAxisAlignment(views::LayoutAlignment::kCenter)
       .SetDefault(views::kMarginsKey,
                   gfx::Insets::TLBR(0, kColumnSpacingTablet, 0, 0))
@@ -514,36 +510,36 @@
                       views::MaximumFlexSizeRule::kScaleToMaximum));
 }
 
-void ContinueTaskContainerView::InitializeTableLayout() {
+void ContinueTaskContainerView::InitializeClamshellLayout() {
   DCHECK(!tablet_mode_);
-  DCHECK(!flex_layout_);
   DCHECK_GT(columns_, 0);
 
-  table_layout_ = SetLayoutManager(std::make_unique<views::TableLayout>());
+  auto* const table_layout =
+      SetLayoutManager(std::make_unique<views::TableLayout>());
   std::vector<size_t> linked_columns;
   for (int i = 0; i < columns_; i++) {
     if (i == 0) {
-      table_layout_->AddPaddingColumn(views::TableLayout::kFixedSize,
-                                      kColumnOuterSpacingClamshell);
+      table_layout->AddPaddingColumn(views::TableLayout::kFixedSize,
+                                     kColumnOuterSpacingClamshell);
     } else {
-      table_layout_->AddPaddingColumn(views::TableLayout::kFixedSize,
-                                      kColumnInnerSpacingClamshell);
+      table_layout->AddPaddingColumn(views::TableLayout::kFixedSize,
+                                     kColumnInnerSpacingClamshell);
     }
-    table_layout_->AddColumn(
+    table_layout->AddColumn(
         views::LayoutAlignment::kStretch, views::LayoutAlignment::kCenter,
         /*horizontal_resize=*/1.0f, views::TableLayout::ColumnSize::kFixed,
         /*fixed_width=*/0, /*min_width=*/0);
     linked_columns.push_back(2 * i + 1);
   }
-  table_layout_->AddPaddingColumn(views::TableLayout::kFixedSize,
-                                  kColumnOuterSpacingClamshell);
+  table_layout->AddPaddingColumn(views::TableLayout::kFixedSize,
+                                 kColumnOuterSpacingClamshell);
 
-  table_layout_->LinkColumnSizes(linked_columns);
+  table_layout->LinkColumnSizes(linked_columns);
   // Continue section only shows if there are 3 or more suggestions, so there
   // are always 2 rows.
-  table_layout_->AddRows(1, views::TableLayout::kFixedSize);
-  table_layout_->AddPaddingRow(views::TableLayout::kFixedSize, kRowSpacing);
-  table_layout_->AddRows(1, views::TableLayout::kFixedSize);
+  table_layout->AddRows(1, views::TableLayout::kFixedSize);
+  table_layout->AddPaddingRow(views::TableLayout::kFixedSize, kRowSpacing);
+  table_layout->AddRows(1, views::TableLayout::kFixedSize);
 }
 
 void ContinueTaskContainerView::MoveFocusUp() {
diff --git a/ash/app_list/views/continue_task_container_view.h b/ash/app_list/views/continue_task_container_view.h
index 6e5993b8..f68f0e1d 100644
--- a/ash/app_list/views/continue_task_container_view.h
+++ b/ash/app_list/views/continue_task_container_view.h
@@ -21,11 +21,6 @@
 #include "ui/compositor/layer_animator.h"
 #include "ui/views/view.h"
 
-namespace views {
-class FlexLayout;
-class TableLayout;
-}  // namespace views
-
 namespace ash {
 
 class AppListViewDelegate;
@@ -93,18 +88,15 @@
   // manager, and disables the view.
   void RemoveViewFromLayout(ContinueTaskView* view);
 
-  // Initializes the view's layout manager to use |flex_layout_|. FlexLayout is
-  // used in tablet mode only. Views will be laid out in a single row centered
-  // in the container. Number of items displayed will depend on available space.
-  // This will not enforce any number of `columns_`.
-  void InitializeFlexLayout();
+  // Lays out children in a single row centered in the container. Number of
+  // items displayed will depend on available space. This will not enforce any
+  // number of `columns_`.
+  void InitializeTabletLayout();
 
-  // Initializes the view's layout manager to use |table_layout_|. TableLayout
-  // is used in clamshell mode only. Views are laid out in a table with a
-  // specific number of `columns_`. This displays views to stretch as to use
-  // all vertical space available in the container. Extra views are added in
-  // multiple rows.
-  void InitializeTableLayout();
+  // Lays out children in a table with a specific number of `columns_`. This
+  // displays views to stretch as to use all vertical space available in the
+  // container. Extra views are added in multiple rows.
+  void InitializeClamshellLayout();
 
   // Describes how old task views should animate when the set of tasks shown in
   // the container updates.
@@ -172,14 +164,6 @@
   raw_ptr<SearchModel::SearchResults> results_ =
       nullptr;  // Owned by SearchModel.
 
-  // Only one of the layouts is to be set.
-  // `flex_layout_`  aligns the views as a single row centered in the container.
-  // Used in tablet mode.
-  raw_ptr<views::FlexLayout> flex_layout_ = nullptr;
-  // `table_layout_`  aligns the views as a table with multiple rows stretched
-  // to fill the container. Used in clamshell mode.
-  raw_ptr<views::TableLayout> table_layout_ = nullptr;
-
   // The list of tasks views for the container.
   std::vector<raw_ptr<ContinueTaskView, VectorExperimental>>
       suggestion_tasks_views_;
diff --git a/ash/app_list/views/search_box_view.cc b/ash/app_list/views/search_box_view.cc
index ac7055c..933e5a6 100644
--- a/ash/app_list/views/search_box_view.cc
+++ b/ash/app_list/views/search_box_view.cc
@@ -108,6 +108,10 @@
 // Border insets for SearchBoxView in bubble launcher.
 constexpr auto kBorderInsetsForAppListBubble = gfx::Insets::TLBR(4, 4, 4, 0);
 
+// Margins for the search box text field in bubble launcher.
+constexpr auto kTextFieldMarginsForAppListBubble =
+    gfx::Insets::TLBR(8, 0, 0, 0);
+
 // The default PlaceholderTextTypes used for productivity launcher. Randomly
 // selected when placeholder text would be shown.
 constexpr SearchBoxView::PlaceholderTextType kDefaultPlaceholders[] = {
@@ -570,6 +574,9 @@
   params.create_background = false;
   params.animate_changing_search_icon = false;
   params.increase_child_view_padding = true;
+  // Add margins to the text field because the BoxLayout vertical centering
+  // does not properly align the text baseline with the icons.
+  params.textfield_margins = kTextFieldMarginsForAppListBubble;
 
   SearchBoxViewBase::Init(params);
 
diff --git a/ash/app_list/views/search_result_view.h b/ash/app_list/views/search_result_view.h
index 59128ad..c719c58f 100644
--- a/ash/app_list/views/search_result_view.h
+++ b/ash/app_list/views/search_result_view.h
@@ -29,6 +29,7 @@
 namespace test {
 class SearchResultListViewTest;
 class SearchResultViewWidgetTest;
+class SearchResultViewPixelTest;
 }  // namespace test
 
 class AppListViewDelegate;
@@ -222,6 +223,7 @@
 
  private:
   friend class test::SearchResultListViewTest;
+  friend class SearchResultViewPixelTest;
   friend class SearchResultListView;
   friend class SearchResultViewWidgetTest;
 
diff --git a/ash/app_list/views/search_result_view_pixeltest.cc b/ash/app_list/views/search_result_view_pixeltest.cc
new file mode 100644
index 0000000..1fe00399
--- /dev/null
+++ b/ash/app_list/views/search_result_view_pixeltest.cc
@@ -0,0 +1,165 @@
+// 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 <cstddef>
+#include "ash/app_list/model/app_list_test_model.h"
+#include "ash/app_list/model/search/test_search_result.h"
+#include "ash/app_list/test/app_list_test_helper.h"
+#include "ash/app_list/views/app_list_bubble_search_page.h"
+#include "ash/app_list/views/app_list_search_view.h"
+#include "ash/app_list/views/result_selection_controller.h"
+#include "ash/app_list/views/search_result_container_view.h"
+#include "ash/app_list/views/search_result_list_view.h"
+#include "ash/app_list/views/search_result_page_view.h"
+#include "ash/app_list/views/search_result_view.h"
+#include "ash/constants/ash_features.h"
+#include "ash/public/cpp/app_list/app_list_types.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/pixel/ash_pixel_differ.h"
+#include "ash/test/pixel/ash_pixel_test_init_params.h"
+#include "ash/wm/tablet_mode/tablet_mode_controller_test_api.h"
+#include "base/test/scoped_feature_list.h"
+#include "chromeos/constants/chromeos_features.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/view_utils.h"
+
+namespace ash {
+
+class SearchResultViewPixelTest
+    : public AshTestBase,
+      public testing::WithParamInterface<
+          std::tuple</*use_rtl=*/bool, /*use_tablet_mode=*/bool>> {
+ public:
+  SearchResultViewPixelTest()
+      : use_rtl_(std::get<0>(GetParam())),
+        use_tablet_mode_(std::get<1>(GetParam())) {}
+
+  std::optional<pixel_test::InitParams> CreatePixelTestInitParams()
+      const override {
+    pixel_test::InitParams init_params;
+    init_params.under_rtl = use_rtl();
+    return init_params;
+  }
+
+  // AshTestBase:
+  void SetUp() override {
+    scoped_feature_list_.InitWithFeatures(
+        {chromeos::features::kCrosWebAppShortcutUiUpdate,
+         features::kSeparateWebAppShortcutBadgeIcon},
+        {});
+    AshTestBase::SetUp();
+
+    if (use_tablet_mode_) {
+      ash::TabletModeControllerTestApi().EnterTabletMode();
+    }
+  }
+
+  bool use_rtl() const { return use_rtl_; }
+
+  bool use_tablet_mode() const { return use_tablet_mode_; }
+
+  AppListSearchView* GetSearchView() {
+    if (use_tablet_mode()) {
+      return GetAppListTestHelper()
+          ->GetFullscreenSearchResultPageView()
+          ->search_view();
+    }
+    return GetAppListTestHelper()->GetBubbleAppListSearchView();
+  }
+
+  bool IsWebAppShortcutStyle(SearchResultView* view) {
+    return view->use_webapp_shortcut_style_;
+  }
+
+  bool IsBadgeIconViewVisible(SearchResultView* view) {
+    return view->badge_icon_view_->GetVisible();
+  }
+
+  void SetUpWebAppSearchResult(SearchModel::SearchResults* results,
+                               int init_id,
+                               int display_score,
+                               bool best_match) {
+    std::unique_ptr<TestSearchResult> result =
+        std::make_unique<TestSearchResult>();
+    result->set_result_id(base::NumberToString(init_id));
+    result->set_display_type(ash::SearchResultDisplayType::kList);
+    result->SetTitle(
+        base::UTF8ToUTF16(base::StringPrintf("Result %d", init_id)));
+    result->set_display_score(display_score);
+    result->SetDetails(u"Detail");
+    result->set_best_match(best_match);
+    result->set_category(AppListSearchResultCategory::kAppShortcuts);
+    result->set_result_type(AppListSearchResultType::kAppShortcutV2);
+    result->SetIconAndBadgeIcon();
+    results->Add(std::move(result));
+  }
+
+  std::vector<raw_ptr<SearchResultContainerView, VectorExperimental>>
+  GetAppShortcutResultContainers() {
+    std::vector<raw_ptr<SearchResultContainerView, VectorExperimental>>
+        app_result_containers = {};
+    for (const auto& result_container :
+         GetSearchView()->result_container_views_for_test()) {
+      if (result_container->GetFirstResultView() &&
+          result_container->GetFirstResultView()->result()->result_type() ==
+              SearchResult::ResultType::kAppShortcutV2) {
+        app_result_containers.push_back(result_container);
+      }
+    }
+    return app_result_containers;
+  }
+
+ private:
+  const bool use_rtl_;
+  const bool use_tablet_mode_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         SearchResultViewPixelTest,
+                         testing::Combine(
+                             /*use_rtl=*/testing::Bool(),
+                             /*use_tablet_mode=*/testing::Bool()));
+
+TEST_P(SearchResultViewPixelTest, WebAppShortcutIconEffectsExists) {
+  auto* test_helper = GetAppListTestHelper();
+  test_helper->ShowAppList();
+
+  PressAndReleaseKey(ui::VKEY_A);
+  SearchModel::SearchResults* results = test_helper->GetSearchResults();
+
+  // Create categorized results and order categories as kApps.
+  std::vector<ash::AppListSearchResultCategory>* ordered_categories =
+      test_helper->GetOrderedResultCategories();
+  ordered_categories->push_back(
+      ash::AppListSearchResultCategory::kAppShortcuts);
+  SetUpWebAppSearchResult(results, 1, 1, false);
+
+  // Verify that search containers have a scheduled update, and ensure they get
+  // run.
+  std::vector<raw_ptr<SearchResultContainerView, VectorExperimental>>
+      result_containers = GetSearchView()->result_container_views_for_test();
+  for (ash::SearchResultContainerView* container : result_containers) {
+    EXPECT_TRUE(container->RunScheduledUpdateForTest());
+  }
+
+  // Verify result container with web app shortuct result is visible.
+  std::vector<raw_ptr<SearchResultContainerView, VectorExperimental>>
+      app_result_containers = GetAppShortcutResultContainers();
+  EXPECT_EQ(app_result_containers.size(), 1.0f);
+  EXPECT_TRUE(app_result_containers[0]->GetVisible());
+
+  // Verify web app shortcut badge logic.
+  SearchResultView* app_result = views::AsViewClass<SearchResultView>(
+      app_result_containers[0]->GetFirstResultView());
+  EXPECT_TRUE(IsWebAppShortcutStyle(app_result));
+  EXPECT_FALSE(IsBadgeIconViewVisible(app_result));
+
+  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+      "web_app_shortcut_icon_effects_exists", /*revision_number*/ 0,
+      app_result));
+}
+
+}  // namespace ash
diff --git a/ash/assistant/ui/assistant_web_container_view.cc b/ash/assistant/ui/assistant_web_container_view.cc
index 04d07a0..f146303 100644
--- a/ash/assistant/ui/assistant_web_container_view.cc
+++ b/ash/assistant/ui/assistant_web_container_view.cc
@@ -234,7 +234,7 @@
   SetBackground(views::CreateRoundedRectBackground(color, background_radii_));
 }
 
-BEGIN_METADATA(AssistantWebContainerView, views::WidgetDelegateView)
+BEGIN_METADATA(AssistantWebContainerView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/assistant/ui/assistant_web_container_view.h b/ash/assistant/ui/assistant_web_container_view.h
index 1aa59e65..e7813fe 100644
--- a/ash/assistant/ui/assistant_web_container_view.h
+++ b/ash/assistant/ui/assistant_web_container_view.h
@@ -24,9 +24,9 @@
 class COMPONENT_EXPORT(ASSISTANT_UI) AssistantWebContainerView
     : public views::WidgetDelegateView,
       public AshWebView::Observer {
- public:
-  METADATA_HEADER(AssistantWebContainerView);
+  METADATA_HEADER(AssistantWebContainerView, views::WidgetDelegateView)
 
+ public:
   explicit AssistantWebContainerView(
       AssistantWebViewDelegate* web_container_view_delegate);
 
diff --git a/ash/assistant/ui/base/assistant_button.cc b/ash/assistant/ui/base/assistant_button.cc
index 8b98c37..cab97c7 100644
--- a/ash/assistant/ui/base/assistant_button.cc
+++ b/ash/assistant/ui/base/assistant_button.cc
@@ -171,7 +171,7 @@
   listener_->OnButtonPressed(id_);
 }
 
-BEGIN_METADATA(AssistantButton, views::ImageButton)
+BEGIN_METADATA(AssistantButton)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/assistant/ui/base/assistant_button.h b/ash/assistant/ui/base/assistant_button.h
index 02760715..e6fc6e9 100644
--- a/ash/assistant/ui/base/assistant_button.h
+++ b/ash/assistant/ui/base/assistant_button.h
@@ -34,9 +34,9 @@
 
 class COMPONENT_EXPORT(ASSISTANT_UI) AssistantButton
     : public views::ImageButton {
- public:
-  METADATA_HEADER(AssistantButton);
+  METADATA_HEADER(AssistantButton, views::ImageButton)
 
+ public:
   // Initialization parameters for customizing the Assistant button.
   struct InitParams {
     InitParams();
diff --git a/ash/assistant/ui/base/assistant_scroll_view.cc b/ash/assistant/ui/base/assistant_scroll_view.cc
index c21c4f3..3d7a14d 100644
--- a/ash/assistant/ui/base/assistant_scroll_view.cc
+++ b/ash/assistant/ui/base/assistant_scroll_view.cc
@@ -94,7 +94,7 @@
       views::ScrollView::ScrollBarMode::kHiddenButEnabled);
 }
 
-BEGIN_METADATA(AssistantScrollView, views::ScrollView)
+BEGIN_METADATA(AssistantScrollView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/assistant/ui/base/assistant_scroll_view.h b/ash/assistant/ui/base/assistant_scroll_view.h
index 88a4fa3..b2262d5 100644
--- a/ash/assistant/ui/base/assistant_scroll_view.h
+++ b/ash/assistant/ui/base/assistant_scroll_view.h
@@ -17,6 +17,8 @@
 class COMPONENT_EXPORT(ASSISTANT_UI) AssistantScrollView
     : public views::ScrollView,
       public views::ViewObserver {
+  METADATA_HEADER(AssistantScrollView, views::ScrollView)
+
  public:
   class Observer : public base::CheckedObserver {
    public:
@@ -28,8 +30,6 @@
     ~Observer() override = default;
   };
 
-  METADATA_HEADER(AssistantScrollView);
-
   AssistantScrollView();
   AssistantScrollView(const AssistantScrollView&) = delete;
   AssistantScrollView& operator=(const AssistantScrollView) = delete;
diff --git a/ash/assistant/ui/dialog_plate/mic_view.cc b/ash/assistant/ui/dialog_plate/mic_view.cc
index f73aa7f..8491d843 100644
--- a/ash/assistant/ui/dialog_plate/mic_view.cc
+++ b/ash/assistant/ui/dialog_plate/mic_view.cc
@@ -126,7 +126,7 @@
   logo_view_->SetState(mic_state, animate);
 }
 
-BEGIN_METADATA(MicView, AssistantButton)
+BEGIN_METADATA(MicView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/assistant/ui/dialog_plate/mic_view.h b/ash/assistant/ui/dialog_plate/mic_view.h
index d70628e..3d1661d 100644
--- a/ash/assistant/ui/dialog_plate/mic_view.h
+++ b/ash/assistant/ui/dialog_plate/mic_view.h
@@ -24,9 +24,9 @@
     : public AssistantButton,
       public AssistantControllerObserver,
       public AssistantInteractionModelObserver {
- public:
-  METADATA_HEADER(MicView);
+  METADATA_HEADER(MicView, AssistantButton)
 
+ public:
   MicView(AssistantButtonListener* listener,
           AssistantButtonId button_id);
   MicView(const MicView&) = delete;
diff --git a/ash/assistant/ui/main_stage/animated_container_view.cc b/ash/assistant/ui/main_stage/animated_container_view.cc
index 8407683..b385690 100644
--- a/ash/assistant/ui/main_stage/animated_container_view.cc
+++ b/ash/assistant/ui/main_stage/animated_container_view.cc
@@ -373,7 +373,7 @@
   return true;
 }
 
-BEGIN_METADATA(AnimatedContainerView, AssistantScrollView)
+BEGIN_METADATA(AnimatedContainerView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/assistant/ui/main_stage/animated_container_view.h b/ash/assistant/ui/main_stage/animated_container_view.h
index 84fa3752..061f1f50 100644
--- a/ash/assistant/ui/main_stage/animated_container_view.h
+++ b/ash/assistant/ui/main_stage/animated_container_view.h
@@ -61,11 +61,11 @@
       public AssistantControllerObserver,
       public AssistantInteractionModelObserver,
       public AssistantResponseObserver {
+  METADATA_HEADER(AnimatedContainerView, AssistantScrollView)
+
  public:
   using AssistantSuggestion = assistant::AssistantSuggestion;
 
-  METADATA_HEADER(AnimatedContainerView);
-
   explicit AnimatedContainerView(AssistantViewDelegate* delegate);
   AnimatedContainerView(const AnimatedContainerView&) = delete;
   AnimatedContainerView& operator=(const AnimatedContainerView&) = delete;
diff --git a/ash/assistant/ui/main_stage/assistant_onboarding_suggestion_view.cc b/ash/assistant/ui/main_stage/assistant_onboarding_suggestion_view.cc
index bb28a3a..a5ff6f4 100644
--- a/ash/assistant/ui/main_stage/assistant_onboarding_suggestion_view.cc
+++ b/ash/assistant/ui/main_stage/assistant_onboarding_suggestion_view.cc
@@ -199,22 +199,21 @@
       AddChildView(std::make_unique<views::InkDropContainerView>());
 
   // Layout.
-  auto& layout =
-      SetLayoutManager(std::make_unique<views::FlexLayout>())
-          ->SetCollapseMargins(true)
-          .SetCrossAxisAlignment(views::LayoutAlignment::kCenter)
-          .SetDefault(views::kFlexBehaviorKey, views::FlexSpecification())
-          .SetDefault(views::kMarginsKey, gfx::Insets::VH(0, 2 * kSpacingDip))
-          .SetInteriorMargin(gfx::Insets::VH(0, 2 * kMarginDip))
-          .SetOrientation(views::LayoutOrientation::kHorizontal);
+  SetLayoutManager(std::make_unique<views::FlexLayout>())
+      ->SetCollapseMargins(true)
+      .SetCrossAxisAlignment(views::LayoutAlignment::kCenter)
+      .SetDefault(views::kFlexBehaviorKey, views::FlexSpecification())
+      .SetDefault(views::kMarginsKey, gfx::Insets::VH(0, 2 * kSpacingDip))
+      .SetInteriorMargin(gfx::Insets::VH(0, 2 * kMarginDip))
+      .SetOrientation(views::LayoutOrientation::kHorizontal);
 
-  // NOTE: Our |layout| ignores the view for drawing focus as it is a special
-  // view which lays out itself. Removing this would cause it *not* to paint.
-  layout.SetChildViewIgnoredByLayout(views::FocusRing::Get(this), true);
+  // Ignore the focus ring, which lays out itself.
+  views::FocusRing::Get(this)->SetProperty(views::kViewIgnoredByLayoutKey,
+                                           true);
 
-  // NOTE: Our |ink_drop_container_| serves only to hold reference to ink drop
-  // layers for painting purposes. It can be completely ignored by our |layout|.
-  layout.SetChildViewIgnoredByLayout(ink_drop_container_, true);
+  // Ignore the `ink_drop_container_`, which serves only to hold reference to
+  // ink drop layers for painting purposes.
+  ink_drop_container_->SetProperty(views::kViewIgnoredByLayoutKey, true);
 
   // Icon.
   icon_ = AddChildView(std::make_unique<views::ImageView>());
@@ -262,7 +261,7 @@
   delegate_->OnSuggestionPressed(suggestion_id_);
 }
 
-BEGIN_METADATA(AssistantOnboardingSuggestionView, views::Button)
+BEGIN_METADATA(AssistantOnboardingSuggestionView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/assistant/ui/main_stage/assistant_onboarding_suggestion_view.h b/ash/assistant/ui/main_stage/assistant_onboarding_suggestion_view.h
index 0dedb8d..abd313f2 100644
--- a/ash/assistant/ui/main_stage/assistant_onboarding_suggestion_view.h
+++ b/ash/assistant/ui/main_stage/assistant_onboarding_suggestion_view.h
@@ -27,9 +27,9 @@
 
 class COMPONENT_EXPORT(ASSISTANT_UI) AssistantOnboardingSuggestionView
     : public views::Button {
- public:
-  METADATA_HEADER(AssistantOnboardingSuggestionView);
+  METADATA_HEADER(AssistantOnboardingSuggestionView, views::Button)
 
+ public:
   AssistantOnboardingSuggestionView(
       AssistantViewDelegate* delegate,
       const assistant::AssistantSuggestion& suggestion,
diff --git a/ash/assistant/ui/main_stage/assistant_opt_in_view.cc b/ash/assistant/ui/main_stage/assistant_opt_in_view.cc
index 049cf5b..0000786b 100644
--- a/ash/assistant/ui/main_stage/assistant_opt_in_view.cc
+++ b/ash/assistant/ui/main_stage/assistant_opt_in_view.cc
@@ -187,7 +187,7 @@
   delegate_->OnOptInButtonPressed();
 }
 
-BEGIN_METADATA(AssistantOptInView, views::View)
+BEGIN_METADATA(AssistantOptInView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/assistant/ui/main_stage/assistant_opt_in_view.h b/ash/assistant/ui/main_stage/assistant_opt_in_view.h
index 4c2d8cd..5a2472e6 100644
--- a/ash/assistant/ui/main_stage/assistant_opt_in_view.h
+++ b/ash/assistant/ui/main_stage/assistant_opt_in_view.h
@@ -25,9 +25,9 @@
 class COMPONENT_EXPORT(ASSISTANT_UI) AssistantOptInView
     : public views::View,
       public AssistantStateObserver {
- public:
-  METADATA_HEADER(AssistantOptInView);
+  METADATA_HEADER(AssistantOptInView, views::View)
 
+ public:
   explicit AssistantOptInView(AssistantViewDelegate* delegate_);
   AssistantOptInView(const AssistantOptInView&) = delete;
   AssistantOptInView& operator=(const AssistantOptInView&) = delete;
diff --git a/ash/assistant/ui/main_stage/chip_view.cc b/ash/assistant/ui/main_stage/chip_view.cc
index 0fcd9ac..93d55a99 100644
--- a/ash/assistant/ui/main_stage/chip_view.cc
+++ b/ash/assistant/ui/main_stage/chip_view.cc
@@ -194,7 +194,7 @@
   return text_view_->GetText();
 }
 
-BEGIN_METADATA(ChipView, views::Button)
+BEGIN_METADATA(ChipView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/assistant/ui/main_stage/chip_view.h b/ash/assistant/ui/main_stage/chip_view.h
index d208a2c..a04ca68e 100644
--- a/ash/assistant/ui/main_stage/chip_view.h
+++ b/ash/assistant/ui/main_stage/chip_view.h
@@ -16,6 +16,8 @@
 namespace ash {
 
 class COMPONENT_EXPORT(ASSISTANT_UI) ChipView : public views::Button {
+  METADATA_HEADER(ChipView, views::Button)
+
  public:
   enum Type { kDefault, kLarge };
 
@@ -41,8 +43,6 @@
   void SetText(const std::u16string& text);
   const std::u16string& GetText() const;
 
-  METADATA_HEADER(ChipView);
-
  private:
   const Type type_;
   raw_ptr<views::BoxLayout> layout_manager_;
diff --git a/ash/assistant/ui/main_stage/suggestion_chip_view.cc b/ash/assistant/ui/main_stage/suggestion_chip_view.cc
index bd19d93b..4f4f480 100644
--- a/ash/assistant/ui/main_stage/suggestion_chip_view.cc
+++ b/ash/assistant/ui/main_stage/suggestion_chip_view.cc
@@ -44,7 +44,7 @@
 
 SuggestionChipView::~SuggestionChipView() = default;
 
-BEGIN_METADATA(SuggestionChipView, ChipView)
+BEGIN_METADATA(SuggestionChipView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/assistant/ui/main_stage/suggestion_chip_view.h b/ash/assistant/ui/main_stage/suggestion_chip_view.h
index 80c74c4..74815f7 100644
--- a/ash/assistant/ui/main_stage/suggestion_chip_view.h
+++ b/ash/assistant/ui/main_stage/suggestion_chip_view.h
@@ -19,11 +19,11 @@
 
 // View representing a suggestion chip for Assistant.
 class COMPONENT_EXPORT(ASSISTANT_UI) SuggestionChipView : public ChipView {
+  METADATA_HEADER(SuggestionChipView, ChipView)
+
  public:
   using AssistantSuggestion = assistant::AssistantSuggestion;
 
-  METADATA_HEADER(SuggestionChipView);
-
   SuggestionChipView(AssistantViewDelegate* delegate,
                      const AssistantSuggestion& suggestion);
   SuggestionChipView(const SuggestionChipView&) = delete;
diff --git a/ash/assistant/ui/main_stage/suggestion_container_view.cc b/ash/assistant/ui/main_stage/suggestion_container_view.cc
index 5266459..56154d3 100644
--- a/ash/assistant/ui/main_stage/suggestion_container_view.cc
+++ b/ash/assistant/ui/main_stage/suggestion_container_view.cc
@@ -267,7 +267,7 @@
   delegate()->OnSuggestionPressed(selected_chip_->suggestion_id());
 }
 
-BEGIN_METADATA(SuggestionContainerView, AnimatedContainerView)
+BEGIN_METADATA(SuggestionContainerView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/assistant/ui/main_stage/suggestion_container_view.h b/ash/assistant/ui/main_stage/suggestion_container_view.h
index 6a24b64..fa7c3456 100644
--- a/ash/assistant/ui/main_stage/suggestion_container_view.h
+++ b/ash/assistant/ui/main_stage/suggestion_container_view.h
@@ -34,11 +34,11 @@
     : public AnimatedContainerView,
       public AssistantSuggestionsModelObserver,
       public AssistantUiModelObserver {
+  METADATA_HEADER(SuggestionContainerView, AnimatedContainerView)
+
  public:
   using AssistantSuggestion = assistant::AssistantSuggestion;
 
-  METADATA_HEADER(SuggestionContainerView);
-
   explicit SuggestionContainerView(AssistantViewDelegate* delegate);
   SuggestionContainerView(const SuggestionContainerView&) = delete;
   SuggestionContainerView& operator=(const SuggestionContainerView&) = delete;
diff --git a/ash/capture_mode/capture_button_view.cc b/ash/capture_mode/capture_button_view.cc
index 6c711e55..fb68fca 100644
--- a/ash/capture_mode/capture_button_view.cc
+++ b/ash/capture_mode/capture_button_view.cc
@@ -241,7 +241,7 @@
       insets, kDropDownButtonRoundedCorners);
 }
 
-BEGIN_METADATA(CaptureButtonView, views::View)
+BEGIN_METADATA(CaptureButtonView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/capture_mode/capture_button_view.h b/ash/capture_mode/capture_button_view.h
index 8ed1d15..4061ced 100644
--- a/ash/capture_mode/capture_button_view.h
+++ b/ash/capture_mode/capture_button_view.h
@@ -27,9 +27,9 @@
 // if multiple recording formats are supported, it will display a drop down
 // button which when pressed will open the recording type selection menu.
 class CaptureButtonView : public views::View {
- public:
-  METADATA_HEADER(CaptureButtonView);
+  METADATA_HEADER(CaptureButtonView, views::View)
 
+ public:
   CaptureButtonView(views::Button::PressedCallback on_capture_button_pressed,
                     views::Button::PressedCallback on_drop_down_pressed,
                     CaptureModeBehavior* active_behavior);
diff --git a/ash/capture_mode/capture_label_view.cc b/ash/capture_mode/capture_label_view.cc
index ace0728..ed15991b 100644
--- a/ash/capture_mode/capture_label_view.cc
+++ b/ash/capture_mode/capture_label_view.cc
@@ -464,7 +464,7 @@
   std::move(countdown_finished_callback_).Run();  // `this` is destroyed here.
 }
 
-BEGIN_METADATA(CaptureLabelView, views::View)
+BEGIN_METADATA(CaptureLabelView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/capture_mode/capture_label_view.h b/ash/capture_mode/capture_label_view.h
index 77ff3e3..28f9152 100644
--- a/ash/capture_mode/capture_label_view.h
+++ b/ash/capture_mode/capture_label_view.h
@@ -31,9 +31,9 @@
 // transform into a 3 second countdown timer.
 class ASH_EXPORT CaptureLabelView : public views::View,
                                     public gfx::AnimationDelegate {
- public:
-  METADATA_HEADER(CaptureLabelView);
+  METADATA_HEADER(CaptureLabelView, views::View)
 
+ public:
   CaptureLabelView(CaptureModeSession* capture_mode_session,
                    views::Button::PressedCallback on_capture_button_pressed,
                    views::Button::PressedCallback on_drop_down_button_pressed);
diff --git a/ash/capture_mode/capture_mode_ash_notification_view.cc b/ash/capture_mode/capture_mode_ash_notification_view.cc
index bc01213..8f7569d 100644
--- a/ash/capture_mode/capture_mode_ash_notification_view.cc
+++ b/ash/capture_mode/capture_mode_ash_notification_view.cc
@@ -84,7 +84,7 @@
           : capture_mode_util::CreatePlayIconView());
 }
 
-BEGIN_METADATA(CaptureModeAshNotificationView, AshNotificationView)
+BEGIN_METADATA(CaptureModeAshNotificationView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_ash_notification_view.h b/ash/capture_mode/capture_mode_ash_notification_view.h
index 5379471..d90a19a 100644
--- a/ash/capture_mode/capture_mode_ash_notification_view.h
+++ b/ash/capture_mode/capture_mode_ash_notification_view.h
@@ -25,9 +25,9 @@
 // notification by either showing a banner on top of the notification image for
 // image captures, or a play icon on top of the video thumbnail.
 class ASH_EXPORT CaptureModeAshNotificationView : public AshNotificationView {
- public:
-  METADATA_HEADER(CaptureModeAshNotificationView);
+  METADATA_HEADER(CaptureModeAshNotificationView, AshNotificationView)
 
+ public:
   CaptureModeAshNotificationView(
       const message_center::Notification& notification,
       CaptureModeType capture_type,
diff --git a/ash/capture_mode/capture_mode_camera_preview_view.cc b/ash/capture_mode/capture_mode_camera_preview_view.cc
index 67d06f9..4ca2ed5 100644
--- a/ash/capture_mode/capture_mode_camera_preview_view.cc
+++ b/ash/capture_mode/capture_mode_camera_preview_view.cc
@@ -89,7 +89,7 @@
   camera_preview_view_->ScheduleRefreshResizeButtonVisibility();
 }
 
-BEGIN_METADATA(CameraPreviewResizeButton, IconButton)
+BEGIN_METADATA(CameraPreviewResizeButton)
 END_METADATA
 
 // -----------------------------------------------------------------------------
@@ -469,7 +469,7 @@
   UpdateA11yOverrideWindow();
 }
 
-BEGIN_METADATA(CameraPreviewView, views::View)
+BEGIN_METADATA(CameraPreviewView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_camera_preview_view.h b/ash/capture_mode/capture_mode_camera_preview_view.h
index 930de75..4ed734b 100644
--- a/ash/capture_mode/capture_mode_camera_preview_view.h
+++ b/ash/capture_mode/capture_mode_camera_preview_view.h
@@ -40,9 +40,9 @@
 class CameraPreviewResizeButton
     : public IconButton,
       public CaptureModeSessionFocusCycler::HighlightableView {
- public:
-  METADATA_HEADER(CameraPreviewResizeButton);
+  METADATA_HEADER(CameraPreviewResizeButton, IconButton)
 
+ public:
   CameraPreviewResizeButton(CameraPreviewView* camera_preview_view,
                             views::Button::PressedCallback callback,
                             const gfx::VectorIcon& icon);
@@ -66,9 +66,9 @@
     : public views::View,
       public CaptureModeSessionFocusCycler::HighlightableView,
       public AccessibilityObserver {
- public:
-  METADATA_HEADER(CameraPreviewView);
+  METADATA_HEADER(CameraPreviewView, views::View)
 
+ public:
   CameraPreviewView(
       CaptureModeCameraController* camera_controller,
       const CameraId& camera_id,
diff --git a/ash/capture_mode/capture_mode_camera_unittests.cc b/ash/capture_mode/capture_mode_camera_unittests.cc
index 673dc7cf..f7473418 100644
--- a/ash/capture_mode/capture_mode_camera_unittests.cc
+++ b/ash/capture_mode/capture_mode_camera_unittests.cc
@@ -4644,7 +4644,8 @@
 }
 
 // Regression test for https://crbug.com/1316230.
-TEST_P(CaptureModeCameraFramesTest, CameraFatalErrors) {
+// Flaky (b/323909190)
+TEST_P(CaptureModeCameraFramesTest, DISABLED_CameraFatalErrors) {
   CaptureModeTestApi().StartForFullscreen(/*for_video=*/true);
   auto* camera_controller = GetCameraController();
   EXPECT_TRUE(camera_controller->selected_camera().is_valid());
diff --git a/ash/capture_mode/capture_mode_menu_group.cc b/ash/capture_mode/capture_mode_menu_group.cc
index 56a4f22b..0330ad7 100644
--- a/ash/capture_mode/capture_mode_menu_group.cc
+++ b/ash/capture_mode/capture_mode_menu_group.cc
@@ -71,9 +71,9 @@
 class CaptureModeMenuHeader
     : public views::View,
       public CaptureModeSessionFocusCycler::HighlightableView {
- public:
-  METADATA_HEADER(CaptureModeMenuHeader);
+  METADATA_HEADER(CaptureModeMenuHeader, views::View)
 
+ public:
   CaptureModeMenuHeader(const gfx::VectorIcon& icon,
                         std::u16string header_laber,
                         bool managed_by_policy)
@@ -132,7 +132,7 @@
   raw_ptr<views::ImageView> managed_icon_view_;
 };
 
-BEGIN_METADATA(CaptureModeMenuHeader, views::View)
+BEGIN_METADATA(CaptureModeMenuHeader)
 END_METADATA
 
 // -----------------------------------------------------------------------------
@@ -143,9 +143,9 @@
 class CaptureModeMenuItem
     : public views::Button,
       public CaptureModeSessionFocusCycler::HighlightableView {
- public:
-  METADATA_HEADER(CaptureModeMenuItem);
+  METADATA_HEADER(CaptureModeMenuItem, views::Button)
 
+ public:
   // If `indented` is true, the content of this menu item will have some extra
   // padding from the left so that it appears indented. This is useful when this
   // item is added to a group that has a header, and it's desired to make it
@@ -176,7 +176,7 @@
   raw_ptr<views::Label> label_view_;
 };
 
-BEGIN_METADATA(CaptureModeMenuItem, views::Button)
+BEGIN_METADATA(CaptureModeMenuItem)
 END_METADATA
 
 // -----------------------------------------------------------------------------
@@ -189,9 +189,9 @@
 class CaptureModeOption
     : public views::Button,
       public CaptureModeSessionFocusCycler::HighlightableView {
- public:
-  METADATA_HEADER(CaptureModeOption);
+  METADATA_HEADER(CaptureModeOption, views::Button)
 
+ public:
   // If `indented` is true, the content of this option will have some extra
   // padding from the left so that it appears indented. This is useful when this
   // option is added to a group that has a header, and it's desired to make it
@@ -341,7 +341,7 @@
   const int id_;
 };
 
-BEGIN_METADATA(CaptureModeOption, views::Button)
+BEGIN_METADATA(CaptureModeOption)
 END_METADATA
 
 // -----------------------------------------------------------------------------
@@ -505,7 +505,7 @@
   RefreshOptionsSelections();
 }
 
-BEGIN_METADATA(CaptureModeMenuGroup, views::View)
+BEGIN_METADATA(CaptureModeMenuGroup)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_menu_group.h b/ash/capture_mode/capture_mode_menu_group.h
index 1afbd00..613a40e 100644
--- a/ash/capture_mode/capture_mode_menu_group.h
+++ b/ash/capture_mode/capture_mode_menu_group.h
@@ -29,9 +29,9 @@
 // independent section in the settings menu. Each group can be created with a
 // header that has an icon and a label for the group, or be header-less.
 class ASH_EXPORT CaptureModeMenuGroup : public views::View {
- public:
-  METADATA_HEADER(CaptureModeMenuGroup);
+  METADATA_HEADER(CaptureModeMenuGroup, views::View)
 
+ public:
   class Delegate {
    public:
     // Called when user selects an option.
diff --git a/ash/capture_mode/capture_mode_menu_toggle_button.cc b/ash/capture_mode/capture_mode_menu_toggle_button.cc
index a451711..a458883 100644
--- a/ash/capture_mode/capture_mode_menu_toggle_button.cc
+++ b/ash/capture_mode/capture_mode_menu_toggle_button.cc
@@ -60,7 +60,7 @@
       color_provider->GetColor(kColorAshSwitchTrackColorInactive));
 }
 
-BEGIN_METADATA(CaptureModeMenuToggleButton, views::View)
+BEGIN_METADATA(CaptureModeMenuToggleButton)
 END_METADATA
 
 }  // namespace ash
\ No newline at end of file
diff --git a/ash/capture_mode/capture_mode_menu_toggle_button.h b/ash/capture_mode/capture_mode_menu_toggle_button.h
index 976f521..e33ca07 100644
--- a/ash/capture_mode/capture_mode_menu_toggle_button.h
+++ b/ash/capture_mode/capture_mode_menu_toggle_button.h
@@ -23,9 +23,9 @@
 // A view section in the capture mode settings menu that consists of a menu item
 // with a switch (toggle button).
 class CaptureModeMenuToggleButton : public views::View {
- public:
-  METADATA_HEADER(CaptureModeMenuToggleButton);
+  METADATA_HEADER(CaptureModeMenuToggleButton, views::View)
 
+ public:
   CaptureModeMenuToggleButton(const gfx::VectorIcon& icon,
                               const std::u16string& label_text,
                               bool enabled,
diff --git a/ash/capture_mode/capture_mode_settings_view.cc b/ash/capture_mode/capture_mode_settings_view.cc
index d7a83ca..148aa64aa 100644
--- a/ash/capture_mode/capture_mode_settings_view.cc
+++ b/ash/capture_mode/capture_mode_settings_view.cc
@@ -510,7 +510,7 @@
   CaptureModeController::Get()->EnableDemoTools(/*enable=*/!was_on);
 }
 
-BEGIN_METADATA(CaptureModeSettingsView, views::ScrollView)
+BEGIN_METADATA(CaptureModeSettingsView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/capture_mode/capture_mode_settings_view.h b/ash/capture_mode/capture_mode_settings_view.h
index dc91306..029ca0d 100644
--- a/ash/capture_mode/capture_mode_settings_view.h
+++ b/ash/capture_mode/capture_mode_settings_view.h
@@ -48,9 +48,9 @@
     : public views::ScrollView,
       public CaptureModeMenuGroup::Delegate,
       public CaptureModeCameraController::Observer {
- public:
-  METADATA_HEADER(CaptureModeSettingsView);
+  METADATA_HEADER(CaptureModeSettingsView, views::ScrollView)
 
+ public:
   CaptureModeSettingsView(CaptureModeSession* session,
                           CaptureModeBehavior* active_behavior);
   CaptureModeSettingsView(const CaptureModeSettingsView&) = delete;
diff --git a/ash/capture_mode/game_capture_bar_view.cc b/ash/capture_mode/game_capture_bar_view.cc
index eb2a793b..dd0ea47 100644
--- a/ash/capture_mode/game_capture_bar_view.cc
+++ b/ash/capture_mode/game_capture_bar_view.cc
@@ -36,7 +36,7 @@
   CaptureModeController::Get()->PerformCapture();
 }
 
-BEGIN_METADATA(GameCaptureBarView, CaptureModeBarView)
+BEGIN_METADATA(GameCaptureBarView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/capture_mode/game_capture_bar_view.h b/ash/capture_mode/game_capture_bar_view.h
index 3b8a69ec..6e636e6b 100644
--- a/ash/capture_mode/game_capture_bar_view.h
+++ b/ash/capture_mode/game_capture_bar_view.h
@@ -18,9 +18,9 @@
 // game capture session. The bar only includes a start recording button, the
 // settings and close buttons.
 class ASH_EXPORT GameCaptureBarView : public CaptureModeBarView {
- public:
-  METADATA_HEADER(GameCaptureBarView);
+  METADATA_HEADER(GameCaptureBarView, CaptureModeBarView)
 
+ public:
   GameCaptureBarView();
   GameCaptureBarView(const GameCaptureBarView&) = delete;
   GameCaptureBarView& operator=(const GameCaptureBarView&) = delete;
diff --git a/ash/components/arc/compat_mode/arc_splash_screen_dialog_view.cc b/ash/components/arc/compat_mode/arc_splash_screen_dialog_view.cc
index 01893f1..f7f959f6 100644
--- a/ash/components/arc/compat_mode/arc_splash_screen_dialog_view.cc
+++ b/ash/components/arc/compat_mode/arc_splash_screen_dialog_view.cc
@@ -419,7 +419,7 @@
   views::BubbleDialogDelegateView::CreateBubble(std::move(dialog_view))->Show();
 }
 
-BEGIN_METADATA(ArcSplashScreenDialogView, views::BubbleDialogDelegateView)
+BEGIN_METADATA(ArcSplashScreenDialogView)
 END_METADATA
 
 }  // namespace arc
diff --git a/ash/components/arc/compat_mode/arc_splash_screen_dialog_view.h b/ash/components/arc/compat_mode/arc_splash_screen_dialog_view.h
index 8aa4feb..0369e348 100644
--- a/ash/components/arc/compat_mode/arc_splash_screen_dialog_view.h
+++ b/ash/components/arc/compat_mode/arc_splash_screen_dialog_view.h
@@ -35,8 +35,9 @@
 class ArcSplashScreenDialogView : public views::BubbleDialogDelegateView,
                                   public views::ViewObserver,
                                   public wm::ActivationChangeObserver {
+  METADATA_HEADER(ArcSplashScreenDialogView, views::BubbleDialogDelegateView)
+
  public:
-  METADATA_HEADER(ArcSplashScreenDialogView);
   // TestApi is used for tests to get internal implementation details.
   class TestApi {
    public:
diff --git a/ash/components/arc/compat_mode/overlay_dialog.cc b/ash/components/arc/compat_mode/overlay_dialog.cc
index cefcd66c..ece92e9 100644
--- a/ash/components/arc/compat_mode/overlay_dialog.cc
+++ b/ash/components/arc/compat_mode/overlay_dialog.cc
@@ -88,7 +88,7 @@
   }
 }
 
-BEGIN_METADATA(OverlayDialog, views::FlexLayoutView)
+BEGIN_METADATA(OverlayDialog)
 END_METADATA
 
 }  // namespace arc
diff --git a/ash/components/arc/compat_mode/overlay_dialog.h b/ash/components/arc/compat_mode/overlay_dialog.h
index 36e5bf7..4cac0b1 100644
--- a/ash/components/arc/compat_mode/overlay_dialog.h
+++ b/ash/components/arc/compat_mode/overlay_dialog.h
@@ -22,9 +22,9 @@
 namespace arc {
 
 class OverlayDialog : public views::FlexLayoutView {
- public:
-  METADATA_HEADER(OverlayDialog);
+  METADATA_HEADER(OverlayDialog, views::FlexLayoutView)
 
+ public:
   OverlayDialog(const OverlayDialog&) = delete;
   OverlayDialog& operator=(const OverlayDialog&) = delete;
   ~OverlayDialog() override;
diff --git a/ash/components/arc/compat_mode/resize_confirmation_dialog_view.cc b/ash/components/arc/compat_mode/resize_confirmation_dialog_view.cc
index 50bd542..37311be 100644
--- a/ash/components/arc/compat_mode/resize_confirmation_dialog_view.cc
+++ b/ash/components/arc/compat_mode/resize_confirmation_dialog_view.cc
@@ -280,7 +280,7 @@
   views::BubbleDialogDelegateView::CreateBubble(std::move(dialog_view))->Show();
 }
 
-BEGIN_METADATA(ResizeConfirmationDialogView, views::BubbleDialogDelegateView)
+BEGIN_METADATA(ResizeConfirmationDialogView)
 END_METADATA
 
 }  // namespace arc
diff --git a/ash/components/arc/compat_mode/resize_confirmation_dialog_view.h b/ash/components/arc/compat_mode/resize_confirmation_dialog_view.h
index 9158f26..6dde0c7a5 100644
--- a/ash/components/arc/compat_mode/resize_confirmation_dialog_view.h
+++ b/ash/components/arc/compat_mode/resize_confirmation_dialog_view.h
@@ -30,8 +30,9 @@
 
 class ResizeConfirmationDialogView : public views::BubbleDialogDelegateView,
                                      public views::WidgetObserver {
+  METADATA_HEADER(ResizeConfirmationDialogView, views::BubbleDialogDelegateView)
+
  public:
-  METADATA_HEADER(ResizeConfirmationDialogView);
   // TestApi is used only in tests to get internal views.
   class TestApi {
    public:
diff --git a/ash/frame/non_client_frame_view_ash.cc b/ash/frame/non_client_frame_view_ash.cc
index ac747ee..ae0476e 100644
--- a/ash/frame/non_client_frame_view_ash.cc
+++ b/ash/frame/non_client_frame_view_ash.cc
@@ -400,7 +400,7 @@
   frame_window->SetProperty(kFrameInactiveColorKey, dialog_title_bar_color);
 }
 
-BEGIN_METADATA(NonClientFrameViewAsh, views::NonClientFrameView)
+BEGIN_METADATA(NonClientFrameViewAsh)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/frame/non_client_frame_view_ash.h b/ash/frame/non_client_frame_view_ash.h
index 5e060da..ae8dc5c9 100644
--- a/ash/frame/non_client_frame_view_ash.h
+++ b/ash/frame/non_client_frame_view_ash.h
@@ -41,9 +41,9 @@
     : public chromeos::NonClientFrameViewBase,
       public FrameContextMenuController::Delegate,
       public aura::WindowObserver {
- public:
-  METADATA_HEADER(NonClientFrameViewAsh);
+  METADATA_HEADER(NonClientFrameViewAsh, chromeos::NonClientFrameViewBase)
 
+ public:
   // |control_immersive| controls whether ImmersiveFullscreenController is
   // created for the NonClientFrameViewAsh; if true and a WindowStateDelegate
   // has not been set on the WindowState associated with |frame|, then an
diff --git a/ash/game_dashboard/game_dashboard_button.cc b/ash/game_dashboard/game_dashboard_button.cc
index 3badc74..bdafd9f 100644
--- a/ash/game_dashboard/game_dashboard_button.cc
+++ b/ash/game_dashboard/game_dashboard_button.cc
@@ -190,7 +190,7 @@
   title_view_->SetText(title_text);
 }
 
-BEGIN_METADATA(GameDashboardButton, views::Button)
+BEGIN_METADATA(GameDashboardButton)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/game_dashboard/game_dashboard_button.h b/ash/game_dashboard/game_dashboard_button.h
index dd31db9..e169c89 100644
--- a/ash/game_dashboard/game_dashboard_button.h
+++ b/ash/game_dashboard/game_dashboard_button.h
@@ -43,9 +43,9 @@
 // true will replace the second "icon_view" with an the up arrow. Called with
 // false, it will show the down arrow.
 class GameDashboardButton : public views::Button {
- public:
-  METADATA_HEADER(GameDashboardButton);
+  METADATA_HEADER(GameDashboardButton, views::Button)
 
+ public:
   explicit GameDashboardButton(PressedCallback callback);
   GameDashboardButton(const GameDashboardButton&) = delete;
   GameDashboardButton& operator=(const GameDashboardButton&) = delete;
diff --git a/ash/game_dashboard/game_dashboard_main_menu_view.cc b/ash/game_dashboard/game_dashboard_main_menu_view.cc
index 58a9156..18ce58fc 100644
--- a/ash/game_dashboard/game_dashboard_main_menu_view.cc
+++ b/ash/game_dashboard/game_dashboard_main_menu_view.cc
@@ -827,7 +827,7 @@
       cros_tokens::kCrosSysSystemBaseElevatedOpaque));
 }
 
-BEGIN_METADATA(GameDashboardMainMenuView, views::BubbleDialogDelegateView)
+BEGIN_METADATA(GameDashboardMainMenuView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/game_dashboard/game_dashboard_main_menu_view.h b/ash/game_dashboard/game_dashboard_main_menu_view.h
index 8febb22e..0e5fed6 100644
--- a/ash/game_dashboard/game_dashboard_main_menu_view.h
+++ b/ash/game_dashboard/game_dashboard_main_menu_view.h
@@ -24,9 +24,9 @@
 // dashboard button.
 class ASH_EXPORT GameDashboardMainMenuView
     : public views::BubbleDialogDelegateView {
- public:
-  METADATA_HEADER(GameDashboardMainMenuView);
+  METADATA_HEADER(GameDashboardMainMenuView, views::BubbleDialogDelegateView)
 
+ public:
   explicit GameDashboardMainMenuView(GameDashboardContext* context);
 
   GameDashboardMainMenuView(const GameDashboardMainMenuView&) = delete;
diff --git a/ash/game_dashboard/game_dashboard_toolbar_view.h b/ash/game_dashboard/game_dashboard_toolbar_view.h
index 98088e6..9f1eecdc 100644
--- a/ash/game_dashboard/game_dashboard_toolbar_view.h
+++ b/ash/game_dashboard/game_dashboard_toolbar_view.h
@@ -21,9 +21,9 @@
 // window. It contains various quick action tiles for users to access without
 // having to open the entire main menu view.
 class ASH_EXPORT GameDashboardToolbarView : public views::BoxLayoutView {
- public:
-  METADATA_HEADER(GameDashboardToolbarView);
+  METADATA_HEADER(GameDashboardToolbarView, views::BoxLayoutView)
 
+ public:
   explicit GameDashboardToolbarView(GameDashboardContext* context);
   GameDashboardToolbarView(const GameDashboardToolbarView&) = delete;
   GameDashboardToolbarView& operator=(const GameDashboardToolbarView) = delete;
diff --git a/ash/game_dashboard/game_dashboard_welcome_dialog.cc b/ash/game_dashboard/game_dashboard_welcome_dialog.cc
index 5ded43d..e84ab18 100644
--- a/ash/game_dashboard/game_dashboard_welcome_dialog.cc
+++ b/ash/game_dashboard/game_dashboard_welcome_dialog.cc
@@ -173,7 +173,7 @@
       gfx::Insets::VH(kShortcutTextBorder, kShortcutTextBorder)));
 }
 
-BEGIN_METADATA(GameDashboardWelcomeDialog, views::FlexLayoutView)
+BEGIN_METADATA(GameDashboardWelcomeDialog)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/game_dashboard/game_dashboard_welcome_dialog.h b/ash/game_dashboard/game_dashboard_welcome_dialog.h
index 6fe7a6b..00aac4e 100644
--- a/ash/game_dashboard/game_dashboard_welcome_dialog.h
+++ b/ash/game_dashboard/game_dashboard_welcome_dialog.h
@@ -15,9 +15,9 @@
 // when first opening any game. It can be disabled via the Game Dashboard
 // Settings.
 class ASH_EXPORT GameDashboardWelcomeDialog : public views::FlexLayoutView {
- public:
-  METADATA_HEADER(GameDashboardWelcomeDialog);
+  METADATA_HEADER(GameDashboardWelcomeDialog, views::FlexLayoutView)
 
+ public:
   GameDashboardWelcomeDialog();
   GameDashboardWelcomeDialog(const GameDashboardWelcomeDialog&) = delete;
   GameDashboardWelcomeDialog& operator=(const GameDashboardWelcomeDialog) =
diff --git a/ash/glanceables/classroom/glanceables_classroom_item_view.cc b/ash/glanceables/classroom/glanceables_classroom_item_view.cc
index e6cb008..bc4354f 100644
--- a/ash/glanceables/classroom/glanceables_classroom_item_view.cc
+++ b/ash/glanceables/classroom/glanceables_classroom_item_view.cc
@@ -235,9 +235,9 @@
     : views::Button(std::move(pressed_callback)) {
   CHECK(assignment);
 
-  auto* const layout = SetLayoutManager(std::make_unique<views::FlexLayout>());
-  layout->SetCrossAxisAlignment(views::LayoutAlignment::kStart);
-  layout->SetInteriorMargin(kInteriorMargin);
+  SetLayoutManager(std::make_unique<views::FlexLayout>())
+      ->SetCrossAxisAlignment(views::LayoutAlignment::kStart)
+      .SetInteriorMargin(kInteriorMargin);
 
   const gfx::RoundedCornersF corner_radii =
       GetRoundedCorners(item_index, last_item_index);
@@ -282,7 +282,7 @@
 
   // Prevent the layout manager from setting the focus ring to a default hidden
   // visibility.
-  layout->SetChildViewIgnoredByLayout(focus_ring, true);
+  focus_ring->SetProperty(views::kViewIgnoredByLayoutKey, true);
 }
 
 GlanceablesClassroomItemView::~GlanceablesClassroomItemView() = default;
@@ -299,7 +299,7 @@
   views::FocusRing::Get(this)->DeprecatedLayoutImmediately();
 }
 
-BEGIN_METADATA(GlanceablesClassroomItemView, views::View)
+BEGIN_METADATA(GlanceablesClassroomItemView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/glanceables/classroom/glanceables_classroom_item_view.h b/ash/glanceables/classroom/glanceables_classroom_item_view.h
index 44746b7..3b85138 100644
--- a/ash/glanceables/classroom/glanceables_classroom_item_view.h
+++ b/ash/glanceables/classroom/glanceables_classroom_item_view.h
@@ -17,9 +17,9 @@
 // A view which shows information about a single assignment in the classroom
 // glanceable.
 class ASH_EXPORT GlanceablesClassroomItemView : public views::Button {
- public:
-  METADATA_HEADER(GlanceablesClassroomItemView);
+  METADATA_HEADER(GlanceablesClassroomItemView, views::Button)
 
+ public:
   GlanceablesClassroomItemView(const GlanceablesClassroomAssignment* assignment,
                                base::RepeatingClosure pressed_callback,
                                size_t item_index,
diff --git a/ash/glanceables/common/glanceables_error_message_view.cc b/ash/glanceables/common/glanceables_error_message_view.cc
index 05cb936f..ff652b8 100644
--- a/ash/glanceables/common/glanceables_error_message_view.cc
+++ b/ash/glanceables/common/glanceables_error_message_view.cc
@@ -60,7 +60,7 @@
   ~DismissErrorLabelButton() override = default;
 };
 
-BEGIN_METADATA(DismissErrorLabelButton, views::LabelButton)
+BEGIN_METADATA(DismissErrorLabelButton)
 END_METADATA
 
 GlanceablesErrorMessageView::GlanceablesErrorMessageView(
@@ -110,7 +110,7 @@
   SetBoundsRect(preferred_bounds);
 }
 
-BEGIN_METADATA(GlanceablesErrorMessageView, views::View)
+BEGIN_METADATA(GlanceablesErrorMessageView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/glanceables/common/glanceables_error_message_view.h b/ash/glanceables/common/glanceables_error_message_view.h
index d14b7f8..86bda93 100644
--- a/ash/glanceables/common/glanceables_error_message_view.h
+++ b/ash/glanceables/common/glanceables_error_message_view.h
@@ -23,9 +23,9 @@
 // Displays error message at the bottom of the glanceables bubble. Used in
 // tasks bubbles.
 class ASH_EXPORT GlanceablesErrorMessageView : public views::FlexLayoutView {
- public:
-  METADATA_HEADER(GlanceablesErrorMessageView);
+  METADATA_HEADER(GlanceablesErrorMessageView, views::FlexLayoutView)
 
+ public:
   GlanceablesErrorMessageView(views::Button::PressedCallback callback,
                               const std::u16string& error_message);
   GlanceablesErrorMessageView(const GlanceablesErrorMessageView&) = delete;
diff --git a/ash/glanceables/tasks/glanceables_task_view.cc b/ash/glanceables/tasks/glanceables_task_view.cc
index 603c1d7..5da2ce5 100644
--- a/ash/glanceables/tasks/glanceables_task_view.cc
+++ b/ash/glanceables/tasks/glanceables_task_view.cc
@@ -326,7 +326,7 @@
   UpdateTaskTitleViewForState(TaskTitleViewState::kEdit);
 }
 
-BEGIN_METADATA(GlanceablesTaskView, views::View)
+BEGIN_METADATA(GlanceablesTaskView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/glanceables/tasks/glanceables_task_view.h b/ash/glanceables/tasks/glanceables_task_view.h
index 339fc8c..c20e8d7f 100644
--- a/ash/glanceables/tasks/glanceables_task_view.h
+++ b/ash/glanceables/tasks/glanceables_task_view.h
@@ -41,9 +41,9 @@
 // | +-----------------+ +---------------------------------------+ |
 // +---------------------------------------------------------------+
 class ASH_EXPORT GlanceablesTaskView : public views::FlexLayoutView {
- public:
-  METADATA_HEADER(GlanceablesTaskView);
+  METADATA_HEADER(GlanceablesTaskView, views::FlexLayoutView)
 
+ public:
   using MarkAsCompletedCallback =
       base::RepeatingCallback<void(const std::string& task_id, bool completed)>;
 
diff --git a/ash/glanceables/tasks/glanceables_task_view_v2.cc b/ash/glanceables/tasks/glanceables_task_view_v2.cc
index fe24dcc..fb0b8dc 100644
--- a/ash/glanceables/tasks/glanceables_task_view_v2.cc
+++ b/ash/glanceables/tasks/glanceables_task_view_v2.cc
@@ -200,7 +200,7 @@
   }
 };
 
-BEGIN_METADATA(EditInBrowserButton, views::LabelButton)
+BEGIN_METADATA(EditInBrowserButton)
 END_METADATA
 
 }  // namespace
@@ -525,7 +525,7 @@
   }
 }
 
-BEGIN_METADATA(GlanceablesTaskViewV2, views::View)
+BEGIN_METADATA(GlanceablesTaskViewV2)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/glanceables/tasks/glanceables_task_view_v2.h b/ash/glanceables/tasks/glanceables_task_view_v2.h
index 6366d1d..9819f0bb 100644
--- a/ash/glanceables/tasks/glanceables_task_view_v2.h
+++ b/ash/glanceables/tasks/glanceables_task_view_v2.h
@@ -44,9 +44,9 @@
 // | +-----------------+ +---------------------------------------+ |
 // +---------------------------------------------------------------+
 class ASH_EXPORT GlanceablesTaskViewV2 : public views::FlexLayoutView {
- public:
-  METADATA_HEADER(GlanceablesTaskViewV2);
+  METADATA_HEADER(GlanceablesTaskViewV2, views::FlexLayoutView)
 
+ public:
   using MarkAsCompletedCallback =
       base::RepeatingCallback<void(const std::string& task_id, bool completed)>;
   using SaveCallback = base::RepeatingCallback<void(
diff --git a/ash/glanceables/tasks/glanceables_tasks_view.cc b/ash/glanceables/tasks/glanceables_tasks_view.cc
index 3d07af1a..7ef470a 100644
--- a/ash/glanceables/tasks/glanceables_tasks_view.cc
+++ b/ash/glanceables/tasks/glanceables_tasks_view.cc
@@ -487,7 +487,7 @@
                                 kMaximumTasks);
 }
 
-BEGIN_METADATA(GlanceablesTasksView, views::View)
+BEGIN_METADATA(GlanceablesTasksView)
 END_METADATA
 
 }  // namespace ash
diff --git a/ash/glanceables/tasks/glanceables_tasks_view.h b/ash/glanceables/tasks/glanceables_tasks_view.h
index cd4498ce..79a2833 100644
--- a/ash/glanceables/tasks/glanceables_tasks_view.h
+++ b/ash/glanceables/tasks/glanceables_tasks_view.h
@@ -52,9 +52,9 @@
 // Glanceables view responsible for interacting with Google Tasks.
 class ASH_EXPORT GlanceablesTasksView : public GlanceablesTasksViewBase,
                                         public views::ViewObserver {
- public:
-  METADATA_HEADER(GlanceablesTasksView);
+  METADATA_HEADER(GlanceablesTasksView, GlanceablesTasksViewBase)
 
+ public:
   explicit GlanceablesTasksView(const ui::ListModel<api::TaskList>* task_lists);
   GlanceablesTasksView(const GlanceablesTasksView&) = delete;
   GlanceablesTasksView& operator=(const GlanceablesTasksView&) = delete;
diff --git a/ash/hud_display/fps_graph_page_view.cc b/ash/hud_display/fps_graph_page_view.cc
index d257b254..85a5612 100644
--- a/ash/hud_display/fps_graph_page_view.cc
+++ b/ash/hud_display/fps_graph_page_view.cc
@@ -32,7 +32,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 // FPSGraphPageView, public:
 
-BEGIN_METADATA(FPSGraphPageView, GraphPageViewBase)
+BEGIN_METADATA(FPSGraphPageView)
 END_METADATA
 
 FPSGraphPageView::FPSGraphPageView(const base::TimeDelta refresh_interval)
diff --git a/ash/hud_display/fps_graph_page_view.h b/ash/hud_display/fps_graph_page_view.h
index 89af3849..c9e7176 100644
--- a/ash/hud_display/fps_graph_page_view.h
+++ b/ash/hud_display/fps_graph_page_view.h
@@ -31,9 +31,9 @@
                          public ui::CompositorObserver,
                          public views::WidgetObserver,
                          public aura::WindowObserver {
- public:
-  METADATA_HEADER(FPSGraphPageView);
+  METADATA_HEADER(FPSGraphPageView, GraphPageViewBase)
 
+ public:
   explicit FPSGraphPageView(const base::TimeDelta refresh_interval);
   FPSGraphPageView(const FPSGraphPageView&) = delete;
   FPSGraphPageView& operator=(const FPSGraphPageView&) = delete;
diff --git a/ash/hud_display/graph_page_view_base.cc b/ash/hud_display/graph_page_view_base.cc
index 620241c..d774b09 100644
--- a/ash/hud_display/graph_page_view_base.cc
+++ b/ash/hud_display/graph_page_view_base.cc
@@ -33,9 +33,9 @@
 
 // ImageButton with underline
 class MinMaxButton : public views::ImageButton {
- public:
-  METADATA_HEADER(MinMaxButton);
+  METADATA_HEADER(MinMaxButton, views::ImageButton)
 
+ public:
   explicit MinMaxButton(views::Button::PressedCallback callback)
       : views::ImageButton(std::move(callback)) {
     SetBorder(views::CreateEmptyBorder(kMinMaxButtonBorder));
@@ -70,7 +70,7 @@
   }
 };
 
-BEGIN_METADATA(MinMaxButton, views::ImageButton)
+BEGIN_METADATA(MinMaxButton)
 END_METADATA
 
 void SetMinimizeIconToButton(views::ImageButton* button) {
@@ -89,7 +89,7 @@
 
 }  // namespace
 
-BEGIN_METADATA(GraphPageViewBase, views::View)
+BEGIN_METADATA(GraphPageViewBase)
 END_METADATA
 
 GraphPageViewBase::GraphPageViewBase() {
diff --git a/ash/hud_display/graph_page_view_base.h b/ash/hud_display/graph_page_view_base.h
index e93cb10..e1df19f 100644
--- a/ash/hud_display/graph_page_view_base.h
+++ b/ash/hud_display/graph_page_view_base.h
@@ -23,9 +23,9 @@
 
 // Interface for a single graph page.
 class GraphPageViewBase : public views::View {
- public:
-  METADATA_HEADER(GraphPageViewBase);
+  METADATA_HEADER(GraphPageViewBase, views::View)
 
+ public:
   GraphPageViewBase();
   GraphPageViewBase(const GraphPageViewBase&) = delete;
   GraphPageViewBase& operator=(const GraphPageViewBase&) = delete;
diff --git a/ash/hud_display/graphs_container_view.cc b/ash/hud_display/graphs_container_view.cc
index 056b71a..33e05ae 100644
--- a/ash/hud_display/graphs_container_view.cc
+++ b/ash/hud_display/graphs_container_view.cc
@@ -34,7 +34,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 // GraphsContainerView, public:
 
-BEGIN_METADATA(GraphsContainerView, views::View)
+BEGIN_METADATA(GraphsContainerView)
 END_METADATA
 
 GraphsContainerView::GraphsContainerView()
diff --git a/ash/hud_display/graphs_container_view.h b/ash/hud_display/graphs_container_view.h
index a45f740..8fe597c 100644
--- a/ash/hud_display/graphs_container_view.h
+++ b/ash/hud_display/graphs_container_view.h
@@ -21,9 +21,9 @@
 
 // GraphsContainerView class draws a bunch of graphs.
 class GraphsContainerView : public views::View {
- public:
-  METADATA_HEADER(GraphsContainerView);
+  METADATA_HEADER(GraphsContainerView, views::View)
 
+ public:
   GraphsContainerView();
   GraphsContainerView(const GraphsContainerView&) = delete;
   GraphsContainerView& operator=(const GraphsContainerView&) = delete;
diff --git a/ash/hud_display/hud_display.cc b/ash/hud_display/hud_display.cc
index e86487b..85b7208e 100644
--- a/ash/hud_display/hud_display.cc
+++ b/ash/hud_display/hud_display.cc
@@ -73,9 +73,9 @@
 // ClientView that return HTNOWHERE by default. A child view can receive event
 // by setting kHitTestComponentKey property to HTCLIENT.
 class HTClientView : public views::ClientView {
- public:
-  METADATA_HEADER(HTClientView);
+  METADATA_HEADER(HTClientView, views::ClientView)
 
+ public:
   HTClientView(HUDDisplayView* hud_display,
                views::Widget* widget,
                views::View* contents_view)
@@ -96,7 +96,7 @@
   raw_ptr<HUDDisplayView> hud_display_;
 };
 
-BEGIN_METADATA(HTClientView, views::ClientView)
+BEGIN_METADATA(HTClientView)
 END_METADATA
 
 std::unique_ptr<views::ClientView> MakeClientView(views::Widget* widget) {
@@ -118,7 +118,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 // HUDDisplayView, public:
 
-BEGIN_METADATA(HUDDisplayView, views::View)
+BEGIN_METADATA(HUDDisplayView)
 END_METADATA
 
 // static
diff --git a/ash/hud_display/hud_display.h b/ash/hud_display/hud_display.h
index f06001ec..cc63b9cf9 100644
--- a/ash/hud_display/hud_display.h
+++ b/ash/hud_display/hud_display.h
@@ -20,9 +20,9 @@
 
 // HUDDisplayView class can be used to display a system monitoring overview.
 class HUDDisplayView : public views::View {
- public:
-  METADATA_HEADER(HUDDisplayView);
+  METADATA_HEADER(HUDDisplayView, views::View)
 
+ public:
   HUDDisplayView();
   HUDDisplayView(const HUDDisplayView&) = delete;
   HUDDisplayView& operator=(const HUDDisplayView&) = delete;
diff --git a/ash/hud_display/hud_header_view.cc b/ash/hud_display/hud_header_view.cc
index 2e915fd..73136dab 100644
--- a/ash/hud_display/hud_header_view.cc
+++ b/ash/hud_display/hud_header_view.cc
@@ -159,7 +159,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 // HUDHeaderView
 
-BEGIN_METADATA(HUDHeaderView, views::View)
+BEGIN_METADATA(HUDHeaderView)
 END_METADATA
 
 HUDHeaderView::HUDHeaderView(HUDDisplayView* hud) {
diff --git a/ash/hud_display/hud_header_view.h b/ash/hud_display/hud_header_view.h
index 38a6847..daae087 100644
--- a/ash/hud_display/hud_header_view.h
+++ b/ash/hud_display/hud_header_view.h
@@ -16,9 +16,9 @@
 
 // HUDHeaderView renders header (with buttons and tabs) of the HUD.
 class HUDHeaderView : public views::View {
- public:
-  METADATA_HEADER(HUDHeaderView);
+  METADATA_HEADER(HUDHeaderView, views::View)
 
+ public:
   explicit HUDHeaderView(HUDDisplayView* hud);
 
   HUDHeaderView(const HUDHeaderView&) = delete;
diff --git a/ash/hud_display/hud_settings_view.cc b/ash/hud_display/hud_settings_view.cc
index 1d26420..ce1aced 100644
--- a/ash/hud_display/hud_settings_view.cc
+++ b/ash/hud_display/hud_settings_view.cc
@@ -138,9 +138,9 @@
 
 // views::Checkbox that ignores theme colors.
 class SettingsCheckbox : public views::Checkbox {
- public:
-  METADATA_HEADER(SettingsCheckbox);
+  METADATA_HEADER(SettingsCheckbox, views::Checkbox)
 
+ public:
   SettingsCheckbox(const std::u16string& label, const std::u16string& tooltip)
       : views::Checkbox(label, views::Button::PressedCallback()) {
     SetTooltipText(tooltip);
@@ -156,13 +156,13 @@
   }
 };
 
-BEGIN_METADATA(SettingsCheckbox, views::Checkbox);
+BEGIN_METADATA(SettingsCheckbox)
 END_METADATA
 
 class AnimationSpeedSlider : public views::Slider {
- public:
-  METADATA_HEADER(AnimationSpeedSlider);
+  METADATA_HEADER(AnimationSpeedSlider, views::Slider)
 
+ public:
   AnimationSpeedSlider(const base::flat_set<float>& values,
                        views::SliderListener* listener = nullptr)
       : views::Slider(listener) {
@@ -181,7 +181,7 @@
   void OnPaint(gfx::Canvas* canvas) override;
 };
 
-BEGIN_METADATA(AnimationSpeedSlider, views::Slider)
+BEGIN_METADATA(AnimationSpeedSlider)
 END_METADATA
 
 void AnimationSpeedSlider::OnPaint(gfx::Canvas* canvas) {
@@ -211,9 +211,9 @@
 
 // Checkbox group for setting UI animation speed.
 class AnimationSpeedControl : public views::SliderListener, public views::View {
- public:
-  METADATA_HEADER(AnimationSpeedControl);
+  METADATA_HEADER(AnimationSpeedControl, views::View)
 
+ public:
   AnimationSpeedControl();
   AnimationSpeedControl(const AnimationSpeedControl&) = delete;
   AnimationSpeedControl& operator=(const AnimationSpeedControl&) = delete;
@@ -239,7 +239,7 @@
   SliderValuesMap slider_values_;
 };
 
-BEGIN_METADATA(AnimationSpeedControl, views::View)
+BEGIN_METADATA(AnimationSpeedControl)
 END_METADATA
 
 AnimationSpeedControl::AnimationSpeedControl() {
@@ -433,7 +433,7 @@
 
 }  // anonymous namespace
 
-BEGIN_METADATA(HUDSettingsView, views::View)
+BEGIN_METADATA(HUDSettingsView)
 END_METADATA
 
 HUDSettingsView::HUDSettingsView(HUDDisplayView* hud_display) {
diff --git a/ash/hud_display/hud_settings_view.h b/ash/hud_display/hud_settings_view.h
index 60d72c3..26402e0 100644
--- a/ash/hud_display/hud_settings_view.h
+++ b/ash/hud_display/hud_settings_view.h
@@ -37,9 +37,9 @@
 class HUDSettingsView : public AshTracingManager::Observer,
                         public views::View,
                         public ui::EventObserver {
- public:
-  METADATA_HEADER(HUDSettingsView);
+  METADATA_HEADER(HUDSettingsView, views::View)
 
+ public:
   explicit HUDSettingsView(HUDDisplayView* hud_display);
   ~HUDSettingsView() override;
 
diff --git a/ash/hud_display/legend.cc b/ash/hud_display/legend.cc
index 0c0644cda..da1a06d 100644
--- a/ash/hud_display/legend.cc
+++ b/ash/hud_display/legend.cc
@@ -28,9 +28,9 @@
 namespace {
 
 class LegendEntry : public views::View {
- public:
-  METADATA_HEADER(LegendEntry);
+  METADATA_HEADER(LegendEntry, views::View)
 
+ public:
   explicit LegendEntry(const Legend::Entry& data);
 
   LegendEntry(const LegendEntry&) = delete;
@@ -55,7 +55,7 @@
   raw_ptr<views::Label> value_ = nullptr;
 };
 
-BEGIN_METADATA(LegendEntry, views::View)
+BEGIN_METADATA(LegendEntry)
 END_METADATA
 
 LegendEntry::LegendEntry(const Legend::Entry& data)
@@ -156,7 +156,7 @@
 
 Legend::Entry::~Entry() = default;
 
-BEGIN_METADATA(Legend, views::View)
+BEGIN_METADATA(Legend)
 END_METADATA
 
 Legend::Legend(const std::vector<Legend::Entry>& contents) {
diff --git a/ash/hud_display/legend.h b/ash/hud_display/legend.h
index b64003e31..357f81c 100644
--- a/ash/hud_display/legend.h
+++ b/ash/hud_display/legend.h
@@ -19,11 +19,10 @@
 
 // Draws legend view.
 class Legend : public views::View {
+  METADATA_HEADER(Legend, views::View)
+
  public:
   using Formatter = base::RepeatingCallback<std::u16string(float)>;
-
-  METADATA_HEADER(Legend);
-
   struct Entry {
     Entry(const Graph& graph,
           std::u16string label,
diff --git a/ash/hud_display/memory_graph_page_view.cc b/ash/hud_display/memory_graph_page_view.cc
index 84c5b7a..b4b3af6 100644
--- a/ash/hud_display/memory_graph_page_view.cc
+++ b/ash/hud_display/memory_graph_page_view.cc
@@ -23,7 +23,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 // MemoryGraphPageView, public:
 
-BEGIN_METADATA(MemoryGraphPageView, GraphPageViewBase)
+BEGIN_METADATA(MemoryGraphPageView)
 END_METADATA
 
 MemoryGraphPageView::MemoryGraphPageView(const base::TimeDelta refresh_interval)
diff --git a/ash/hud_display/memory_graph_page_view.h b/ash/hud_display/memory_graph_page_view.h
index 17ba7a7..9bfa9c57 100644
--- a/ash/hud_display/memory_graph_page_view.h
+++ b/ash/hud_display/memory_graph_page_view.h
@@ -16,9 +16,9 @@
 
 // MemoryGraphPageView class draws memory graphs.
 class MemoryGraphPageView : public GraphPageViewBase {
- public:
-  METADATA_HEADER(MemoryGraphPageView);
+  METADATA_HEADER(MemoryGraphPageView, GraphPageViewBase)
 
+ public:
   explicit MemoryGraphPageView(const base::TimeDelta refresh_interval);
   MemoryGraphPageView(const MemoryGraphPageView&) = delete;
   MemoryGraphPageView& operator=(const MemoryGraphPageView&) = delete;
diff --git a/ash/hud_display/reference_lines.cc b/ash/hud_display/reference_lines.cc
index 1ca5fbd..fc0d2ff 100644
--- a/ash/hud_display/reference_lines.cc
+++ b/ash/hud_display/reference_lines.cc
@@ -35,7 +35,7 @@
 
 }  // anonymous namespace
 
-BEGIN_METADATA(ReferenceLines, views::View)
+BEGIN_METADATA(ReferenceLines)
 END_METADATA
 
 ReferenceLines::ReferenceLines(float left,
diff --git a/ash/hud_display/reference_lines.h b/ash/hud_display/reference_lines.h
index a3199ac..b25ad9f 100644
--- a/ash/hud_display/reference_lines.h
+++ b/ash/hud_display/reference_lines.h
@@ -25,9 +25,9 @@
 
 // Draws opaque reference lines on top of the graphs.
 class ReferenceLines : public views::View {
- public:
-  METADATA_HEADER(ReferenceLines);
+  METADATA_HEADER(ReferenceLines, views::View)
 
+ public:
   // |left|, |top|, |right|, |bottom| are labels to be attached to the axes.
   // |x_unit|, |y_unit| - dimentional labels, like "s", "Gb", ...
   // To draw horizontal ticks, graph data is assumed to have
diff --git a/ash/hud_display/tab_strip.cc b/ash/hud_display/tab_strip.cc
index 5facacb..aea4db9 100644
--- a/ash/hud_display/tab_strip.cc
+++ b/ash/hud_display/tab_strip.cc
@@ -74,7 +74,7 @@
 
 }  // namespace
 
-BEGIN_METADATA(HUDTabButton, views::LabelButton)
+BEGIN_METADATA(HUDTabButton)
 END_METADATA
 
 HUDTabButton::HUDTabButton(Style style,
@@ -163,7 +163,7 @@
   canvas->DrawPath(path, flags);
 }
 
-BEGIN_METADATA(HUDTabStrip, views::View)
+BEGIN_METADATA(HUDTabStrip)
 END_METADATA
 
 HUDTabStrip::HUDTabStrip(HUDDisplayView* hud) : hud_(hud) {
diff --git a/ash/hud_display/tab_strip.h b/ash/hud_display/tab_strip.h
index b672e4d7..fc7f2e3e 100644
--- a/ash/hud_display/tab_strip.h
+++ b/ash/hud_display/tab_strip.h
@@ -28,6 +28,8 @@
 class HUDTabStrip;
 
 class HUDTabButton : public views::LabelButton {
+  METADATA_HEADER(HUDTabButton, views::LabelButton)
+
  public:
   // Defines tab paint style.
   enum class Style {
@@ -36,8 +38,6 @@
     RIGHT,   // Tab to the right of the active tab.
   };
 
-  METADATA_HEADER(HUDTabButton);
-
   HUDTabButton(Style style,
                const HUDDisplayMode display_mode,
                const std::u16string& text);
@@ -62,9 +62,9 @@
 };
 
 class HUDTabStrip : public views::View {
- public:
-  METADATA_HEADER(HUDTabStrip);
+  METADATA_HEADER(HUDTabStrip, views::View)
 
+ public:
   explicit HUDTabStrip(HUDDisplayView* hud);
 
   HUDTabStrip(const HUDTabStrip&) = delete;
diff --git a/ash/login/ui/login_expanded_public_account_view.cc b/ash/login/ui/login_expanded_public_account_view.cc
index cea9821e..8b999a3 100644
--- a/ash/login/ui/login_expanded_public_account_view.cc
+++ b/ash/login/ui/login_expanded_public_account_view.cc
@@ -464,11 +464,6 @@
 
   ~RightPaneView() override = default;
 
-  gfx::Size CalculatePreferredSize(
-      const views::SizeBounds& available_size) const override {
-    return GetLayoutManager()->GetPreferredSize(this, available_size);
-  }
-
   void UpdateForUser(const LoginUserInfo& user) {
     DCHECK_EQ(user.basic_user_info.type,
               user_manager::UserType::kPublicAccount);
diff --git a/ash/login/ui/login_user_view.cc b/ash/login/ui/login_user_view.cc
index 5dc86fc..b71f255 100644
--- a/ash/login/ui/login_user_view.cc
+++ b/ash/login/ui/login_user_view.cc
@@ -693,8 +693,7 @@
 }
 
 void LoginUserView::SetLargeLayout() {
-  auto* layout = SetLayoutManager(std::make_unique<views::TableLayout>());
-  layout
+  SetLayoutManager(std::make_unique<views::TableLayout>())
       ->AddColumn(views::LayoutAlignment::kEnd, views::LayoutAlignment::kCenter,
                   1.0f, views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
       .AddPaddingColumn(views::TableLayout::kFixedSize,
@@ -714,7 +713,7 @@
       .AddRows(1, views::TableLayout::kFixedSize);
 
   AddChildView(tap_button_.get());
-  layout->SetChildViewIgnoredByLayout(tap_button_, true);
+  tap_button_->SetProperty(views::kViewIgnoredByLayoutKey, true);
 
   AddChildView(user_image_.get());
   user_image_->SetProperty(views::kTableColAndRowSpanKey, gfx::Size(5, 1));
diff --git a/ash/shelf/login_shelf_view_pixeltest.cc b/ash/shelf/login_shelf_view_pixeltest.cc
index ad1470e7..2514e2b0 100644
--- a/ash/shelf/login_shelf_view_pixeltest.cc
+++ b/ash/shelf/login_shelf_view_pixeltest.cc
@@ -77,28 +77,28 @@
   PressAndReleaseKey(ui::VKEY_TAB);
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       "focus_on_login_user_expand_button",
-      /*revision_number=*/8, primary_big_user_view_.get(),
+      /*revision_number=*/6, primary_big_user_view_.get(),
       primary_shelf_window));
 
   // Trigger the tab key. Check that the login shelf shutdown button is focused.
   PressAndReleaseKey(ui::VKEY_TAB);
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       "focus_on_shutdown_button",
-      /*revision_number=*/8, primary_big_user_view_.get(),
+      /*revision_number=*/6, primary_big_user_view_.get(),
       primary_shelf_window));
 
   // Trigger the tab key. Check that the browser as guest button is focused.
   PressAndReleaseKey(ui::VKEY_TAB);
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       "focus_on_browser_as_guest_button",
-      /*revision_number=*/8, primary_big_user_view_.get(),
+      /*revision_number=*/6, primary_big_user_view_.get(),
       primary_shelf_window));
 
   // Trigger the tab key. Check that the add person button is focused.
   PressAndReleaseKey(ui::VKEY_TAB);
   EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
       "focus_on_add_person_button",
-      /*revision_number=*/8, primary_big_user_view_.get(),
+      /*revision_number=*/6, primary_big_user_view_.get(),
       primary_shelf_window));
 }
 
diff --git a/ash/shelf/shelf_app_button_pixeltest.cc b/ash/shelf/shelf_app_button_pixeltest.cc
new file mode 100644
index 0000000..d4766b1
--- /dev/null
+++ b/ash/shelf/shelf_app_button_pixeltest.cc
@@ -0,0 +1,78 @@
+// 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/public/cpp/shelf_model.h"
+#include "ash/public/cpp/shelf_types.h"
+#include "ash/shelf/shelf.h"
+#include "ash/shelf/shelf_app_button.h"
+#include "ash/shelf/shelf_controller.h"
+#include "ash/shelf/shelf_view.h"
+#include "ash/shelf/shelf_view_test_api.h"
+#include "ash/shelf/shelf_widget.h"
+#include "ash/shelf/test/shelf_test_base.h"
+#include "ash/test/ash_test_base.h"
+#include "ash/test/pixel/ash_pixel_differ.h"
+#include "ash/test/pixel/ash_pixel_test_init_params.h"
+#include "base/test/scoped_feature_list.h"
+#include "chromeos/constants/chromeos_features.h"
+
+namespace ash {
+
+class ShelfAppButtonPixelTest
+    : public ShelfTestBase,
+      public testing::WithParamInterface</*use_rtl=*/bool> {
+ public:
+  ShelfAppButtonPixelTest() : use_rtl_(GetParam()) {}
+
+  std::optional<pixel_test::InitParams> CreatePixelTestInitParams()
+      const override {
+    pixel_test::InitParams init_params;
+    init_params.under_rtl = use_rtl();
+    return init_params;
+  }
+
+  // ShelfTestBase:
+  void SetUp() override {
+    scoped_feature_list_.InitWithFeatures(
+        /*enabled_features=*/{chromeos::features::kCrosWebAppShortcutUiUpdate,
+                              ash::features::kSeparateWebAppShortcutBadgeIcon},
+        /*disabled_features=*/{});
+    ShelfTestBase::SetUp();
+  }
+
+  void TearDown() override { ShelfTestBase::TearDown(); }
+
+  ShelfAppButton* GetItemViewAt(ShelfID id) {
+    return GetPrimaryShelf()
+        ->shelf_widget()
+        ->shelf_view_for_testing()
+        ->GetShelfAppButton(id);
+  }
+
+  bool use_rtl() const { return use_rtl_; }
+
+ private:
+  const bool use_rtl_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         ShelfAppButtonPixelTest,
+                         /*use_rtl=*/testing::Bool());
+
+TEST_P(ShelfAppButtonPixelTest, WebAppShortcutIconEffectsExists) {
+  const ShelfItem item = AddWebAppShortcut();
+  ShelfAppButton* item_view = GetItemViewAt(item.id);
+  item_view->GetWidget()->LayoutRootViewIfNecessary();
+
+  ShelfViewTestAPI shelf_view_test_api(
+      GetPrimaryShelf()->GetShelfViewForTesting());
+  shelf_view_test_api.RunMessageLoopUntilAnimationsDone();
+
+  EXPECT_TRUE(GetPixelDiffer()->CompareUiComponentsOnPrimaryScreen(
+      "web_app_shortcut_icon_effects_exists", /*revision_number=*/1,
+      item_view));
+}
+
+}  // namespace ash
diff --git a/ash/shelf/shelf_layout_manager.cc b/ash/shelf/shelf_layout_manager.cc
index 1add6c8d..33147e7b 100644
--- a/ash/shelf/shelf_layout_manager.cc
+++ b/ash/shelf/shelf_layout_manager.cc
@@ -2063,6 +2063,10 @@
         adjusted_shelf_position);
     shelf_->hotseat_widget()->UpdateTargetBoundsForGesture(
         adjusted_shelf_position);
+    if (features::IsDeskButtonEnabled()) {
+      shelf_->desk_button_widget()->UpdateTargetBoundsForGesture(
+          adjusted_shelf_position);
+    }
     shelf_->navigation_widget()->UpdateTargetBoundsForGesture(
         adjusted_shelf_position);
     shelf_->status_area_widget()->UpdateTargetBoundsForGesture(
diff --git a/ash/shelf/test/shelf_test_base.cc b/ash/shelf/test/shelf_test_base.cc
index ebad379..0c1d602 100644
--- a/ash/shelf/test/shelf_test_base.cc
+++ b/ash/shelf/test/shelf_test_base.cc
@@ -10,6 +10,7 @@
 #include "ash/shelf/shelf_view_test_api.h"
 #include "ash/shelf/shelf_widget.h"
 #include "ash/test/ash_test_util.h"
+#include "ui/gfx/image/image_unittest_util.h"
 
 namespace ash {
 
@@ -59,6 +60,16 @@
   }
 }
 
+ShelfItem ShelfTestBase::AddWebAppShortcut() {
+  const int icon_dimension = 28;
+  const int badge_icon_dimension = 18;
+  ShelfItem item = ShelfTestUtil::AddWebAppShortcut(
+      "shortcut_id", true,
+      gfx::test::CreateImageSkia(icon_dimension, SK_ColorRED),
+      gfx::test::CreateImageSkia(badge_icon_dimension, SK_ColorCYAN));
+  return item;
+}
+
 ShelfID ShelfTestBase::AddAppShortcutWithIconColor(ShelfItemType item_type,
                                                    SkColor color) {
   ShelfItem item = ShelfTestUtil::AddAppShortcutWithIcon(
diff --git a/ash/shelf/test/shelf_test_base.h b/ash/shelf/test/shelf_test_base.h
index 62fe38d..fd1c443 100644
--- a/ash/shelf/test/shelf_test_base.h
+++ b/ash/shelf/test/shelf_test_base.h
@@ -5,6 +5,7 @@
 #ifndef ASH_SHELF_TEST_SHELF_TEST_BASE_H_
 #define ASH_SHELF_TEST_SHELF_TEST_BASE_H_
 
+#include "ash/public/cpp/shelf_item.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/test/ash_test_base.h"
 #include "ash/test/ash_test_color_generator.h"
@@ -45,6 +46,9 @@
   // different colors.
   void AddAppShortcutsUntilOverflow(bool use_alternative_color = false);
 
+  // Adds a shelf item that is a webapp shortcut.
+  ShelfItem AddWebAppShortcut();
+
   // Adds a shelf item of the specified type and color.
   ShelfID AddAppShortcutWithIconColor(ShelfItemType item_type, SkColor color);
 
diff --git a/ash/style/combobox.cc b/ash/style/combobox.cc
index 4fa746ed0..603e0a4 100644
--- a/ash/style/combobox.cc
+++ b/ash/style/combobox.cc
@@ -349,8 +349,8 @@
   OnComboboxModelChanged(model_);
 
   // Set up layout.
-  auto* const layout = SetLayoutManager(std::make_unique<views::FlexLayout>());
-  layout->SetInteriorMargin(kComboboxBorderInsets);
+  SetLayoutManager(std::make_unique<views::FlexLayout>())
+      ->SetInteriorMargin(kComboboxBorderInsets);
   // Allow `title_` to shrink and elide, so that `drop_down_arrow_` on the
   // right always remains visible.
   title_->SetProperty(
@@ -373,8 +373,8 @@
   StyleUtil::InstallRoundedCornerHighlightPathGenerator(
       this, kComboboxRoundedCorners);
   StyleUtil::SetUpInkDropForButton(this);
-  layout->SetChildViewIgnoredByLayout(views::FocusRing::Get(this),
-                                      /*ignored=*/true);
+  views::FocusRing::Get(this)->SetProperty(views::kViewIgnoredByLayoutKey,
+                                           /*ignored=*/true);
 
   event_handler_ = std::make_unique<ComboboxEventHandler>(this);
 
diff --git a/ash/style/drop_down_checkbox.cc b/ash/style/drop_down_checkbox.cc
index c14e2728..ea776dd 100644
--- a/ash/style/drop_down_checkbox.cc
+++ b/ash/style/drop_down_checkbox.cc
@@ -275,8 +275,8 @@
   model_->AddObserver(selection_model_.get());
 
   // Set up layout.
-  auto* const layout = SetLayoutManager(std::make_unique<views::FlexLayout>());
-  layout->SetInteriorMargin(kDropDownCheckboxBorderInsets);
+  SetLayoutManager(std::make_unique<views::FlexLayout>())
+      ->SetInteriorMargin(kDropDownCheckboxBorderInsets);
   // Allow `title_` to shrink and elide, so that `drop_down_arrow_` on the
   // right always remains visible.
   title_->SetProperty(
@@ -299,8 +299,8 @@
   views::InstallRoundRectHighlightPathGenerator(
       this, gfx::Insets(), kDropDownCheckboxRoundedCorners);
   StyleUtil::SetUpInkDropForButton(this);
-  layout->SetChildViewIgnoredByLayout(views::FocusRing::Get(this),
-                                      /*ignored=*/true);
+  views::FocusRing::Get(this)->SetProperty(views::kViewIgnoredByLayoutKey,
+                                           /*ignored=*/true);
 
   event_handler_ = std::make_unique<EventHandler>(this);
 
diff --git a/ash/style/tab_slider.cc b/ash/style/tab_slider.cc
index 52f41ea..bcd451d 100644
--- a/ash/style/tab_slider.cc
+++ b/ash/style/tab_slider.cc
@@ -115,10 +115,7 @@
 
   Init();
 
-  // Explicitly mark this view as ignored because
-  // `views::kViewIgnoredByLayoutKey` is not supported by `views::TableLayout`.
-  static_cast<views::TableLayout*>(GetLayoutManager())
-      ->SetChildViewIgnoredByLayout(selector_view_, /*ignored=*/true);
+  selector_view_->SetProperty(views::kViewIgnoredByLayoutKey, true);
 
   enabled_changed_subscription_ = AddEnabledChangedCallback(base::BindRepeating(
       &TabSlider::OnEnabledStateChanged, base::Unretained(this)));
diff --git a/ash/system/focus_mode/focus_mode_detailed_view.cc b/ash/system/focus_mode/focus_mode_detailed_view.cc
index 7abf19ca2..0a1dfc6c 100644
--- a/ash/system/focus_mode/focus_mode_detailed_view.cc
+++ b/ash/system/focus_mode/focus_mode_detailed_view.cc
@@ -508,6 +508,7 @@
       toggle_view_->tri_view()->box_layout();
   toggle_view_tri_view_layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
+  toggle_view_tri_view_layout->InvalidateLayout();
 }
 
 void FocusModeDetailedView::UpdateToggleButtonAccessibility(
@@ -792,6 +793,7 @@
       toggle_row->tri_view()->box_layout();
   toggle_view_tri_view_layout->set_cross_axis_alignment(
       views::BoxLayout::CrossAxisAlignment::kCenter);
+  toggle_view_tri_view_layout->InvalidateLayout();
 }
 
 void FocusModeDetailedView::OnDoNotDisturbToggleClicked() {
diff --git a/ash/system/holding_space/holding_space_drag_util.cc b/ash/system/holding_space/holding_space_drag_util.cc
index c8370c43..46e217c 100644
--- a/ash/system/holding_space/holding_space_drag_util.cc
+++ b/ash/system/holding_space/holding_space_drag_util.cc
@@ -35,6 +35,7 @@
 #include "ui/views/layout/fill_layout.h"
 #include "ui/views/layout/layout_manager_base.h"
 #include "ui/views/view.h"
+#include "ui/views/view_class_properties.h"
 #include "ui/views/widget/widget.h"
 
 namespace ash {
@@ -453,9 +454,9 @@
   }
 
   void InitLayout(const std::vector<const HoldingSpaceItem*>& items) {
-    auto* layout = SetLayoutManager(std::make_unique<views::FillLayout>());
+    SetLayoutManager(std::make_unique<views::FillLayout>());
     AddDragImageItemViews(items);
-    AddDragImageOverflowBadge(layout, items.size());
+    AddDragImageOverflowBadge(items.size());
   }
 
   void AddDragImageItemViews(
@@ -490,16 +491,17 @@
     first_drag_image_item_view_ = container->children()[0].get();
   }
 
-  void AddDragImageOverflowBadge(views::FillLayout* layout, size_t count) {
+  void AddDragImageOverflowBadge(size_t count) {
     if (count <= kDragImageViewMaxItemsToPaint)
       return;
 
     drag_image_overflow_badge_ = AddChildView(
         std::make_unique<DragImageOverflowBadge>(count, color_provider_));
 
-    // This view's `layout` manager ignores `drag_image_overflow_badge_` as it
-    // is manually positioned relative to the `first_drag_image_item_view_`.
-    layout->SetChildViewIgnoredByLayout(drag_image_overflow_badge_, true);
+    // `drag_image_overflow_badge_` is manually positioned relative to the
+    // `first_drag_image_item_view_`.
+    drag_image_overflow_badge_->SetProperty(views::kViewIgnoredByLayoutKey,
+                                            true);
   }
 
   const raw_ptr<const ui::ColorProvider> color_provider_;
diff --git a/ash/system/holding_space/holding_space_tray_child_bubble.cc b/ash/system/holding_space/holding_space_tray_child_bubble.cc
index e83a402..a544923 100644
--- a/ash/system/holding_space/holding_space_tray_child_bubble.cc
+++ b/ash/system/holding_space/holding_space_tray_child_bubble.cc
@@ -94,13 +94,13 @@
 
  private:
   // views::BoxLayout:
-  void LayoutImpl() override {
-    if (host_view()->height() >= host_view()->GetPreferredSize().height()) {
-      views::BoxLayout::LayoutImpl();
+  void Layout(views::View* host) override {
+    if (host->height() >= host->GetPreferredSize().height()) {
+      views::BoxLayout::Layout(host);
       return;
     }
 
-    gfx::Rect contents_bounds(host_view()->GetContentsBounds());
+    gfx::Rect contents_bounds(host->GetContentsBounds());
     contents_bounds.Inset(inside_border_insets());
 
     const int width = contents_bounds.width();
@@ -112,7 +112,7 @@
     // `available_height` is tracked to later determine if there will be
     // vertical overflow of `contents_bounds`.
     int available_height = contents_bounds.height();
-    for (views::View* child : host_view()->children()) {
+    for (views::View* child : host->children()) {
       if (!child->GetVisible())
         continue;
 
@@ -130,7 +130,7 @@
 
     // Perform child layouts, ceding height where possible to fit within
     // `contents_bounds`. Note: this does not guarantee that `contents_bounds`
-    // will not be exceeded. Overflow will be clipped by the `host_view()` view.
+    // will not be exceeded. Overflow will be clipped by the `host` view.
     for (auto& [child, height] : children_with_heights) {
       // A `child` view is willing to cede height if it does not specify a
       // minimum size. This is the case for the `PinnedFilesSection` which
diff --git a/ash/system/notification_center/views/notifier_settings_view.cc b/ash/system/notification_center/views/notifier_settings_view.cc
index 5b90a4e0..cb5ddb6 100644
--- a/ash/system/notification_center/views/notifier_settings_view.cc
+++ b/ash/system/notification_center/views/notifier_settings_view.cc
@@ -65,6 +65,7 @@
 #include "ui/views/layout/fill_layout.h"
 #include "ui/views/layout/table_layout.h"
 #include "ui/views/painter.h"
+#include "ui/views/view_class_properties.h"
 #include "ui/views/widget/widget.h"
 
 namespace ash {
@@ -491,7 +492,7 @@
   // constructor, and replace TableLayout with BoxLayout.  Toggle the visibility
   // of the policy icon dynamically as needed.
 
-  auto* layout = SetLayoutManager(std::make_unique<views::TableLayout>());
+  auto* const layout = SetLayoutManager(std::make_unique<views::TableLayout>());
   layout
       // Add a column for the checkbox.
       ->AddPaddingColumn(views::TableLayout::kFixedSize,
@@ -523,7 +524,8 @@
       .AddRows(1, views::TableLayout::kFixedSize);
 
   // FocusRing is a child of Button. Ignore it.
-  layout->SetChildViewIgnoredByLayout(views::FocusRing::Get(this), true);
+  views::FocusRing::Get(this)->SetProperty(views::kViewIgnoredByLayoutKey,
+                                           true);
 
   if (!GetEnabled()) {
     auto policy_enforced_icon = std::make_unique<views::ImageView>();
diff --git a/ash/system/tray/tray_bubble_view.cc b/ash/system/tray/tray_bubble_view.cc
index fb81b727..10ca5f7e 100644
--- a/ash/system/tray/tray_bubble_view.cc
+++ b/ash/system/tray/tray_bubble_view.cc
@@ -114,26 +114,24 @@
   ~BottomAlignedBoxLayout() override {}
 
  private:
-  void LayoutImpl() override {
-    if (host_view()->height() >= host_view()->GetPreferredSize().height() ||
+  void Layout(View* host) override {
+    if (host->height() >= host->GetPreferredSize().height() ||
         !bubble_view_->is_gesture_dragging()) {
-      views::BoxLayout::LayoutImpl();
+      BoxLayout::Layout(host);
       return;
     }
 
     int consumed_height = 0;
-    for (auto i = host_view()->children().rbegin();
-         i != host_view()->children().rend() &&
-         consumed_height < host_view()->height();
+    for (auto i = host->children().rbegin();
+         i != host->children().rend() && consumed_height < host->height();
          ++i) {
       View* child = *i;
       if (!child->GetVisible()) {
         continue;
       }
       gfx::Size size = child->GetPreferredSize();
-      child->SetBounds(0,
-                       host_view()->height() - consumed_height - size.height(),
-                       host_view()->width(), size.height());
+      child->SetBounds(0, host->height() - consumed_height - size.height(),
+                       host->width(), size.height());
       consumed_height += size.height();
     }
   }
diff --git a/ash/system/unified/classroom_bubble_base_view.cc b/ash/system/unified/classroom_bubble_base_view.cc
index 897edbc..b1b3d17 100644
--- a/ash/system/unified/classroom_bubble_base_view.cc
+++ b/ash/system/unified/classroom_bubble_base_view.cc
@@ -54,8 +54,7 @@
 ClassroomBubbleBaseView::ClassroomBubbleBaseView(
     std::unique_ptr<ui::ComboboxModel> combobox_model)
     : GlanceableTrayChildBubble(/*for_glanceables_container=*/true) {
-  layout_manager_ = SetLayoutManager(std::make_unique<views::FlexLayout>());
-  layout_manager_
+  SetLayoutManager(std::make_unique<views::FlexLayout>())
       ->SetInteriorMargin(gfx::Insets::TLBR(kInteriorGlanceableBubbleMargin,
                                             kInteriorGlanceableBubbleMargin, 0,
                                             kInteriorGlanceableBubbleMargin))
@@ -211,10 +210,7 @@
     } else {
       ShowErrorMessage(
           l10n_util::GetStringUTF16(IDS_GLANCEABLES_CLASSROOM_FETCH_ERROR));
-
-      // Explicitly signal to the layout manager to ignore the view.
-      layout_manager_->SetChildViewIgnoredByLayout(error_message(),
-                                                   /*ignored=*/true);
+      error_message()->SetProperty(views::kViewIgnoredByLayoutKey, true);
     }
   }
 }
diff --git a/ash/system/unified/classroom_bubble_base_view.h b/ash/system/unified/classroom_bubble_base_view.h
index dfea5ffa..497ebbc 100644
--- a/ash/system/unified/classroom_bubble_base_view.h
+++ b/ash/system/unified/classroom_bubble_base_view.h
@@ -14,7 +14,6 @@
 class GURL;
 
 namespace views {
-class FlexLayout;
 class FlexLayoutView;
 class Label;
 }
@@ -89,7 +88,6 @@
   raw_ptr<GlanceablesListFooterView> list_footer_view_ = nullptr;
   raw_ptr<GlanceablesProgressBarView> progress_bar_ = nullptr;
   raw_ptr<views::Label> empty_list_label_ = nullptr;
-  raw_ptr<views::FlexLayout> layout_manager_ = nullptr;
 
   base::ScopedObservation<views::View, views::ViewObserver>
       combobox_view_observation_{this};
diff --git a/ash/system/video_conference/bubble/toggle_effects_view.cc b/ash/system/video_conference/bubble/toggle_effects_view.cc
index 3e620d05..6b72a1e 100644
--- a/ash/system/video_conference/bubble/toggle_effects_view.cc
+++ b/ash/system/video_conference/bubble/toggle_effects_view.cc
@@ -211,7 +211,7 @@
   focus_ring->SetHaloInset(-3);
   // Since the focus ring doesn't set a LayoutManager it won't get drawn
   // unless excluded by the tile's LayoutManager.
-  layout_->SetChildViewIgnoredByLayout(focus_ring, true);
+  focus_ring->SetProperty(views::kViewIgnoredByLayoutKey, true);
 
   auto icon = std::make_unique<views::ImageView>();
   icon->SetID(video_conference::BubbleViewID::kToggleEffectIcon);
diff --git a/ash/wallpaper/wallpaper_constants.h b/ash/wallpaper/wallpaper_constants.h
index 6a490a6..bc41e4a 100644
--- a/ash/wallpaper/wallpaper_constants.h
+++ b/ash/wallpaper/wallpaper_constants.h
@@ -7,8 +7,6 @@
 
 #include "ash/public/cpp/style/color_provider.h"
 
-#include <string_view>
-
 namespace ash::wallpaper_constants {
 
 // Blur sigma used for normal wallpaper.
@@ -27,18 +25,6 @@
 // The ID of the default time of day wallpaper.
 constexpr uint64_t kDefaultTimeOfDayWallpaperUnitId = 18;
 
-// Keys for fields stored in SeaPen metadata json.
-// TODO(b/321275173): move those consts into another file as they are shared
-// with VC background.
-inline constexpr std::string_view kSeaPenCreationTimeKey = "creation_time";
-inline constexpr std::string_view kSeaPenFreeformQueryKey = "freeform_query";
-inline constexpr std::string_view kSeaPenTemplateIdKey = "template_id";
-inline constexpr std::string_view kSeaPenTemplateOptionsKey = "options";
-inline constexpr std::string_view kSeaPenUserVisibleQueryTextKey =
-    "user_visible_query_text";
-inline constexpr std::string_view kSeaPenUserVisibleQueryTemplateKey =
-    "user_visible_query_template";
-
 }  // namespace ash::wallpaper_constants
 
 #endif  // ASH_WALLPAPER_WALLPAPER_CONSTANTS_H_
diff --git a/ash/wallpaper/wallpaper_file_manager.cc b/ash/wallpaper/wallpaper_file_manager.cc
index 466aa75..51d37a8 100644
--- a/ash/wallpaper/wallpaper_file_manager.cc
+++ b/ash/wallpaper/wallpaper_file_manager.cc
@@ -10,6 +10,7 @@
 #include "ash/public/cpp/image_util.h"
 #include "ash/public/cpp/wallpaper/wallpaper_controller.h"
 #include "ash/wallpaper/wallpaper_constants.h"
+#include "ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.h"
 #include "ash/wallpaper/wallpaper_utils/wallpaper_file_utils.h"
 #include "base/files/file_enumerator.h"
 #include "base/files/file_util.h"
@@ -198,8 +199,8 @@
     return std::nullopt;
   }
   base::Value::Dict& dict = parsed->GetDict();
-  if (!dict.contains(wallpaper_constants::kSeaPenFreeformQueryKey) &&
-      !dict.contains(wallpaper_constants::kSeaPenTemplateIdKey)) {
+  if (!dict.contains(kSeaPenFreeformQueryKey) &&
+      !dict.contains(kSeaPenTemplateIdKey)) {
     LOG(WARNING) << "Parsed JSON does not contain required keys";
     return std::nullopt;
   }
diff --git a/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.cc b/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.cc
new file mode 100644
index 0000000..3dac0ccd
--- /dev/null
+++ b/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.cc
@@ -0,0 +1,57 @@
+// 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/wallpaper/wallpaper_utils/sea_pen_metadata_utils.h"
+
+#include "base/json/json_writer.h"
+#include "base/json/values_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/time.h"
+
+namespace ash {
+
+base::Value::Dict SeaPenQueryToDict(
+    const personalization_app::mojom::SeaPenQueryPtr& query) {
+  base::Value::Dict query_dict = base::Value::Dict();
+  query_dict.Set(kSeaPenCreationTimeKey, base::TimeToValue(base::Time::Now()));
+
+  switch (query->which()) {
+    case personalization_app::mojom::SeaPenQuery::Tag::kTextQuery:
+      query_dict.Set(kSeaPenFreeformQueryKey, query->get_text_query());
+      break;
+    case personalization_app::mojom::SeaPenQuery::Tag::kTemplateQuery:
+      query_dict.Set(kSeaPenTemplateIdKey,
+                     base::NumberToString(static_cast<int32_t>(
+                         query->get_template_query()->id)));
+      base::Value::Dict options_dict = base::Value::Dict();
+      for (const auto& [chip, option] : query->get_template_query()->options) {
+        options_dict.Set(base::NumberToString(static_cast<int32_t>(chip)),
+                         base::NumberToString(static_cast<int32_t>(option)));
+      }
+      query_dict.Set(kSeaPenTemplateOptionsKey, std::move(options_dict));
+      query_dict.Set(kSeaPenUserVisibleQueryTextKey,
+                     query->get_template_query()->user_visible_query->text);
+      query_dict.Set(
+          kSeaPenUserVisibleQueryTemplateKey,
+          query->get_template_query()->user_visible_query->template_title);
+      break;
+  }
+
+  return query_dict;
+}
+
+std::string QueryDictToXmpString(const base::Value::Dict& query_dict) {
+  static constexpr char kXmpData[] = R"(
+            <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0">
+               <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+                  <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
+                     <dc:description>%s</dc:description>
+                  </rdf:Description>
+               </rdf:RDF>
+            </x:xmpmeta>)";
+  return base::StringPrintf(kXmpData,
+                            base::WriteJson(query_dict).value_or("").c_str());
+}
+
+}  // namespace ash
diff --git a/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.h b/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.h
new file mode 100644
index 0000000..600bd330
--- /dev/null
+++ b/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.h
@@ -0,0 +1,52 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_WALLPAPER_WALLPAPER_UTILS_SEA_PEN_METADATA_UTILS_H_
+#define ASH_WALLPAPER_WALLPAPER_UTILS_SEA_PEN_METADATA_UTILS_H_
+
+#include <string>
+#include <string_view>
+
+#include "ash/ash_export.h"
+#include "ash/webui/common/mojom/sea_pen.mojom.h"
+#include "base/values.h"
+
+namespace ash {
+
+// Keys for fields stored in SeaPen metadata json.
+inline constexpr std::string_view kSeaPenCreationTimeKey = "creation_time";
+inline constexpr std::string_view kSeaPenFreeformQueryKey = "freeform_query";
+inline constexpr std::string_view kSeaPenTemplateIdKey = "template_id";
+inline constexpr std::string_view kSeaPenTemplateOptionsKey = "options";
+inline constexpr std::string_view kSeaPenUserVisibleQueryTextKey =
+    "user_visible_query_text";
+inline constexpr std::string_view kSeaPenUserVisibleQueryTemplateKey =
+    "user_visible_query_template";
+
+/**
+ * Serializes a sea pen query information `query` into
+ * base::Value::Dict format based on the query type. Such as
+ * {creation_time:<number>, freeform_query:<string>} or {creation_time:<number>,
+ * user_visible_query_text:<string>, user_visible_query_template:<string>,
+ * template_id:<number>, options:{<chip_number>:<option_number>, ...}}. For
+ * example:
+ * {"creation_time":"13349580387513653","freeform_query":"test freeform query"}
+ * {"creation_time":"13349580387513653", "user_visible_query_text": "test
+ * template query", "user_visible_query_template": "test template",
+ * "template_id":"2","options":{"4":"34","5":"40"}}
+ *
+ * @param query  pointer to the sea pen query
+ * @return query information in base::Value::Dict format
+ */
+ASH_EXPORT base::Value::Dict SeaPenQueryToDict(
+    const personalization_app::mojom::SeaPenQueryPtr& query);
+
+// Constructs the xmp metadata string from the base::Value::Dict query
+// information.
+ASH_EXPORT std::string QueryDictToXmpString(
+    const base::Value::Dict& query_dict);
+
+}  // namespace ash
+
+#endif  // ASH_WALLPAPER_WALLPAPER_UTILS_SEA_PEN_METADATA_UTILS_H_
diff --git a/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils_unittest.cc b/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils_unittest.cc
new file mode 100644
index 0000000..7ad4a5b6
--- /dev/null
+++ b/ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils_unittest.cc
@@ -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.
+
+#include "ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.h"
+
+#include "ash/test/ash_test_base.h"
+#include "base/json/values_util.h"
+#include "base/time/time_override.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace ash {
+namespace {
+
+base::subtle::ScopedTimeClockOverrides CreateScopedTimeNowOverride() {
+  return base::subtle::ScopedTimeClockOverrides(
+      []() -> base::Time {
+        base::Time fake_now;
+        bool success =
+            base::Time::FromString("2023-04-05T01:23:45Z", &fake_now);
+        DCHECK(success);
+        return fake_now;
+      },
+      nullptr, nullptr);
+}
+
+TEST(SeaPenMetadataUtilsTest, SeaPenTextQueryToDict) {
+  auto time_override = CreateScopedTimeNowOverride();
+  std::string user_search_query = "user search query text";
+  ash::personalization_app::mojom::SeaPenQueryPtr search_query =
+      ash::personalization_app::mojom::SeaPenQuery::NewTextQuery(
+          user_search_query);
+  base::Value::Dict result = SeaPenQueryToDict(search_query);
+  base::Value::Dict expected =
+      base::Value::Dict()
+          .Set("creation_time", base::TimeToValue(base::Time::Now()))
+          .Set("freeform_query", user_search_query);
+  EXPECT_EQ(expected, result);
+}
+
+TEST(SeaPenMetadataUtilsTest, SeaPenTemplateQueryToDict) {
+  auto time_override = CreateScopedTimeNowOverride();
+  base::flat_map<ash::personalization_app::mojom::SeaPenTemplateChip,
+                 ash::personalization_app::mojom::SeaPenTemplateOption>
+      options(
+          {{ash::personalization_app::mojom::SeaPenTemplateChip::kFlowerColor,
+            ash::personalization_app::mojom::SeaPenTemplateOption::
+                kFlowerColorBlue},
+           {ash::personalization_app::mojom::SeaPenTemplateChip::kFlowerType,
+            ash::personalization_app::mojom::SeaPenTemplateOption::
+                kFlowerTypeRose}});
+  ash::personalization_app::mojom::SeaPenQueryPtr search_query =
+      ash::personalization_app::mojom::SeaPenQuery::NewTemplateQuery(
+          ash::personalization_app::mojom::SeaPenTemplateQuery::New(
+              ash::personalization_app::mojom::SeaPenTemplateId::kFlower,
+              options,
+              ash::personalization_app::mojom::SeaPenUserVisibleQuery::New(
+                  "test template query", "test template title")));
+  base::Value::Dict result = SeaPenQueryToDict(search_query);
+  base::Value::Dict expected =
+      base::Value::Dict()
+          .Set("creation_time", base::TimeToValue(base::Time::Now()))
+          .Set("template_id",
+               base::NumberToString(static_cast<int32_t>(
+                   ash::personalization_app::mojom::SeaPenTemplateId::kFlower)))
+          .Set("options",
+               base::Value::Dict()
+                   .Set(base::NumberToString(static_cast<int32_t>(
+                            ash::personalization_app::mojom::
+                                SeaPenTemplateChip::kFlowerColor)),
+                        base::NumberToString(static_cast<int32_t>(
+                            ash::personalization_app::mojom::
+                                SeaPenTemplateOption::kFlowerColorBlue)))
+                   .Set(base::NumberToString(static_cast<int32_t>(
+                            ash::personalization_app::mojom::
+                                SeaPenTemplateChip::kFlowerType)),
+                        base::NumberToString(static_cast<int32_t>(
+                            ash::personalization_app::mojom::
+                                SeaPenTemplateOption::kFlowerTypeRose))))
+          .Set("user_visible_query_text", "test template query")
+          .Set("user_visible_query_template", "test template title");
+  EXPECT_EQ(expected, result);
+}
+
+}  // namespace
+}  // namespace ash
diff --git a/ash/webui/common/BUILD.gn b/ash/webui/common/BUILD.gn
index 9c253ed..70c26ebb 100644
--- a/ash/webui/common/BUILD.gn
+++ b/ash/webui/common/BUILD.gn
@@ -40,6 +40,8 @@
     "sea_pen_provider.h",
     "sea_pen_resources.cc",
     "sea_pen_resources.h",
+    "sea_pen_resources_generated.cc",
+    "sea_pen_resources_generated.h",
   ]
 
   deps = [
diff --git a/ash/webui/common/resources/sea_pen/constants_generated.ts b/ash/webui/common/resources/sea_pen/constants_generated.ts
index f4eadc0a..2ff02fd 100644
--- a/ash/webui/common/resources/sea_pen/constants_generated.ts
+++ b/ash/webui/common/resources/sea_pen/constants_generated.ts
@@ -5,6 +5,8 @@
 // AUTOGENERATED - DO NOT EDIT!
 // This file is generated by sea_pen_client_generator.py
 
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
+
 import {SeaPenTemplate} from './constants.js';
 import {SeaPenTemplateChip, SeaPenTemplateId, SeaPenTemplateOption} from './sea_pen_generated.mojom-webui.js';
 
@@ -13,9 +15,10 @@
   return [
     {
       id: SeaPenTemplateId.kFlower,
-      title: 'Airbrushed',
-      text: `A radiant <${SeaPenTemplateChip.kFlowerColor}> <${
-          SeaPenTemplateChip.kFlowerType}> in bloom`,
+      title: loadTimeData.getString('seaPenTemplateTitleFlower'),
+      text: loadTimeData.getStringF(
+          'seaPenTemplateFlower', `<${SeaPenTemplateChip.kFlowerColor}>`,
+          `<${SeaPenTemplateChip.kFlowerType}>`),
       preview: [{
         url:
             'chrome://resources/ash/common/sea_pen/sea_pen_images/sea_pen_flower.jpg',
@@ -26,35 +29,42 @@
           [
             {
               value: SeaPenTemplateOption.kFlowerColorPink,
-              translation: 'pink',
+              translation:
+                  loadTimeData.getString('seaPenOptionFlowerColorPink'),
             },
             {
               value: SeaPenTemplateOption.kFlowerColorPurple,
-              translation: 'light purple',
+              translation:
+                  loadTimeData.getString('seaPenOptionFlowerColorPurple'),
             },
             {
               value: SeaPenTemplateOption.kFlowerColorBlue,
-              translation: 'light blue',
+              translation:
+                  loadTimeData.getString('seaPenOptionFlowerColorBlue'),
             },
             {
               value: SeaPenTemplateOption.kFlowerColorWhite,
-              translation: 'white',
+              translation:
+                  loadTimeData.getString('seaPenOptionFlowerColorWhite'),
             },
             {
               value: SeaPenTemplateOption.kFlowerColorCoral,
-              translation: 'coral',
+              translation:
+                  loadTimeData.getString('seaPenOptionFlowerColorCoral'),
             },
             {
               value: SeaPenTemplateOption.kFlowerColorYellow,
-              translation: 'pastel yellow',
+              translation:
+                  loadTimeData.getString('seaPenOptionFlowerColorYellow'),
             },
             {
               value: SeaPenTemplateOption.kFlowerColorGreen,
-              translation: 'light green',
+              translation:
+                  loadTimeData.getString('seaPenOptionFlowerColorGreen'),
             },
             {
               value: SeaPenTemplateOption.kFlowerColorRed,
-              translation: 'dark red',
+              translation: loadTimeData.getString('seaPenOptionFlowerColorRed'),
             },
           ],
         ],
@@ -63,43 +73,52 @@
           [
             {
               value: SeaPenTemplateOption.kFlowerTypeRose,
-              translation: 'garden rose',
+              translation: loadTimeData.getString('seaPenOptionFlowerTypeRose'),
             },
             {
               value: SeaPenTemplateOption.kFlowerTypeCallaLily,
-              translation: 'calla lily',
+              translation:
+                  loadTimeData.getString('seaPenOptionFlowerTypeCallaLily'),
             },
             {
               value: SeaPenTemplateOption.kFlowerTypeWindflower,
-              translation: 'windflower',
+              translation:
+                  loadTimeData.getString('seaPenOptionFlowerTypeWindflower'),
             },
             {
               value: SeaPenTemplateOption.kFlowerTypeTulip,
-              translation: 'tulip',
+              translation:
+                  loadTimeData.getString('seaPenOptionFlowerTypeTulip'),
             },
             {
               value: SeaPenTemplateOption.kFlowerTypeLilyOfTheValley,
-              translation: 'lily of the valley',
+              translation: loadTimeData.getString(
+                  'seaPenOptionFlowerTypeLilyOfTheValley'),
             },
             {
               value: SeaPenTemplateOption.kFlowerTypeBirdOfParadise,
-              translation: 'bird of paradise',
+              translation: loadTimeData.getString(
+                  'seaPenOptionFlowerTypeBirdOfParadise'),
             },
             {
               value: SeaPenTemplateOption.kFlowerTypeOrchid,
-              translation: 'orchid',
+              translation:
+                  loadTimeData.getString('seaPenOptionFlowerTypeOrchid'),
             },
             {
               value: SeaPenTemplateOption.kFlowerTypeRanunculus,
-              translation: 'ranunculus',
+              translation:
+                  loadTimeData.getString('seaPenOptionFlowerTypeRanunculus'),
             },
             {
               value: SeaPenTemplateOption.kFlowerTypeDaisy,
-              translation: 'daisy',
+              translation:
+                  loadTimeData.getString('seaPenOptionFlowerTypeDaisy'),
             },
             {
               value: SeaPenTemplateOption.kFlowerTypeHydrangea,
-              translation: 'hydrangea',
+              translation:
+                  loadTimeData.getString('seaPenOptionFlowerTypeHydrangea'),
             },
           ],
         ],
diff --git a/ash/webui/common/sea_pen_resources_generated.cc b/ash/webui/common/sea_pen_resources_generated.cc
new file mode 100644
index 0000000..e585d2c7
--- /dev/null
+++ b/ash/webui/common/sea_pen_resources_generated.cc
@@ -0,0 +1,52 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// AUTOGENERATED - DO NOT EDIT!
+// This file is generated by sea_pen_client_generator.py
+
+#include "ash/webui/common/sea_pen_resources_generated.h"
+
+#include "ash/constants/ash_features.h"
+#include "chromeos/strings/grit/chromeos_strings.h"
+#include "ui/base/webui/web_ui_util.h"
+
+namespace ash::common {
+
+void AddSeaPenWallpaperTemplateStrings(content::WebUIDataSource* source) {
+  static constexpr webui::LocalizedString kLocalizedStrings[] = {
+      {"seaPenTemplateTitleFlower", IDS_SEA_PEN_TEMPLATE_TITLE_FLOWER},
+      {"seaPenTemplateFlower", IDS_SEA_PEN_TEMPLATE_FLOWER},
+      {"seaPenOptionFlowerTypeRose", IDS_SEA_PEN_OPTION_FLOWER_TYPE_ROSE},
+      {"seaPenOptionFlowerTypeCallaLily",
+       IDS_SEA_PEN_OPTION_FLOWER_TYPE_CALLA_LILY},
+      {"seaPenOptionFlowerTypeWindflower",
+       IDS_SEA_PEN_OPTION_FLOWER_TYPE_WINDFLOWER},
+      {"seaPenOptionFlowerTypeTulip", IDS_SEA_PEN_OPTION_FLOWER_TYPE_TULIP},
+      {"seaPenOptionFlowerTypeLilyOfTheValley",
+       IDS_SEA_PEN_OPTION_FLOWER_TYPE_LILY_OF_THE_VALLEY},
+      {"seaPenOptionFlowerTypeBirdOfParadise",
+       IDS_SEA_PEN_OPTION_FLOWER_TYPE_BIRD_OF_PARADISE},
+      {"seaPenOptionFlowerTypeOrchid", IDS_SEA_PEN_OPTION_FLOWER_TYPE_ORCHID},
+      {"seaPenOptionFlowerTypeRanunculus",
+       IDS_SEA_PEN_OPTION_FLOWER_TYPE_RANUNCULUS},
+      {"seaPenOptionFlowerTypeDaisy", IDS_SEA_PEN_OPTION_FLOWER_TYPE_DAISY},
+      {"seaPenOptionFlowerTypeHydrangea",
+       IDS_SEA_PEN_OPTION_FLOWER_TYPE_HYDRANGEA},
+      {"seaPenOptionFlowerColorPink", IDS_SEA_PEN_OPTION_FLOWER_COLOR_PINK},
+      {"seaPenOptionFlowerColorPurple", IDS_SEA_PEN_OPTION_FLOWER_COLOR_PURPLE},
+      {"seaPenOptionFlowerColorBlue", IDS_SEA_PEN_OPTION_FLOWER_COLOR_BLUE},
+      {"seaPenOptionFlowerColorWhite", IDS_SEA_PEN_OPTION_FLOWER_COLOR_WHITE},
+      {"seaPenOptionFlowerColorCoral", IDS_SEA_PEN_OPTION_FLOWER_COLOR_CORAL},
+      {"seaPenOptionFlowerColorYellow", IDS_SEA_PEN_OPTION_FLOWER_COLOR_YELLOW},
+      {"seaPenOptionFlowerColorGreen", IDS_SEA_PEN_OPTION_FLOWER_COLOR_GREEN},
+      {"seaPenOptionFlowerColorRed", IDS_SEA_PEN_OPTION_FLOWER_COLOR_RED},
+  };
+  source->AddLocalizedStrings(kLocalizedStrings);
+}
+
+void AddSeaPenVcBackgroundTemplateStrings(content::WebUIDataSource* source) {
+  NOTIMPLEMENTED();
+}
+
+}  // namespace ash::common
diff --git a/ash/webui/common/sea_pen_resources_generated.h b/ash/webui/common/sea_pen_resources_generated.h
new file mode 100644
index 0000000..45b87ad
--- /dev/null
+++ b/ash/webui/common/sea_pen_resources_generated.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 ASH_WEBUI_COMMON_SEA_PEN_RESOURCES_GENERATED_H_
+#define ASH_WEBUI_COMMON_SEA_PEN_RESOURCES_GENERATED_H_
+
+#include "content/public/browser/web_ui_data_source.h"
+
+namespace content {
+class WebUIDataSource;
+}  // namespace content
+
+namespace ash::common {
+
+void AddSeaPenWallpaperTemplateStrings(content::WebUIDataSource* source);
+
+void AddSeaPenVcBackgroundTemplateStrings(content::WebUIDataSource* source);
+
+}  // namespace ash::common
+
+#endif  // ASH_WEBUI_COMMON_SEA_PEN_RESOURCES_GENERATED_H_
diff --git a/ash/webui/firmware_update_ui/firmware_update_app_ui.cc b/ash/webui/firmware_update_ui/firmware_update_app_ui.cc
index 5d52b50..a3634be6 100644
--- a/ash/webui/firmware_update_ui/firmware_update_app_ui.cc
+++ b/ash/webui/firmware_update_ui/firmware_update_app_ui.cc
@@ -72,6 +72,8 @@
       {"versionText", IDS_FIRMWARE_VERSION_TEXT},
       {"proceedConfirmationText", IDS_FIRMWARE_PROCEED_UPDATE_CONFIRMATION},
       {"confirmationDisclaimer", IDS_FIRMWARE_CONFIRMATION_DISCLAIMER_TEXT},
+      {"confirmationDisclaimerIconAriaLabel",
+       IDS_FIRMWARE_CONFIRMATION_DISCLAIMER_ICON_ARIA_LABEL},
       {"requestIdRemoveReplug", IDS_FIRMWARE_REQUEST_ID_REMOVE_REPLUG},
       {"requestIdRemoveUsbCable", IDS_FIRMWARE_REQUEST_ID_REMOVE_USB_CABLE},
       {"requestIdInsertUsbCable", IDS_FIRMWARE_REQUEST_ID_INSERT_USB_CABLE},
diff --git a/ash/webui/firmware_update_ui/resources/firmware_confirmation_dialog.html b/ash/webui/firmware_update_ui/resources/firmware_confirmation_dialog.html
index 5335134..494f298 100644
--- a/ash/webui/firmware_update_ui/resources/firmware_confirmation_dialog.html
+++ b/ash/webui/firmware_update_ui/resources/firmware_confirmation_dialog.html
@@ -47,8 +47,8 @@
     <div slot="body" class="firmware-dialog-body-font">
       <template is="dom-if" if="[[shouldShowDisclaimer]]" restamp>
         <div id="disclaimer">
-          <div id="disclaimer-icon">
-            <iron-icon icon="firmware-updates:warning"></iron-icon>
+          <div id="disclaimer-icon" aria-label="[[i18n('confirmationDisclaimerIconAriaLabel')]]">
+            <iron-icon icon="firmware-updates:warning" aria-hidden="true"></iron-icon>
           </div>
           <div id="disclaimer-text">[[i18n('confirmationDisclaimer')]]</div>
         </div>
diff --git a/ash/webui/firmware_update_ui/resources/firmware_update_dialog.html b/ash/webui/firmware_update_ui/resources/firmware_update_dialog.html
index 152543f25..dd77b82 100644
--- a/ash/webui/firmware_update_ui/resources/firmware_update_dialog.html
+++ b/ash/webui/firmware_update_ui/resources/firmware_update_dialog.html
@@ -37,11 +37,12 @@
   <cr-dialog id="updateDialog" show-on-attach
       on-close="closeDialog">
     <div slot="title" id="updateDialogTitle" class="firmware-dialog-title-font"
-        tabindex="0" aria-labelledby="updateDialogTitle">
+        aria-labelledby="updateDialogTitle" aria-live="polite">
       [[dialogContent.title]]
     </div>
     <div slot="body" class="firmware-dialog-body-font">
-      <div id="updateDialogBody" aria-hidden="true" tabindex="0">
+      <div id="updateDialogBody" aria-live="[[getDialogBodyAriaLive(installationProgress.*,
+                                              lastDeviceRequestId)]]">
         [[dialogContent.body]]
       </div>
     </div>
@@ -49,7 +50,7 @@
         hidden$="[[!shouldShowProgressBar(installationProgress.*,
                    isInitiallyInflight, lastDeviceRequestId)]]">
       <label id="progress" class="firmware-dialog-installing-font"
-          aria-live="polite" tabindex="0">
+          aria-live="polite">
         [[dialogContent.footer]]
       </label>
       <template is="dom-if"
diff --git a/ash/webui/firmware_update_ui/resources/firmware_update_dialog.ts b/ash/webui/firmware_update_ui/resources/firmware_update_dialog.ts
index 0743b2b..17a5d50 100644
--- a/ash/webui/firmware_update_ui/resources/firmware_update_dialog.ts
+++ b/ash/webui/firmware_update_ui/resources/firmware_update_dialog.ts
@@ -161,15 +161,6 @@
       this.isInitiallyInflight = false;
     }
     this.installationProgress = update;
-    if (this.isUpdateInProgress() && this.isDialogOpen()) {
-      // 'aria-hidden' is used to prevent ChromeVox from announcing
-      // the body text automatically. Setting 'aria-hidden' to false
-      // here allows ChromeVox to announce the body text when a user
-      // navigates to it.
-      assert(this.shadowRoot);
-      this.shadowRoot.querySelector('#updateDialogBody')!.setAttribute(
-          'aria-hidden', 'false');
-    }
   }
 
   protected installationProgressChanged(
@@ -444,6 +435,12 @@
     return isAppV2Enabled() && this.lastDeviceRequestId !== null &&
         this.installationProgress.state === UpdateState.kWaitingForUser;
   }
+
+  private getDialogBodyAriaLive(): string {
+    // Use assertive aria-live value to ensure user requests are announced
+    // before they time out.
+    return this.isWaitingForUserAction() ? 'assertive' : '';
+  }
 }
 
 declare global {
diff --git a/ash/webui/personalization_app/personalization_app_ui.cc b/ash/webui/personalization_app/personalization_app_ui.cc
index 0e8e190..e77688c 100644
--- a/ash/webui/personalization_app/personalization_app_ui.cc
+++ b/ash/webui/personalization_app/personalization_app_ui.cc
@@ -16,6 +16,7 @@
 #include "ash/webui/common/mojom/sea_pen.mojom.h"
 #include "ash/webui/common/sea_pen_provider.h"
 #include "ash/webui/common/sea_pen_resources.h"
+#include "ash/webui/common/sea_pen_resources_generated.h"
 #include "ash/webui/common/trusted_types_util.h"
 #include "ash/webui/grit/ash_personalization_app_resources.h"
 #include "ash/webui/grit/ash_personalization_app_resources_map.h"
@@ -363,6 +364,7 @@
   source->AddLocalizedStrings(kLocalizedStrings);
 
   ::ash::common::AddSeaPenStrings(source);
+  ::ash::common::AddSeaPenWallpaperTemplateStrings(source);
 
   source->AddString("googlePhotosURL", GetGooglePhotosURL());
 
diff --git a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
index 2e5bd997..572fc00 100644
--- a/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
+++ b/ash/webui/shortcut_customization_ui/backend/accelerator_configuration_provider_unittest.cc
@@ -770,16 +770,28 @@
       // search + esc -> search + esc
       {/*trigger_on_press=*/true, ui::VKEY_ESCAPE, ui::EF_COMMAND_DOWN,
        AcceleratorAction::kShowTaskManager},
+      // shift + zoom -> shift + VKEY_ZOOM (no change)
+      {/*trigger_on_press=*/true, ui::VKEY_ZOOM, ui::EF_SHIFT_DOWN,
+       AcceleratorAction::kToggleFullscreen},
       // shift + zoom -> shift + search + VKEY_ZOOM
       {/*trigger_on_press=*/true, ui::VKEY_ZOOM,
        ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN,
        AcceleratorAction::kToggleFullscreen},
+      // zoom -> VKEY_ZOOM (no change)
+      {/*trigger_on_press=*/true, ui::VKEY_ZOOM, ui::EF_NONE,
+       AcceleratorAction::kToggleFullscreen},
       // zoom -> search + VKEY_ZOOM
       {/*trigger_on_press=*/true, ui::VKEY_ZOOM, ui::EF_COMMAND_DOWN,
        AcceleratorAction::kToggleFullscreen},
+      // brightness_up -> VKEY_BRIGHTNESS_UP (no change)
+      {/*trigger_on_press=*/true, ui::VKEY_BRIGHTNESS_UP, ui::EF_NONE,
+       AcceleratorAction::kBrightnessUp},
       // brightness_up -> search + VKEY_BRIGHTNESS_UP
       {/*trigger_on_press=*/true, ui::VKEY_BRIGHTNESS_UP, ui::EF_COMMAND_DOWN,
        AcceleratorAction::kBrightnessUp},
+      // alt + brightness_up -> alt + VKEY_BRIGHTNESS_UP (no change)
+      {/*trigger_on_press=*/true, ui::VKEY_BRIGHTNESS_UP, ui::EF_ALT_DOWN,
+       AcceleratorAction::kKeyboardBrightnessUp},
       // alt + brightness_up -> alt + search + VKEY_BRIGHTNESS_UP
       {/*trigger_on_press=*/true, ui::VKEY_BRIGHTNESS_UP,
        ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN,
@@ -3196,9 +3208,12 @@
           // [brightness_up] -> [search + brightness_up].
           [&](const std::vector<ui::Accelerator>& default_accelerators) {
             const ui::Accelerator expected_accelerator(ui::VKEY_BRIGHTNESS_UP,
-                                                       ui::EF_COMMAND_DOWN);
-            EXPECT_EQ(1u, default_accelerators.size());
+                                                       ui::EF_NONE);
+            const ui::Accelerator expected_accelerator2(ui::VKEY_BRIGHTNESS_UP,
+                                                        ui::EF_COMMAND_DOWN);
+            EXPECT_EQ(2u, default_accelerators.size());
             EXPECT_TRUE(expected_accelerator == default_accelerators[0]);
+            EXPECT_TRUE(expected_accelerator2 == default_accelerators[1]);
           }));
 }
 
diff --git a/ash/webui/vc_background_ui/vc_background_ui.cc b/ash/webui/vc_background_ui/vc_background_ui.cc
index 05df37be..07adcd7 100644
--- a/ash/webui/vc_background_ui/vc_background_ui.cc
+++ b/ash/webui/vc_background_ui/vc_background_ui.cc
@@ -11,6 +11,7 @@
 #include "ash/webui/common/mojom/sea_pen.mojom.h"
 #include "ash/webui/common/sea_pen_provider.h"
 #include "ash/webui/common/sea_pen_resources.h"
+#include "ash/webui/common/sea_pen_resources_generated.h"
 #include "ash/webui/common/trusted_types_util.h"
 #include "ash/webui/grit/ash_vc_background_resources.h"
 #include "ash/webui/grit/ash_vc_background_resources_map.h"
@@ -36,6 +37,7 @@
   // TODO(b/311416410) real translated title.
   source->AddString("vcBackgroundTitle", u"VC Background");
   ::ash::common::AddSeaPenStrings(source);
+  ::ash::common::AddSeaPenVcBackgroundTemplateStrings(source);
 
   source->UseStringsJs();
   source->EnableReplaceI18nInJS();
diff --git a/ash/wm/window_restore/pine_contents_view.cc b/ash/wm/window_restore/pine_contents_view.cc
index da457f2c..59e113b 100644
--- a/ash/wm/window_restore/pine_contents_view.cc
+++ b/ash/wm/window_restore/pine_contents_view.cc
@@ -68,6 +68,7 @@
 constexpr int kOverflowTableSize = 20;
 constexpr int kOverflowBackgroundRounding = 20;
 constexpr int kOverflowCountBackgroundRounding = 9;
+constexpr int kOverflowTwoWindowPadding = 11;
 constexpr gfx::Size kOverflowIconPreferredSize(20, 20);
 constexpr gfx::Size kOverflowCountPreferredSize(18, 18);
 
@@ -212,6 +213,8 @@
   explicit PineItemsOverflowView(const PineContentsView::AppsData& apps) {
     const int elements = static_cast<int>(apps.size());
     CHECK_GE(elements, kOverflowMinElements);
+    // TODO(http://b/322361588): Remove after the 3-window case is added.
+    CHECK_NE(elements, 3);
 
     // TODO(hewer): Fix margins so the icons and text are aligned with
     // `PineItemView` elements.
@@ -219,35 +222,45 @@
     SetCrossAxisAlignment(views::BoxLayout::CrossAxisAlignment::kCenter);
     SetOrientation(views::BoxLayout::Orientation::kHorizontal);
 
-    // Create a 2x2 table view to display the icons for 4 or more windows, or
-    // the count of the remaining windows, rather than a single icon.
-    views::TableLayoutView* table_layout_view;
-    AddChildView(
-        views::Builder<views::TableLayoutView>()
-            .CopyAddressTo(&table_layout_view)
-            .AddColumn(
-                views::LayoutAlignment::kCenter,
-                views::LayoutAlignment::kCenter, views::TableLayout::kFixedSize,
-                views::TableLayout::ColumnSize::kFixed, kOverflowTableSize, 0)
-            .AddPaddingColumn(views::TableLayout::kFixedSize,
-                              kOverflowTablePadding)
-            .AddColumn(
-                views::LayoutAlignment::kCenter,
-                views::LayoutAlignment::kCenter, views::TableLayout::kFixedSize,
-                views::TableLayout::ColumnSize::kFixed, kOverflowTableSize, 0)
-            .AddRows(1, views::TableLayout::kFixedSize, kOverflowTableSize)
-            .AddPaddingRow(views::TableLayout::kFixedSize,
-                           kOverflowTablePadding)
-            .AddRows(1, views::TableLayout::kFixedSize, kOverflowTableSize)
-            .SetBackground(views::CreateRoundedRectBackground(
-                SK_ColorLTGRAY, kOverflowBackgroundRounding))
-            .Build());
+    // TODO(hewer): Fix the styling to make the background smaller than the
+    // contents.
+    views::Builder<views::TableLayoutView> table_builder;
+    table_builder
+        .SetBackground(views::CreateRoundedRectBackground(
+            SK_ColorLTGRAY, kOverflowBackgroundRounding))
+        .AddColumn(
+            views::LayoutAlignment::kCenter, views::LayoutAlignment::kCenter,
+            views::TableLayout::kFixedSize,
+            views::TableLayout::ColumnSize::kFixed, kOverflowTableSize, 0)
+        .AddPaddingColumn(views::TableLayout::kFixedSize, kOverflowTablePadding)
+        .AddColumn(
+            views::LayoutAlignment::kCenter, views::LayoutAlignment::kCenter,
+            views::TableLayout::kFixedSize,
+            views::TableLayout::ColumnSize::kFixed, kOverflowTableSize, 0);
+    if (elements >= kOverflowMaxElements) {
+      // Create a 2x2 table view to display the icons for 4 or more windows, or
+      // the count of the remaining windows, rather than a single icon.
+      table_builder
+          .AddRows(1, views::TableLayout::kFixedSize, kOverflowTableSize)
+          .AddPaddingRow(views::TableLayout::kFixedSize, kOverflowTablePadding)
+          .AddRows(1, views::TableLayout::kFixedSize, kOverflowTableSize);
+    } else if (elements == kOverflowMinElements) {
+      // Create a 1x2 table view to display the icons for 2 windows, centered
+      // vertically.
+      table_builder
+          .AddPaddingRow(views::TableLayout::kFixedSize,
+                         kOverflowTwoWindowPadding)
+          .AddRows(1, views::TableLayout::kFixedSize, kOverflowTableSize)
+          .AddPaddingRow(views::TableLayout::kFixedSize,
+                         kOverflowTwoWindowPadding);
+    }
+    views::TableLayoutView* table_layout_view =
+        AddChildView(std::move(table_builder).Build());
 
     // TODO(sammiequon): Handle case where the app is not ready or installed.
     auto* delegate = Shell::Get()->saved_desk_delegate();
 
     // Add the overflow apps to the table.
-    // TODO(http://b/322361367): Handle case where there are 2 overflow windows.
     // TODO(http://b/322361588): Handle case where there are 3 overflow windows.
     for (int i = kOverflowMinThreshold; i < elements; ++i) {
       // If there are 5 or more overflow windows, save the last spot in the
@@ -466,10 +479,6 @@
         {"odknhmnlageboeamepcngndbggdpaobj", {}},  // Settings
         {"fkiggjmkendpmbegkagpmagjepfkpmeb", {}},  // Files
         {"oabkinaljpjeilageghcdlnekhphhphl", {}},  // Calculator
-        {"agimnkijcaahngcdmfeangaknmldooml", {}},  // YouTube
-        {"kefjledonklijopmnomlcbpllchaibag", {}},  // Google Slides
-        {"mgndgikekgjfcpckkfioiadnlibdjbkf",       // Chrome
-         {"https://www.reddit.com/"}},
     };
     PineItemsContainerView* container_view = AddChildView(
         std::make_unique<PineItemsContainerView>(kTestingAppsData));
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 8942ea1..685df99 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -784,9 +784,6 @@
     "task/thread_pool/environment_config.h",
     "task/thread_pool/job_task_source.cc",
     "task/thread_pool/job_task_source.h",
-    "task/thread_pool/job_task_source_interface.h",
-    "task/thread_pool/job_task_source_old.cc",
-    "task/thread_pool/job_task_source_old.h",
     "task/thread_pool/pooled_parallel_task_runner.cc",
     "task/thread_pool/pooled_parallel_task_runner.h",
     "task/thread_pool/pooled_sequenced_task_runner.cc",
diff --git a/base/android/java/src/org/chromium/base/cached_flags/CachedFlag.java b/base/android/java/src/org/chromium/base/cached_flags/CachedFlag.java
index f5368798..bb629c497 100644
--- a/base/android/java/src/org/chromium/base/cached_flags/CachedFlag.java
+++ b/base/android/java/src/org/chromium/base/cached_flags/CachedFlag.java
@@ -40,6 +40,7 @@
  */
 public class CachedFlag extends Flag {
     private final boolean mDefaultValue;
+    private String mPreferenceKey;
 
     public CachedFlag(FeatureMap featureMap, String featureName, boolean defaultValue) {
         super(featureMap, featureName);
@@ -125,7 +126,11 @@
     }
 
     String getSharedPreferenceKey() {
-        return CachedFlagsSharedPreferences.FLAGS_CACHED.createKey(mFeatureName);
+        // Create the key only once to avoid String concatenation every flag check.
+        if (mPreferenceKey == null) {
+            mPreferenceKey = CachedFlagsSharedPreferences.FLAGS_CACHED.createKey(mFeatureName);
+        }
+        return mPreferenceKey;
     }
 
     /**
diff --git a/base/containers/fixed_flat_map.h b/base/containers/fixed_flat_map.h
index df048f0..3f74b78b 100644
--- a/base/containers/fixed_flat_map.h
+++ b/base/containers/fixed_flat_map.h
@@ -93,7 +93,7 @@
 // of keys and values. Requires that the passed in `data` contains unique keys.
 //
 // Large inputs will run into compiler limits, e.g. "constexpr evaluation hit
-// maximum step limit". In that case, use `MakeFixedFlatMap(sorted_unique)`.
+// maximum step limit", unless `data` is already sorted.
 //
 // Example usage:
 //   constexpr auto kMap = base::MakeFixedFlatMap<std::string_view, int>(
diff --git a/base/containers/span.h b/base/containers/span.h
index 1edfb81..8beadb6 100644
--- a/base/containers/span.h
+++ b/base/containers/span.h
@@ -207,6 +207,9 @@
 // - The deduction guides from a contiguous range are folded into a single one,
 //   and treat borrowed ranges correctly.
 //
+// Other differences:
+// - Using StrictNumeric<size_t> instead of size_t where possible.
+//
 // Additions beyond the C++ standard draft
 // - as_chars() function.
 // - as_writable_chars() function.
@@ -303,14 +306,14 @@
     return span<T, Count>(data() + (size() - Count), Count);
   }
 
-  constexpr span<T> first(size_t count) const noexcept {
-    CHECK_LE(count, size());
+  constexpr span<T> first(StrictNumeric<size_t> count) const noexcept {
+    CHECK_LE(size_t{count}, size());
     return {data(), count};
   }
 
-  constexpr span<T> last(size_t count) const noexcept {
-    CHECK_LE(count, size());
-    return {data() + (size() - count), count};
+  constexpr span<T> last(StrictNumeric<size_t> count) const noexcept {
+    CHECK_LE(size_t{count}, size());
+    return {data() + (size() - size_t{count}), count};
   }
 
   template <size_t Offset, size_t Count = dynamic_extent>
@@ -509,14 +512,14 @@
     return span<T, Count>(data() + (size() - Count), Count);
   }
 
-  constexpr span<T> first(size_t count) const noexcept {
-    CHECK_LE(count, size());
+  constexpr span<T> first(StrictNumeric<size_t> count) const noexcept {
+    CHECK_LE(size_t{count}, size());
     return {data(), count};
   }
 
-  constexpr span<T> last(size_t count) const noexcept {
-    CHECK_LE(count, size());
-    return {data() + (size() - count), count};
+  constexpr span<T> last(StrictNumeric<size_t> count) const noexcept {
+    CHECK_LE(size_t{count}, size());
+    return {data() + (size() - size_t{count}), count};
   }
 
   template <size_t Offset, size_t Count = dynamic_extent>
diff --git a/base/hash/md5.h b/base/hash/md5.h
index aa889f3..cdf8327 100644
--- a/base/hash/md5.h
+++ b/base/hash/md5.h
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "base/base_export.h"
+#include "base/containers/span.h"
 #include "base/strings/string_piece.h"
 #include "build/build_config.h"
 
@@ -57,9 +58,20 @@
 // Converts a digest into human-readable hexadecimal.
 BASE_EXPORT std::string MD5DigestToBase16(const MD5Digest& digest);
 
+// Computes the MD5 sum of the given `data`.
+// The 'digest' structure will be filled with the result.
+BASE_EXPORT void MD5Sum(base::span<const uint8_t> data, MD5Digest* digest);
+
 // Computes the MD5 sum of the given data buffer with the given length.
 // The given 'digest' structure will be filled with the result data.
-BASE_EXPORT void MD5Sum(const void* data, size_t length, MD5Digest* digest);
+//
+// TODO(https://crbug.com.1490484): Remove this overload, in favor of the one
+// taking `span` (see above).
+BASE_EXPORT inline void MD5Sum(const void* data,
+                               size_t length,
+                               MD5Digest* digest) {
+  MD5Sum(span(static_cast<const uint8_t*>(data), length), digest);
+}
 
 // Returns the MD5 (in hexadecimal) of a string.
 BASE_EXPORT std::string MD5String(const StringPiece& str);
diff --git a/base/hash/md5_boringssl.cc b/base/hash/md5_boringssl.cc
index 7e9c93b..51de7df 100644
--- a/base/hash/md5_boringssl.cc
+++ b/base/hash/md5_boringssl.cc
@@ -25,13 +25,13 @@
   return ToLowerASCII(HexEncode(digest.a, MD5_DIGEST_LENGTH));
 }
 
-void MD5Sum(const void* data, size_t length, MD5Digest* digest) {
-  MD5(reinterpret_cast<const uint8_t*>(data), length, digest->a);
+void MD5Sum(base::span<const uint8_t> data, MD5Digest* digest) {
+  MD5(data.data(), data.size(), digest->a);
 }
 
 std::string MD5String(const StringPiece& str) {
   MD5Digest digest;
-  MD5Sum(str.data(), str.size(), &digest);
+  MD5Sum(base::as_byte_span(str), &digest);
   return MD5DigestToBase16(digest);
 }
 }  // namespace base
diff --git a/base/hash/md5_nacl.cc b/base/hash/md5_nacl.cc
index c49e20c6..80b3f10a 100644
--- a/base/hash/md5_nacl.cc
+++ b/base/hash/md5_nacl.cc
@@ -23,6 +23,7 @@
 
 #include <stddef.h>
 
+#include "base/containers/span.h"
 #include "base/hash/md5.h"
 #include "base/strings/string_number_conversions.h"
 
@@ -271,16 +272,17 @@
   return ret;
 }
 
-void MD5Sum(const void* data, size_t length, MD5Digest* digest) {
+void MD5Sum(span<const uint8_t> data, MD5Digest* digest) {
   MD5Context ctx;
   MD5Init(&ctx);
-  MD5Update(&ctx, StringPiece(reinterpret_cast<const char*>(data), length));
+  span<const char> chars = as_chars(data);
+  MD5Update(&ctx, StringPiece(chars.data(), chars.size()));
   MD5Final(digest, &ctx);
 }
 
 std::string MD5String(const StringPiece& str) {
   MD5Digest digest;
-  MD5Sum(str.data(), str.length(), &digest);
+  MD5Sum(as_byte_span(str), &digest);
   return MD5DigestToBase16(digest);
 }
 
diff --git a/base/hash/md5_unittest.cc b/base/hash/md5_unittest.cc
index 7578fcf..9084a14f 100644
--- a/base/hash/md5_unittest.cc
+++ b/base/hash/md5_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/hash/md5.h"
 
+#include "base/containers/span.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace base {
@@ -27,11 +28,11 @@
   EXPECT_EQ(expected, actual);
 }
 
-TEST(MD5, MD5SumEmtpyData) {
+TEST(MD5, MD5SumEmptyData) {
   MD5Digest digest;
   const char data[] = "";
 
-  MD5Sum(data, strlen(data), &digest);
+  MD5Sum(base::as_byte_span(std::string_view(data)), &digest);
 
   int expected[] = {0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04,
                     0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e};
@@ -44,7 +45,7 @@
   MD5Digest digest;
   const char data[] = "a";
 
-  MD5Sum(data, strlen(data), &digest);
+  MD5Sum(base::as_byte_span(std::string_view(data)), &digest);
 
   int expected[] = {0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8,
                     0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61};
@@ -54,20 +55,22 @@
 }
 
 TEST(MD5, MD5SumLongData) {
-  const int length = 10 * 1024 * 1024 + 1;
+  const size_t length = 10 * 1024 * 1024 + 1;
   std::unique_ptr<char[]> data(new char[length]);
 
-  for (int i = 0; i < length; ++i)
+  for (size_t i = 0; i < length; ++i) {
     data[i] = i & 0xFF;
+  }
 
   MD5Digest digest;
-  MD5Sum(data.get(), length, &digest);
+  MD5Sum(base::as_byte_span(base::span(data.get(), length)), &digest);
 
   int expected[] = {0x90, 0xbd, 0x6a, 0xd9, 0x0a, 0xce, 0xf5, 0xad,
                     0xaa, 0x92, 0x20, 0x3e, 0x21, 0xc7, 0xa1, 0x3e};
 
-  for (int i = 0; i < 16; ++i)
+  for (size_t i = 0; i < 16; ++i) {
     EXPECT_EQ(expected[i], digest.a[i] & 0xFF);
+  }
 }
 
 TEST(MD5, ContextWithEmptyData) {
diff --git a/base/metrics/metrics_hashes.cc b/base/metrics/metrics_hashes.cc
index 0632fd2..9843df9 100644
--- a/base/metrics/metrics_hashes.cc
+++ b/base/metrics/metrics_hashes.cc
@@ -7,6 +7,7 @@
 #include <string.h>
 
 #include "base/check_op.h"
+#include "base/containers/span.h"
 #include "base/hash/md5.h"
 #include "base/hash/sha1.h"
 #include "base/sys_byteorder.h"
@@ -40,13 +41,13 @@
   //   struct.unpack('>Q', hashlib.md5(name.encode('utf-8')).digest()[:8])[0]
   //
   base::MD5Digest digest;
-  base::MD5Sum(name.data(), name.size(), &digest);
+  base::MD5Sum(base::as_byte_span(name), &digest);
   return DigestToUInt64(digest);
 }
 
 uint32_t HashMetricNameAs32Bits(base::StringPiece name) {
   base::MD5Digest digest;
-  base::MD5Sum(name.data(), name.size(), &digest);
+  base::MD5Sum(base::as_byte_span(name), &digest);
   return DigestToUInt32(digest);
 }
 
diff --git a/base/pickle.h b/base/pickle.h
index 4d98168..a40afe28 100644
--- a/base/pickle.h
+++ b/base/pickle.h
@@ -276,6 +276,10 @@
     return reinterpret_cast<const char*>(header_) + header_size_;
   }
 
+  base::span<const uint8_t> payload_bytes() const {
+    return base::as_bytes(base::make_span(payload(), payload_size()));
+  }
+
   // Returns the address of the byte immediately following the currently valid
   // header + payload.
   const char* end_of_payload() const {
diff --git a/base/process/port_provider_mac.cc b/base/process/port_provider_mac.cc
index 2ebb22c0..e7703c0 100644
--- a/base/process/port_provider_mac.cc
+++ b/base/process/port_provider_mac.cc
@@ -20,12 +20,14 @@
   observer_list_->RemoveObserver(observer);
 }
 
-void PortProvider::NotifyObservers(ProcessHandle process) {
-  observer_list_->Notify(FROM_HERE, &Observer::OnReceivedTaskPort, process);
+void PortProvider::NotifyObservers(ProcessHandle process_handle) {
+  observer_list_->Notify(FROM_HERE, &Observer::OnReceivedTaskPort,
+                         process_handle);
 }
 
-mach_port_t SelfPortProvider::TaskForPid(base::ProcessHandle process) const {
-  DCHECK(base::Process(process).is_current());
+mach_port_t SelfPortProvider::TaskForHandle(
+    base::ProcessHandle process_handle) const {
+  DCHECK(base::Process(process_handle).is_current());
   return mach_task_self();
 }
 
diff --git a/base/process/port_provider_mac.h b/base/process/port_provider_mac.h
index 74cab33..c3f5481 100644
--- a/base/process/port_provider_mac.h
+++ b/base/process/port_provider_mac.h
@@ -33,12 +33,12 @@
     // received for a given process.
     // This notification is guaranteed to be sent on the same task runner where
     // the observer was added.
-    virtual void OnReceivedTaskPort(ProcessHandle process) = 0;
+    virtual void OnReceivedTaskPort(ProcessHandle process_handle) = 0;
   };
 
-  // Returns the mach task port for |process| if possible, or else
-  // |MACH_PORT_NULL|.
-  virtual mach_port_t TaskForPid(ProcessHandle process) const = 0;
+  // Returns the mach task port for `process_handle` if possible, or else
+  // `MACH_PORT_NULL`.
+  virtual mach_port_t TaskForHandle(ProcessHandle process_handle) const = 0;
 
   // Observer interface.
   void AddObserver(Observer* observer);
@@ -46,7 +46,7 @@
 
  protected:
   // Called by subclasses to send a notification to observers.
-  void NotifyObservers(ProcessHandle process);
+  void NotifyObservers(ProcessHandle process_handle);
 
  private:
   scoped_refptr<base::ObserverListThreadSafe<Observer>> observer_list_;
@@ -55,7 +55,7 @@
 // Port provider that returns the calling process's task port, ignoring its
 // argument.
 class BASE_EXPORT SelfPortProvider : public base::PortProvider {
-  mach_port_t TaskForPid(base::ProcessHandle process) const override;
+  mach_port_t TaskForHandle(base::ProcessHandle process_handle) const override;
 };
 
 }  // namespace base
diff --git a/base/process/process_mac.cc b/base/process/process_mac.cc
index 9331a05..f7b0e750 100644
--- a/base/process/process_mac.cc
+++ b/base/process/process_mac.cc
@@ -164,7 +164,7 @@
   CHECK(IsValid());
   CHECK(port_provider);
 
-  mach_port_t task_port = port_provider->TaskForPid(Pid());
+  mach_port_t task_port = port_provider->TaskForHandle(Handle());
   if (task_port == TASK_NULL) {
     // Upon failure, return the default value.
     return Priority::kUserBlocking;
@@ -201,7 +201,7 @@
     return false;
   }
 
-  mach_port_t task_port = port_provider->TaskForPid(Pid());
+  mach_port_t task_port = port_provider->TaskForHandle(Handle());
   if (task_port == TASK_NULL) {
     return false;
   }
diff --git a/base/process/process_metrics.h b/base/process/process_metrics.h
index 3c876329..490f53c 100644
--- a/base/process/process_metrics.h
+++ b/base/process/process_metrics.h
@@ -250,7 +250,7 @@
       uint64_t absolute_package_idle_wakeups);
 
   // Queries the port provider if it's set.
-  mach_port_t TaskForPid(ProcessHandle process) const;
+  mach_port_t TaskForHandle(ProcessHandle process_handle) const;
 #endif
 
 #if BUILDFLAG(IS_WIN)
diff --git a/base/process/process_metrics_apple.cc b/base/process/process_metrics_apple.cc
index 6cf39a6..1a23c8a 100644
--- a/base/process/process_metrics_apple.cc
+++ b/base/process/process_metrics_apple.cc
@@ -80,21 +80,21 @@
 }  // namespace
 
 // Implementations of ProcessMetrics class shared by Mac and iOS.
-mach_port_t ProcessMetrics::TaskForPid(ProcessHandle process) const {
+mach_port_t ProcessMetrics::TaskForHandle(ProcessHandle process_handle) const {
   mach_port_t task = MACH_PORT_NULL;
 #if BUILDFLAG(IS_MAC)
   if (port_provider_) {
-    task = port_provider_->TaskForPid(process_);
+    task = port_provider_->TaskForHandle(process_);
   }
 #endif
-  if (task == MACH_PORT_NULL && process_ == getpid()) {
+  if (task == MACH_PORT_NULL && process_handle == getpid()) {
     task = mach_task_self();
   }
   return task;
 }
 
 TimeDelta ProcessMetrics::GetCumulativeCPUUsage() {
-  mach_port_t task = TaskForPid(process_);
+  mach_port_t task = TaskForHandle(process_);
   if (task == MACH_PORT_NULL) {
     return TimeDelta();
   }
@@ -144,7 +144,7 @@
 }
 
 int ProcessMetrics::GetPackageIdleWakeupsPerSecond() {
-  mach_port_t task = TaskForPid(process_);
+  mach_port_t task = TaskForHandle(process_);
   task_power_info power_info_data;
 
   GetPowerInfo(task, &power_info_data);
@@ -164,7 +164,7 @@
 }
 
 int ProcessMetrics::GetIdleWakeupsPerSecond() {
-  mach_port_t task = TaskForPid(process_);
+  mach_port_t task = TaskForHandle(process_);
   task_power_info power_info_data;
 
   GetPowerInfo(task, &power_info_data);
diff --git a/base/process/process_metrics_unittest.cc b/base/process/process_metrics_unittest.cc
index c387cb7..e5196db 100644
--- a/base/process/process_metrics_unittest.cc
+++ b/base/process/process_metrics_unittest.cc
@@ -160,8 +160,8 @@
   TestChildPortProvider(const TestChildPortProvider&) = delete;
   TestChildPortProvider& operator=(const TestChildPortProvider&) = delete;
 
-  mach_port_t TaskForPid(ProcessHandle process) const final {
-    return process == handle_ ? port_.get() : MACH_PORT_NULL;
+  mach_port_t TaskForHandle(ProcessHandle process_handle) const final {
+    return process_handle == handle_ ? port_.get() : MACH_PORT_NULL;
   }
 
  private:
diff --git a/base/process/process_unittest.cc b/base/process/process_unittest.cc
index c808410..4e18fc3 100644
--- a/base/process/process_unittest.cc
+++ b/base/process/process_unittest.cc
@@ -58,7 +58,7 @@
 // Fake port provider that returns the calling process's
 // task port, ignoring its argument.
 class FakePortProvider : public base::PortProvider {
-  mach_port_t TaskForPid(base::ProcessHandle process) const override {
+  mach_port_t TaskForHandle(base::ProcessHandle process_handle) const override {
     return mach_task_self();
   }
 };
diff --git a/base/task/common/task_annotator.cc b/base/task/common/task_annotator.cc
index 4c2ab37..faa8e8f 100644
--- a/base/task/common/task_annotator.cc
+++ b/base/task/common/task_annotator.cc
@@ -11,6 +11,7 @@
 #include "base/auto_reset.h"
 #include "base/check_op.h"
 #include "base/compiler_specific.h"
+#include "base/containers/span.h"
 #include "base/debug/alias.h"
 #include "base/hash/md5.h"
 #include "base/logging.h"
@@ -314,7 +315,7 @@
 uint32_t TaskAnnotator::ScopedSetIpcHash::MD5HashMetricName(
     base::StringPiece name) {
   base::MD5Digest digest;
-  base::MD5Sum(name.data(), name.size(), &digest);
+  base::MD5Sum(base::as_byte_span(name), &digest);
   uint32_t value;
   DCHECK_GE(sizeof(digest.a), sizeof(value));
   memcpy(&value, digest.a, sizeof(value));
diff --git a/base/task/post_job.cc b/base/task/post_job.cc
index 1ed3baa..69fa319 100644
--- a/base/task/post_job.cc
+++ b/base/task/post_job.cc
@@ -4,11 +4,8 @@
 
 #include "base/task/post_job.h"
 
-#include "base/feature_list.h"
 #include "base/task/scoped_set_task_priority_for_current_thread.h"
-#include "base/task/task_features.h"
 #include "base/task/thread_pool/job_task_source.h"
-#include "base/task/thread_pool/job_task_source_old.h"
 #include "base/task/thread_pool/pooled_task_runner_delegate.h"
 #include "base/task/thread_pool/thread_pool_impl.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
@@ -26,7 +23,7 @@
       << "Hint: if this is in a unit test, you're likely merely missing a "
          "base::test::TaskEnvironment member in your fixture.\n";
 
-  return internal::CreateJobTaskSource(
+  return base::MakeRefCounted<internal::JobTaskSource>(
       from_here, traits, std::move(worker_task),
       std::move(max_concurrency_callback),
       static_cast<internal::ThreadPoolImpl*>(ThreadPoolInstance::Get()));
@@ -104,15 +101,15 @@
 
 void JobHandle::UpdatePriority(TaskPriority new_priority) {
   if (!internal::PooledTaskRunnerDelegate::MatchesCurrentDelegate(
-          task_source_->GetDelegate())) {
+          task_source_->delegate())) {
     return;
   }
-  task_source_->GetDelegate()->UpdateJobPriority(task_source_, new_priority);
+  task_source_->delegate()->UpdateJobPriority(task_source_, new_priority);
 }
 
 void JobHandle::NotifyConcurrencyIncrease() {
   if (!internal::PooledTaskRunnerDelegate::MatchesCurrentDelegate(
-          task_source_->GetDelegate())) {
+          task_source_->delegate())) {
     return;
   }
   task_source_->NotifyConcurrencyIncrease();
@@ -120,35 +117,36 @@
 
 void JobHandle::Join() {
   DCHECK(internal::PooledTaskRunnerDelegate::MatchesCurrentDelegate(
-      task_source_->GetDelegate()));
+      task_source_->delegate()));
   DCHECK_GE(internal::GetTaskPriorityForCurrentThread(),
             task_source_->priority_racy())
       << "Join may not be called on Job with higher priority than the current "
          "thread.";
   UpdatePriority(internal::GetTaskPriorityForCurrentThread());
-
-  // Ensure that the job is queued if it has remaining concurrency. This is
-  // necessary to support CreateJob(...).Join().
-  task_source_->NotifyConcurrencyIncrease();
-
+  if (task_source_->GetRemainingConcurrency() != 0) {
+    // Make sure the task source is in the queue if not enough workers are
+    // contributing. This is necessary for CreateJob(...).Join(). This is a
+    // noop if the task source was already in the queue.
+    task_source_->delegate()->EnqueueJobTaskSource(task_source_);
+  }
   bool must_run = task_source_->WillJoin();
   while (must_run)
     must_run = task_source_->RunJoinTask();
   // Remove |task_source_| from the ThreadPool to prevent access to
   // |max_concurrency_callback| after Join().
-  task_source_->GetDelegate()->RemoveJobTaskSource(task_source_);
+  task_source_->delegate()->RemoveJobTaskSource(task_source_);
   task_source_ = nullptr;
 }
 
 void JobHandle::Cancel() {
   DCHECK(internal::PooledTaskRunnerDelegate::MatchesCurrentDelegate(
-      task_source_->GetDelegate()));
+      task_source_->delegate()));
   task_source_->Cancel();
   bool must_run = task_source_->WillJoin();
   DCHECK(!must_run);
   // Remove |task_source_| from the ThreadPool to prevent access to
   // |max_concurrency_callback| after Join().
-  task_source_->GetDelegate()->RemoveJobTaskSource(task_source_);
+  task_source_->delegate()->RemoveJobTaskSource(task_source_);
   task_source_ = nullptr;
 }
 
@@ -169,8 +167,13 @@
   auto task_source =
       CreateJobTaskSource(from_here, traits, std::move(worker_task),
                           std::move(max_concurrency_callback));
-  task_source->NotifyConcurrencyIncrease();
-  return internal::JobTaskSource::CreateJobHandle(std::move(task_source));
+  const bool queued =
+      static_cast<internal::ThreadPoolImpl*>(ThreadPoolInstance::Get())
+          ->EnqueueJobTaskSource(task_source);
+  if (queued) {
+    return internal::JobTaskSource::CreateJobHandle(std::move(task_source));
+  }
+  return JobHandle();
 }
 
 JobHandle CreateJob(const Location& from_here,
@@ -183,25 +186,4 @@
   return internal::JobTaskSource::CreateJobHandle(std::move(task_source));
 }
 
-namespace internal {
-
-scoped_refptr<JobTaskSource> CreateJobTaskSource(
-    const Location& from_here,
-    const TaskTraits& traits,
-    RepeatingCallback<void(JobDelegate*)> worker_task,
-    MaxConcurrencyCallback max_concurrency_callback,
-    PooledTaskRunnerDelegate* delegate) {
-  if (base::FeatureList::IsEnabled(kUseNewJobImplementation)) {
-    return base::MakeRefCounted<internal::JobTaskSourceNew>(
-        from_here, traits, std::move(worker_task),
-        std::move(max_concurrency_callback), delegate);
-  }
-
-  return base::MakeRefCounted<internal::JobTaskSourceOld>(
-      from_here, traits, std::move(worker_task),
-      std::move(max_concurrency_callback), delegate);
-}
-
-}  // namespace internal
-
 }  // namespace base
diff --git a/base/task/post_job.h b/base/task/post_job.h
index f64230b..582aa61 100644
--- a/base/task/post_job.h
+++ b/base/task/post_job.h
@@ -207,19 +207,6 @@
           RepeatingCallback<void(JobDelegate*)> worker_task,
           MaxConcurrencyCallback max_concurrency_callback);
 
-namespace internal {
-
-// Helper which creates a JobTaskSourceNew or JobTaskSourceOld depending on the
-// UseNewJobImplementation feature. Exposed for testing.
-scoped_refptr<JobTaskSource> BASE_EXPORT
-CreateJobTaskSource(const Location& from_here,
-                    const TaskTraits& traits,
-                    RepeatingCallback<void(JobDelegate*)> worker_task,
-                    MaxConcurrencyCallback max_concurrency_callback,
-                    PooledTaskRunnerDelegate* delegate);
-
-}  // namespace internal
-
 }  // namespace base
 
 #endif  // BASE_TASK_POST_JOB_H_
diff --git a/base/task/task_features.cc b/base/task/task_features.cc
index 6b304fa6..b69c77c 100644
--- a/base/task/task_features.cc
+++ b/base/task/task_features.cc
@@ -75,10 +75,6 @@
 const base::FeatureParam<int> kMaxDelayedStarvationTasksParam{
     &kMaxDelayedStarvationTasks, "count", 3};
 
-BASE_FEATURE(kUseNewJobImplementation,
-             "UseNewJobImplementation",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 BASE_FEATURE(kThreadGroupSemaphore,
              "ThreadGroupSemaphore",
              base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/base/task/task_features.h b/base/task/task_features.h
index 35536ccd..e8611ac 100644
--- a/base/task/task_features.h
+++ b/base/task/task_features.h
@@ -66,9 +66,6 @@
 extern const BASE_EXPORT base::FeatureParam<int>
     kMaxDelayedStarvationTasksParam;
 
-// Feature to use a JobTaskSource implementation that minimizes lock contention.
-BASE_EXPORT BASE_DECLARE_FEATURE(kUseNewJobImplementation);
-
 // Feature to use ThreadGroupSemaphore instead of ThreadGroupImpl.
 BASE_EXPORT BASE_DECLARE_FEATURE(kThreadGroupSemaphore);
 extern const BASE_EXPORT base::FeatureParam<int> kMaxNumWorkersCreated;
diff --git a/base/task/thread_pool/job_task_source.cc b/base/task/thread_pool/job_task_source.cc
index 1b70c2e..be531a9f 100644
--- a/base/task/thread_pool/job_task_source.cc
+++ b/base/task/thread_pool/job_task_source.cc
@@ -5,20 +5,25 @@
 #include "base/task/thread_pool/job_task_source.h"
 
 #include <bit>
-#include <limits>
 #include <type_traits>
+#include <utility>
 
 #include "base/check_op.h"
 #include "base/functional/bind.h"
 #include "base/functional/callback_helpers.h"
+#include "base/memory/ptr_util.h"
 #include "base/notreached.h"
 #include "base/task/common/checked_lock.h"
+#include "base/task/task_features.h"
 #include "base/task/thread_pool/pooled_task_runner_delegate.h"
+#include "base/template_util.h"
 #include "base/threading/thread_restrictions.h"
 #include "base/time/time.h"
+#include "base/time/time_override.h"
 #include "base/trace_event/base_tracing.h"
 
-namespace base::internal {
+namespace base {
+namespace internal {
 
 namespace {
 
@@ -33,105 +38,65 @@
 
 }  // namespace
 
-JobTaskSourceNew::State::State() = default;
-JobTaskSourceNew::State::~State() = default;
+JobTaskSource::State::State() = default;
+JobTaskSource::State::~State() = default;
 
-JobTaskSourceNew::State::Value JobTaskSourceNew::State::Cancel() {
+JobTaskSource::State::Value JobTaskSource::State::Cancel() {
   return {value_.fetch_or(kCanceledMask, std::memory_order_relaxed)};
 }
 
-JobTaskSourceNew::State::Value JobTaskSourceNew::State::IncrementWorkerCount() {
-  uint32_t prev =
+JobTaskSource::State::Value JobTaskSource::State::DecrementWorkerCount() {
+  const uint32_t value_before_sub =
+      value_.fetch_sub(kWorkerCountIncrement, std::memory_order_relaxed);
+  DCHECK((value_before_sub >> kWorkerCountBitOffset) > 0);
+  return {value_before_sub};
+}
+
+JobTaskSource::State::Value JobTaskSource::State::IncrementWorkerCount() {
+  uint32_t value_before_add =
       value_.fetch_add(kWorkerCountIncrement, std::memory_order_relaxed);
   // The worker count must not overflow a uint8_t.
-  DCHECK((prev >> kWorkerCountBitOffset) < ((1 << 8) - 1));
-  return {prev};
+  DCHECK((value_before_add >> kWorkerCountBitOffset) < ((1 << 8) - 1));
+  return {value_before_add};
 }
 
-JobTaskSourceNew::State::Value JobTaskSourceNew::State::DecrementWorkerCount() {
-  uint32_t prev =
-      value_.fetch_sub(kWorkerCountIncrement, std::memory_order_relaxed);
-  DCHECK((prev >> kWorkerCountBitOffset) > 0);
-  return {prev};
-}
-
-JobTaskSourceNew::State::Value JobTaskSourceNew::State::RequestSignalJoin() {
-  uint32_t prev = value_.fetch_or(kSignalJoinMask, std::memory_order_relaxed);
-  return {prev};
-}
-
-bool JobTaskSourceNew::State::FetchAndResetRequestSignalJoin() {
-  uint32_t prev = value_.fetch_and(~kSignalJoinMask, std::memory_order_relaxed);
-  return !!(prev & kSignalJoinMask);
-}
-
-bool JobTaskSourceNew::State::ShouldQueueUponCapacityIncrease() {
-  // If `WillRunTask()` is running: setting
-  // `kOutsideWillRunTaskOrMustReenqueueMask` ensures that this capacity
-  // increase is taken into account in the returned `RunStatus`.
-  //
-  // If `WillRunTask()` is not running, setting
-  // `kOutsideWillRunTaskOrMustReenqueueMask` is a no-op (already set).
-  //
-  // Release paired with Acquire in `ExitWillRunTask()`, see comment there.
-  Value prev{
-      value_.fetch_or(kQueuedMask | kOutsideWillRunTaskOrMustReenqueueMask,
-                      std::memory_order_release)};
-  return !prev.queued() && prev.outside_will_run_task_or_must_reenqueue();
-}
-
-JobTaskSourceNew::State::Value JobTaskSourceNew::State::EnterWillRunTask() {
-  Value prev{
-      value_.fetch_and(~(kQueuedMask | kOutsideWillRunTaskOrMustReenqueueMask),
-                       std::memory_order_relaxed)};
-  CHECK(prev.outside_will_run_task_or_must_reenqueue());
-  return {prev};
-}
-
-bool JobTaskSourceNew::State::ExitWillRunTask(bool saturated) {
-  uint32_t bits_to_set = kOutsideWillRunTaskOrMustReenqueueMask;
-  if (!saturated) {
-    // If the task source is not saturated, it will be re-enqueued.
-    bits_to_set |= kQueuedMask;
-  }
-
-  // Acquire paired with Release in `ShouldQueueUponCapacityIncrease()` or
-  // `WillReenqueue()` so that anything that runs after clearing
-  // `kOutsideWillRunTaskOrMustReenqueueMask` sees max concurrency changes
-  // applied before setting it.
-  Value prev{value_.fetch_or(bits_to_set, std::memory_order_acquire)};
-
-  // `kQueuedMask` and `kOutsideWillRunTaskOrMustReenqueueMask` were cleared by
-  // `EnterWillRunTask()`. Since then, they may have *both* been set by
-  //  `ShouldQueueUponCapacityIncrease()` or `WillReenqueue()`.
-  CHECK_EQ(prev.queued(), prev.outside_will_run_task_or_must_reenqueue());
-
-  return prev.outside_will_run_task_or_must_reenqueue();
-}
-
-bool JobTaskSourceNew::State::WillReenqueue() {
-  // Release paired with Acquire in `ExitWillRunTask()`, see comment there.
-  Value prev{
-      value_.fetch_or(kQueuedMask | kOutsideWillRunTaskOrMustReenqueueMask,
-                      std::memory_order_release)};
-  return prev.outside_will_run_task_or_must_reenqueue();
-}
-
-JobTaskSourceNew::State::Value JobTaskSourceNew::State::Load() const {
+JobTaskSource::State::Value JobTaskSource::State::Load() const {
   return {value_.load(std::memory_order_relaxed)};
 }
 
-JobTaskSourceNew::JobTaskSourceNew(
-    const Location& from_here,
-    const TaskTraits& traits,
-    RepeatingCallback<void(JobDelegate*)> worker_task,
-    MaxConcurrencyCallback max_concurrency_callback,
-    PooledTaskRunnerDelegate* delegate)
-    : JobTaskSource(traits, TaskSourceExecutionMode::kJob),
+JobTaskSource::JoinFlag::JoinFlag() = default;
+JobTaskSource::JoinFlag::~JoinFlag() = default;
+
+void JobTaskSource::JoinFlag::Reset() {
+  value_.store(kNotWaiting, std::memory_order_relaxed);
+}
+
+void JobTaskSource::JoinFlag::SetWaiting() {
+  value_.store(kWaitingForWorkerToYield, std::memory_order_relaxed);
+}
+
+bool JobTaskSource::JoinFlag::ShouldWorkerYield() {
+  // The fetch_and() sets the state to kWaitingForWorkerToSignal if it was
+  // previously kWaitingForWorkerToYield, otherwise it leaves it unchanged.
+  return value_.fetch_and(kWaitingForWorkerToSignal,
+                          std::memory_order_relaxed) ==
+         kWaitingForWorkerToYield;
+}
+
+bool JobTaskSource::JoinFlag::ShouldWorkerSignal() {
+  return value_.exchange(kNotWaiting, std::memory_order_relaxed) != kNotWaiting;
+}
+
+JobTaskSource::JobTaskSource(const Location& from_here,
+                             const TaskTraits& traits,
+                             RepeatingCallback<void(JobDelegate*)> worker_task,
+                             MaxConcurrencyCallback max_concurrency_callback,
+                             PooledTaskRunnerDelegate* delegate)
+    : TaskSource(traits, TaskSourceExecutionMode::kJob),
       max_concurrency_callback_(std::move(max_concurrency_callback)),
       worker_task_(std::move(worker_task)),
       primary_task_(base::BindRepeating(
-          [](JobTaskSourceNew* self) {
+          [](JobTaskSource* self) {
             CheckedLock::AssertNoLockHeldOnCurrentThread();
             // Each worker task has its own delegate with associated state.
             JobDelegate job_delegate{self, self->delegate_};
@@ -143,21 +108,18 @@
       delegate_(delegate) {
   DCHECK(delegate_);
   task_metadata_.sequence_num = -1;
-  // Prevent wait on `join_event_` from triggering a ScopedBlockingCall as this
-  // would acquire `ThreadGroup::lock_` and cause lock inversion.
-  join_event_.declare_only_used_while_idle();
 }
 
-JobTaskSourceNew::~JobTaskSourceNew() {
+JobTaskSource::~JobTaskSource() {
   // Make sure there's no outstanding active run operation left.
   DCHECK_EQ(state_.Load().worker_count(), 0U);
 }
 
-ExecutionEnvironment JobTaskSourceNew::GetExecutionEnvironment() {
+ExecutionEnvironment JobTaskSource::GetExecutionEnvironment() {
   return {SequenceToken::Create()};
 }
 
-void JobTaskSourceNew::WillEnqueue(int sequence_num, TaskAnnotator& annotator) {
+void JobTaskSource::WillEnqueue(int sequence_num, TaskAnnotator& annotator) {
   if (task_metadata_.sequence_num != -1) {
     // WillEnqueue() was already called.
     return;
@@ -166,17 +128,17 @@
   annotator.WillQueueTask("ThreadPool_PostJob", &task_metadata_);
 }
 
-bool JobTaskSourceNew::WillJoin() {
-  // Increment worker count to indicate that this thread participates.
-  State::Value state_before_add;
-  {
-    CheckedAutoLock auto_lock(state_.increment_worker_count_lock());
-    state_before_add = state_.IncrementWorkerCount();
-  }
+bool JobTaskSource::WillJoin() {
+  TRACE_EVENT0("base", "Job.WaitForParticipationOpportunity");
+  CheckedAutoLock auto_lock(worker_lock_);
+  DCHECK(!worker_released_condition_);  // This may only be called once.
+  worker_released_condition_ = worker_lock_.CreateConditionVariable();
+  // Prevent wait from triggering a ScopedBlockingCall as this would cause
+  // |ThreadGroup::lock_| to be acquired, causing lock inversion.
+  worker_released_condition_->declare_only_used_while_idle();
+  const auto state_before_add = state_.IncrementWorkerCount();
 
-  // Return when the job is canceled or the (newly incremented) worker count is
-  // below or equal to max concurrency.
-  if (!state_before_add.canceled() &&
+  if (!state_before_add.is_canceled() &&
       state_before_add.worker_count() <
           GetMaxConcurrency(state_before_add.worker_count())) {
     return true;
@@ -184,136 +146,116 @@
   return WaitForParticipationOpportunity();
 }
 
-bool JobTaskSourceNew::RunJoinTask() {
-  {
-    TRACE_EVENT0("base", "Job.JoinParticipates");
-    JobDelegate job_delegate{this, nullptr};
-    worker_task_.Run(&job_delegate);
-  }
+bool JobTaskSource::RunJoinTask() {
+  JobDelegate job_delegate{this, nullptr};
+  worker_task_.Run(&job_delegate);
 
-  const auto state = state_.Load();
+  // It is safe to read |state_| without a lock since this variable is atomic
+  // and the call to GetMaxConcurrency() is used for a best effort early exit.
+  // Stale values will only cause WaitForParticipationOpportunity() to be
+  // called.
+  const auto state = TS_UNCHECKED_READ(state_).Load();
   // The condition is slightly different from the one in WillJoin() since we're
   // using |state| that was already incremented to include the joining thread.
-  if (!state.canceled() &&
+  if (!state.is_canceled() &&
       state.worker_count() <= GetMaxConcurrency(state.worker_count() - 1)) {
     return true;
   }
 
+  TRACE_EVENT0("base", "Job.WaitForParticipationOpportunity");
+  CheckedAutoLock auto_lock(worker_lock_);
   return WaitForParticipationOpportunity();
 }
 
-void JobTaskSourceNew::Cancel(TaskSource::Transaction* transaction) {
+void JobTaskSource::Cancel(TaskSource::Transaction* transaction) {
   // Sets the kCanceledMask bit on |state_| so that further calls to
   // WillRunTask() never succeed. std::memory_order_relaxed without a lock is
   // safe because this task source never needs to be re-enqueued after Cancel().
-  state_.Cancel();
+  TS_UNCHECKED_READ(state_).Cancel();
 }
 
-bool JobTaskSourceNew::WaitForParticipationOpportunity() {
-  TRACE_EVENT0("base", "Job.WaitForParticipationOpportunity");
+// EXCLUSIVE_LOCK_REQUIRED(worker_lock_)
+bool JobTaskSource::WaitForParticipationOpportunity() {
+  DCHECK(!join_flag_.IsWaiting());
+
+  // std::memory_order_relaxed is sufficient because no other state is
+  // synchronized with |state_| outside of |lock_|.
+  auto state = state_.Load();
+  // |worker_count - 1| to exclude the joining thread which is not active.
+  size_t max_concurrency = GetMaxConcurrency(state.worker_count() - 1);
 
   // Wait until either:
-  //  A) `worker_count` <= "max concurrency" and state is not canceled.
-  //  B) All other workers returned and `worker_count` is 1.
-  for (;;) {
-    auto state = state_.RequestSignalJoin();
+  //  A) |worker_count| is below or equal to max concurrency and state is not
+  //  canceled.
+  //  B) All other workers returned and |worker_count| is 1.
+  while (!((state.worker_count() <= max_concurrency && !state.is_canceled()) ||
+           state.worker_count() == 1)) {
+    // std::memory_order_relaxed is sufficient because no other state is
+    // synchronized with |join_flag_| outside of |lock_|.
+    join_flag_.SetWaiting();
 
-    size_t max_concurrency = GetMaxConcurrency(state.worker_count() - 1);
-
-    // Case A:
-    if (state.worker_count() <= max_concurrency && !state.canceled()) {
-      state_.FetchAndResetRequestSignalJoin();
-      return true;
-    }
-
-    // Case B:
-    // Only the joining thread remains.
-    if (state.worker_count() == 1U) {
-      DCHECK(state.canceled() || max_concurrency == 0U);
-      // WillRunTask() can run concurrently with this. Synchronize with it via a
-      // lock to guarantee that the ordering is one of these 2 options:
-      // 1. WillRunTask is first. It increments worker count. The condition
-      //    below detects that worker count is no longer 1 and we loop again.
-      // 2. This runs first. It cancels the job. WillRunTask returns
-      //    RunStatus::kDisallowed and doesn't increment the worker count.
-      // We definitely don't want this 3rd option (made impossible by the lock):
-      // 3. WillRunTask() observes that the job is not canceled. This observes
-      //    that the worker count is 1 and returns. JobHandle::Join returns and
-      //    its owner deletes state needed by the worker task. WillRunTask()
-      //    increments the worker count and the worker task stats running -->
-      //    use-after-free.
-      CheckedAutoLock auto_lock(state_.increment_worker_count_lock());
-
-      if (state_.Load().worker_count() != 1U) {
-        continue;
-      }
-
-      state_.Cancel();
-      state_.FetchAndResetRequestSignalJoin();
-      state_.DecrementWorkerCount();
-      return false;
-    }
-
-    join_event_.Wait();
+    // To avoid unnecessarily waiting, if either condition A) or B) change
+    // |lock_| is taken and |worker_released_condition_| signaled if necessary:
+    // 1- In DidProcessTask(), after worker count is decremented.
+    // 2- In NotifyConcurrencyIncrease(), following a max_concurrency increase.
+    worker_released_condition_->Wait();
+    state = state_.Load();
+    // |worker_count - 1| to exclude the joining thread which is not active.
+    max_concurrency = GetMaxConcurrency(state.worker_count() - 1);
   }
+  // It's possible though unlikely that the joining thread got a participation
+  // opportunity without a worker signaling.
+  join_flag_.Reset();
+
+  // Case A:
+  if (state.worker_count() <= max_concurrency && !state.is_canceled()) {
+    return true;
+  }
+  // Case B:
+  // Only the joining thread remains.
+  DCHECK_EQ(state.worker_count(), 1U);
+  DCHECK(state.is_canceled() || max_concurrency == 0U);
+  state_.DecrementWorkerCount();
+  // Prevent subsequent accesses to user callbacks.
+  state_.Cancel();
+  return false;
 }
 
-TaskSource::RunStatus JobTaskSourceNew::WillRunTask() {
-  // The lock below prevents a race described in Case B of
-  // `WaitForParticipationOpportunity()`.
-  CheckedAutoLock auto_lock(state_.increment_worker_count_lock());
+TaskSource::RunStatus JobTaskSource::WillRunTask() {
+  CheckedAutoLock auto_lock(worker_lock_);
+  auto state_before_add = state_.Load();
 
-  for (;;) {
-    auto prev_state = state_.EnterWillRunTask();
-
-    // Don't allow this worker to run the task if either:
-    //   A) Job was cancelled.
-    //   B) `worker_count` is already at `max_concurrency`.
-    //   C) `max_concurrency` was lowered below or to `worker_count`.
-
-    // Case A:
-    if (prev_state.canceled()) {
-      state_.ExitWillRunTask(/* saturated=*/true);
-      return RunStatus::kDisallowed;
-    }
-
-    const size_t worker_count_before_increment = prev_state.worker_count();
-    const size_t max_concurrency =
-        GetMaxConcurrency(worker_count_before_increment);
-
-    if (worker_count_before_increment < max_concurrency) {
-      prev_state = state_.IncrementWorkerCount();
-      // Worker count may have been decremented since it was read, but not
-      // incremented, due to the lock.
-      CHECK_LE(prev_state.worker_count(), worker_count_before_increment);
-      bool saturated = max_concurrency == (worker_count_before_increment + 1);
-      bool concurrency_increased_during_will_run_task =
-          state_.ExitWillRunTask(saturated);
-
-      if (saturated && !concurrency_increased_during_will_run_task) {
-        return RunStatus::kAllowedSaturated;
-      }
-
-      return RunStatus::kAllowedNotSaturated;
-    }
-
-    // Case B or C:
-    bool concurrency_increased_during_will_run_task =
-        state_.ExitWillRunTask(/* saturated=*/true);
-    if (!concurrency_increased_during_will_run_task) {
-      return RunStatus::kDisallowed;
-    }
-
-    // If concurrency increased during `WillRunTask()`, loop again to
-    // re-evaluate the `RunStatus`.
+  // Don't allow this worker to run the task if either:
+  //   A) |state_| was canceled.
+  //   B) |worker_count| is already at |max_concurrency|.
+  //   C) |max_concurrency| was lowered below or to |worker_count|.
+  // Case A:
+  if (state_before_add.is_canceled()) {
+    return RunStatus::kDisallowed;
   }
+
+  const size_t max_concurrency =
+      GetMaxConcurrency(state_before_add.worker_count());
+  if (state_before_add.worker_count() < max_concurrency) {
+    state_before_add = state_.IncrementWorkerCount();
+  }
+  const size_t worker_count_before_add = state_before_add.worker_count();
+  // Case B) or C):
+  if (worker_count_before_add >= max_concurrency) {
+    return RunStatus::kDisallowed;
+  }
+
+  DCHECK_LT(worker_count_before_add, max_concurrency);
+  return max_concurrency == worker_count_before_add + 1
+             ? RunStatus::kAllowedSaturated
+             : RunStatus::kAllowedNotSaturated;
 }
 
-size_t JobTaskSourceNew::GetRemainingConcurrency() const {
+size_t JobTaskSource::GetRemainingConcurrency() const {
   // It is safe to read |state_| without a lock since this variable is atomic,
   // and no other state is synchronized with GetRemainingConcurrency().
-  const auto state = state_.Load();
-  if (state.canceled()) {
+  const auto state = TS_UNCHECKED_READ(state_).Load();
+  if (state.is_canceled()) {
     return 0;
   }
   const size_t max_concurrency = GetMaxConcurrency(state.worker_count());
@@ -323,54 +265,52 @@
   return max_concurrency - state.worker_count();
 }
 
-bool JobTaskSourceNew::IsActive() const {
+bool JobTaskSource::IsActive() const {
+  CheckedAutoLock auto_lock(worker_lock_);
   auto state = state_.Load();
   return GetMaxConcurrency(state.worker_count()) != 0 ||
          state.worker_count() != 0;
 }
 
-size_t JobTaskSourceNew::GetWorkerCount() const {
-  return state_.Load().worker_count();
+size_t JobTaskSource::GetWorkerCount() const {
+  return TS_UNCHECKED_READ(state_).Load().worker_count();
 }
 
-bool JobTaskSourceNew::NotifyConcurrencyIncrease() {
-  const auto state = state_.Load();
-
-  // No need to signal the joining thread of re-enqueue if canceled.
-  if (state.canceled()) {
-    return true;
+void JobTaskSource::NotifyConcurrencyIncrease() {
+  // Avoid unnecessary locks when NotifyConcurrencyIncrease() is spuriously
+  // called.
+  if (GetRemainingConcurrency() == 0) {
+    return;
   }
 
-  const auto worker_count = state.worker_count();
-  const auto max_concurrency = GetMaxConcurrency(worker_count);
-
-  // Signal the joining thread if there is a request to do so and there is room
-  // for the joining thread to participate.
-  if (worker_count <= max_concurrency &&
-      state_.FetchAndResetRequestSignalJoin()) {
-    join_event_.Signal();
+  {
+    // Lock is taken to access |join_flag_| below and signal
+    // |worker_released_condition_|.
+    CheckedAutoLock auto_lock(worker_lock_);
+    if (join_flag_.ShouldWorkerSignal()) {
+      worker_released_condition_->Signal();
+    }
   }
 
-  // The job should be queued if the max concurrency isn't reached and it's not
-  // already queued.
-  if (worker_count < max_concurrency &&
-      state_.ShouldQueueUponCapacityIncrease()) {
-    return delegate_->EnqueueJobTaskSource(this);
-  }
-
-  return true;
+  // Make sure the task source is in the queue if not already.
+  // Caveat: it's possible but unlikely that the task source has already reached
+  // its intended concurrency and doesn't need to be enqueued if there
+  // previously were too many worker. For simplicity, the task source is always
+  // enqueued and will get discarded if already saturated when it is popped from
+  // the priority queue.
+  delegate_->EnqueueJobTaskSource(this);
 }
 
-size_t JobTaskSourceNew::GetMaxConcurrency() const {
-  return GetMaxConcurrency(state_.Load().worker_count());
+size_t JobTaskSource::GetMaxConcurrency() const {
+  return GetMaxConcurrency(TS_UNCHECKED_READ(state_).Load().worker_count());
 }
 
-size_t JobTaskSourceNew::GetMaxConcurrency(size_t worker_count) const {
+size_t JobTaskSource::GetMaxConcurrency(size_t worker_count) const {
   return std::min(max_concurrency_callback_.Run(worker_count),
                   kMaxWorkersPerJob);
 }
 
-uint8_t JobTaskSourceNew::AcquireTaskId() {
+uint8_t JobTaskSource::AcquireTaskId() {
   static_assert(kMaxWorkersPerJob <= sizeof(assigned_task_ids_) * 8,
                 "TaskId bitfield isn't big enough to fit kMaxWorkersPerJob.");
   uint32_t assigned_task_ids =
@@ -391,91 +331,83 @@
   return static_cast<uint8_t>(task_id);
 }
 
-void JobTaskSourceNew::ReleaseTaskId(uint8_t task_id) {
+void JobTaskSource::ReleaseTaskId(uint8_t task_id) {
   // memory_order_release to match AcquireTaskId().
   uint32_t previous_task_ids = assigned_task_ids_.fetch_and(
       ~(uint32_t(1) << task_id), std::memory_order_release);
   DCHECK(previous_task_ids & (uint32_t(1) << task_id));
 }
 
-bool JobTaskSourceNew::ShouldYield() {
-  // It's safe to read `state_` without a lock because it's atomic, keeping in
-  // mind that threads may not immediately see the new value when it's updated.
-  return state_.Load().canceled();
+bool JobTaskSource::ShouldYield() {
+  // It is safe to read |join_flag_| and |state_| without a lock since these
+  // variables are atomic, keeping in mind that threads may not immediately see
+  // the new value when it is updated.
+  return TS_UNCHECKED_READ(join_flag_).ShouldWorkerYield() ||
+         TS_UNCHECKED_READ(state_).Load().is_canceled();
 }
 
-PooledTaskRunnerDelegate* JobTaskSourceNew::GetDelegate() const {
-  return delegate_;
-}
-
-Task JobTaskSourceNew::TakeTask(TaskSource::Transaction* transaction) {
+Task JobTaskSource::TakeTask(TaskSource::Transaction* transaction) {
   // JobTaskSource members are not lock-protected so no need to acquire a lock
   // if |transaction| is nullptr.
-  DCHECK_GT(state_.Load().worker_count(), 0U);
+  DCHECK_GT(TS_UNCHECKED_READ(state_).Load().worker_count(), 0U);
   DCHECK(primary_task_);
   return {task_metadata_, primary_task_};
 }
 
-bool JobTaskSourceNew::DidProcessTask(
-    TaskSource::Transaction* /*transaction*/) {
-  auto state = state_.Load();
-  size_t worker_count_excluding_this = state.worker_count() - 1;
+bool JobTaskSource::DidProcessTask(TaskSource::Transaction* /*transaction*/) {
+  // Lock is needed to access |join_flag_| below and signal
+  // |worker_released_condition_|.
+  CheckedAutoLock auto_lock(worker_lock_);
+  const auto state_before_sub = state_.DecrementWorkerCount();
 
-  // Invoke the max concurrency callback before decrementing the worker count,
-  // because as soon as the worker count is decremented, JobHandle::Join() can
-  // return and state needed the callback may be deleted. Also, as an
-  // optimization, avoid invoking the callback if the job is canceled.
-  size_t max_concurrency =
-      state.canceled() ? 0U : GetMaxConcurrency(worker_count_excluding_this);
-
-  state = state_.DecrementWorkerCount();
-  if (state.signal_join() && state_.FetchAndResetRequestSignalJoin()) {
-    join_event_.Signal();
+  if (join_flag_.ShouldWorkerSignal()) {
+    worker_released_condition_->Signal();
   }
 
-  // A canceled task source should not be re-enqueued.
-  if (state.canceled()) {
+  // A canceled task source should never get re-enqueued.
+  if (state_before_sub.is_canceled()) {
     return false;
   }
 
-  // Re-enqueue if there isn't enough concurrency.
-  if (worker_count_excluding_this < max_concurrency) {
-    return state_.WillReenqueue();
-  }
+  DCHECK_GT(state_before_sub.worker_count(), 0U);
 
-  return false;
+  // Re-enqueue the TaskSource if the task ran and the worker count is below the
+  // max concurrency.
+  // |worker_count - 1| to exclude the returning thread.
+  return state_before_sub.worker_count() <=
+         GetMaxConcurrency(state_before_sub.worker_count() - 1);
 }
 
 // This is a no-op and should always return true.
-bool JobTaskSourceNew::WillReEnqueue(TimeTicks now,
-                                     TaskSource::Transaction* /*transaction*/) {
+bool JobTaskSource::WillReEnqueue(TimeTicks now,
+                                  TaskSource::Transaction* /*transaction*/) {
   return true;
 }
 
 // This is a no-op.
-bool JobTaskSourceNew::OnBecomeReady() {
+bool JobTaskSource::OnBecomeReady() {
   return false;
 }
 
-TaskSourceSortKey JobTaskSourceNew::GetSortKey() const {
+TaskSourceSortKey JobTaskSource::GetSortKey() const {
   return TaskSourceSortKey(priority_racy(), ready_time_,
-                           state_.Load().worker_count());
+                           TS_UNCHECKED_READ(state_).Load().worker_count());
 }
 
 // This function isn't expected to be called since a job is never delayed.
 // However, the class still needs to provide an override.
-TimeTicks JobTaskSourceNew::GetDelayedSortKey() const {
+TimeTicks JobTaskSource::GetDelayedSortKey() const {
   return TimeTicks();
 }
 
 // This function isn't expected to be called since a job is never delayed.
 // However, the class still needs to provide an override.
-bool JobTaskSourceNew::HasReadyTasks(TimeTicks now) const {
+bool JobTaskSource::HasReadyTasks(TimeTicks now) const {
   NOTREACHED();
   return true;
 }
 
-absl::optional<Task> JobTaskSourceNew::Clear(
+absl::optional<Task> JobTaskSource::Clear(
     TaskSource::Transaction* transaction) {
   Cancel();
 
@@ -485,4 +417,5 @@
   return absl::nullopt;
 }
 
-}  // namespace base::internal
+}  // namespace internal
+}  // namespace base
diff --git a/base/task/thread_pool/job_task_source.h b/base/task/thread_pool/job_task_source.h
index cd7c973c..107052c 100644
--- a/base/task/thread_pool/job_task_source.h
+++ b/base/task/thread_pool/job_task_source.h
@@ -8,63 +8,69 @@
 #include <stddef.h>
 
 #include <atomic>
-#include <cstdint>
+#include <limits>
+#include <memory>
 #include <utility>
 
 #include "base/base_export.h"
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
-#include "base/synchronization/waitable_event.h"
+#include "base/synchronization/condition_variable.h"
 #include "base/task/common/checked_lock.h"
 #include "base/task/common/task_annotator.h"
 #include "base/task/post_job.h"
 #include "base/task/task_traits.h"
-#include "base/task/thread_pool/job_task_source_interface.h"
 #include "base/task/thread_pool/task.h"
 #include "base/task/thread_pool/task_source.h"
 #include "base/task/thread_pool/task_source_sort_key.h"
 
-namespace base::internal {
+namespace base {
+namespace internal {
 
 class PooledTaskRunnerDelegate;
 
 // A JobTaskSource generates many Tasks from a single RepeatingClosure.
 //
 // Derived classes control the intended concurrency with GetMaxConcurrency().
-class BASE_EXPORT JobTaskSourceNew : public JobTaskSource {
+class BASE_EXPORT JobTaskSource : public TaskSource {
  public:
-  JobTaskSourceNew(const Location& from_here,
-                   const TaskTraits& traits,
-                   RepeatingCallback<void(JobDelegate*)> worker_task,
-                   MaxConcurrencyCallback max_concurrency_callback,
-                   PooledTaskRunnerDelegate* delegate);
-  JobTaskSourceNew(const JobTaskSource&) = delete;
-  JobTaskSourceNew& operator=(const JobTaskSourceNew&) = delete;
+  JobTaskSource(const Location& from_here,
+                const TaskTraits& traits,
+                RepeatingCallback<void(JobDelegate*)> worker_task,
+                MaxConcurrencyCallback max_concurrency_callback,
+                PooledTaskRunnerDelegate* delegate);
+  JobTaskSource(const JobTaskSource&) = delete;
+  JobTaskSource& operator=(const JobTaskSource&) = delete;
+
+  static JobHandle CreateJobHandle(
+      scoped_refptr<internal::JobTaskSource> task_source) {
+    return JobHandle(std::move(task_source));
+  }
 
   // Called before the task source is enqueued to initialize task metadata.
-  void WillEnqueue(int sequence_num, TaskAnnotator& annotator) override;
+  void WillEnqueue(int sequence_num, TaskAnnotator& annotator);
 
-  // Notifies this task source that max concurrency increased. Returns false iff
-  // there was an unsuccessful attempt to enqueue the task source.
-  bool NotifyConcurrencyIncrease() override;
+  // Notifies this task source that max concurrency was increased, and the
+  // number of worker should be adjusted.
+  void NotifyConcurrencyIncrease();
 
   // Informs this JobTaskSource that the current thread would like to join and
   // contribute to running |worker_task|. Returns true if the joining thread can
   // contribute (RunJoinTask() can be called), or false if joining was completed
   // and all other workers returned because either there's no work remaining or
   // Job was cancelled.
-  bool WillJoin() override;
+  bool WillJoin();
 
   // Contributes to running |worker_task| and returns true if the joining thread
   // can contribute again (RunJoinTask() can be called again), or false if
   // joining was completed and all other workers returned because either there's
   // no work remaining or Job was cancelled. This should be called only after
   // WillJoin() or RunJoinTask() previously returned true.
-  bool RunJoinTask() override;
+  bool RunJoinTask();
 
   // Cancels this JobTaskSource, causing all workers to yield and WillRunTask()
   // to return RunStatus::kDisallowed.
-  void Cancel(TaskSource::Transaction* transaction = nullptr) override;
+  void Cancel(TaskSource::Transaction* transaction = nullptr);
 
   // TaskSource:
   ExecutionEnvironment GetExecutionEnvironment() override;
@@ -73,40 +79,31 @@
   TimeTicks GetDelayedSortKey() const override;
   bool HasReadyTasks(TimeTicks now) const override;
 
-  bool IsActive() const override;
-  size_t GetWorkerCount() const override;
+  bool IsActive() const;
+  size_t GetWorkerCount() const;
 
   // Returns the maximum number of tasks from this TaskSource that can run
   // concurrently.
-  size_t GetMaxConcurrency() const override;
+  size_t GetMaxConcurrency() const;
 
-  uint8_t AcquireTaskId() override;
-  void ReleaseTaskId(uint8_t task_id) override;
+  uint8_t AcquireTaskId();
+  void ReleaseTaskId(uint8_t task_id);
 
   // Returns true if a worker should return from the worker task on the current
   // thread ASAP.
-  bool ShouldYield() override;
+  bool ShouldYield();
 
-  PooledTaskRunnerDelegate* GetDelegate() const override;
+  PooledTaskRunnerDelegate* delegate() const { return delegate_; }
 
  private:
-  // Atomic variable to track job state.
+  // Atomic internal state to track the number of workers running a task from
+  // this JobTaskSource and whether this JobTaskSource is canceled. All
+  // operations are performed with std::memory_order_relaxed as State is only
+  // ever modified under a lock or read atomically (optimistic read).
   class State {
    public:
-    // When set, the job is canceled.
-    static constexpr uint32_t kCanceledMask = 1 << 0;
-    // When set, the Join()'ing thread wants to be signaled when worker count
-    // is decremented or capacity is created by a max concurrency increase.
-    static constexpr uint32_t kSignalJoinMask = 1 << 1;
-    // When set, the job is queued. Note: The job may be queued when this is not
-    // set, see details in JobTaskSource::State::ExitWillRunTask().
-    static constexpr uint32_t kQueuedMask = 1 << 2;
-    // When set, WillRunTask() is not running *or* WillRunTask() is running and
-    // there was a request to keep the job queued (via
-    // `ShouldQueueUponCapacityIncrease()` or `WillReenqueue()`).
-    static constexpr uint32_t kOutsideWillRunTaskOrMustReenqueueMask = 1 << 3;
-    // Offset for the number of workers running the job.
-    static constexpr int kWorkerCountBitOffset = 4;
+    static constexpr uint32_t kCanceledMask = 1;
+    static constexpr int kWorkerCountBitOffset = 1;
     static constexpr uint32_t kWorkerCountIncrement = 1
                                                       << kWorkerCountBitOffset;
 
@@ -114,12 +111,8 @@
       uint8_t worker_count() const {
         return static_cast<uint8_t>(value >> kWorkerCountBitOffset);
       }
-      bool canceled() const { return value & kCanceledMask; }
-      bool signal_join() const { return value & kSignalJoinMask; }
-      bool queued() const { return value & kQueuedMask; }
-      bool outside_will_run_task_or_must_reenqueue() const {
-        return value & kOutsideWillRunTaskOrMustReenqueueMask;
-      }
+      // Returns true if canceled.
+      bool is_canceled() const { return value & kCanceledMask; }
 
       uint32_t value;
     };
@@ -127,71 +120,76 @@
     State();
     ~State();
 
-    // Sets as canceled. Returns the state before the operation.
+    // Sets as canceled. Returns the state
+    // before the operation.
     Value Cancel();
 
     // Increments the worker count by 1. Returns the state before the operation.
-    //
-    // This requires holding `increment_worker_count_lock()`, to allow
-    // WaitForParticipationOpportunity() to check worker count and apply changes
-    // with a guarantee that it hasn't been incremented in between (worker count
-    // could still be decremented while the lock is held).
-    Value IncrementWorkerCount()
-        EXCLUSIVE_LOCKS_REQUIRED(increment_worker_count_lock());
+    Value IncrementWorkerCount();
 
     // Decrements the worker count by 1. Returns the state before the operation.
     Value DecrementWorkerCount();
 
-    // Requests to signal the Join()'ing thread when worker count is
-    // decremented or capacity is created by increasing "max concurrency".
-    // Returns the state before the operation.
-    Value RequestSignalJoin();
-
-    // Returns whether the Join()'ing thread should be signaled when worker
-    // count is decremented or capacity is created by increasing "max
-    // concurrency". Resets the bit so that this won't return true until
-    // `RequestSignalJoin()` is called again.
-    bool FetchAndResetRequestSignalJoin();
-
-    // Indicates that max capacity was increased above the number of workers.
-    // Returns true iff the job should be queued.
-    bool ShouldQueueUponCapacityIncrease();
-
-    // Indicates that WillRunTask() was entered. Returns the previous state.
-    Value EnterWillRunTask();
-
-    // Indicates that WillRunTask() will exit. `saturated` is true iff
-    // `WillRunTask()` determined that max concurrency is reached. Returns true
-    // iff `ShouldQueueUponCapacityIncrease()` or `WillQueue()` was invoked
-    // since `EnterWillRunTask()`.
-    bool ExitWillRunTask(bool saturated);
-
-    // Indicates that `DidProcessTask()` decided to re-enqueue the job. If this
-    // returns false, the job shouldn't re-enqueue the job (another worker
-    // currently in `WillRunTask()` will request that it remains in the queue).
-    bool WillReenqueue();
-
     // Loads and returns the state.
     Value Load() const;
 
-    // Returns a lock that must be held to call `IncrementWorkerCount()`.
-    CheckedLock& increment_worker_count_lock() {
-      return increment_worker_count_lock_;
-    }
-
    private:
-    std::atomic<uint32_t> value_{kOutsideWillRunTaskOrMustReenqueueMask};
-    CheckedLock increment_worker_count_lock_{UniversalSuccessor()};
+    std::atomic<uint32_t> value_{0};
   };
 
-  ~JobTaskSourceNew() override;
+  // Atomic flag that indicates if the joining thread is currently waiting on
+  // another worker to yield or to signal.
+  class JoinFlag {
+   public:
+    static constexpr uint32_t kNotWaiting = 0;
+    static constexpr uint32_t kWaitingForWorkerToSignal = 1;
+    static constexpr uint32_t kWaitingForWorkerToYield = 3;
+    // kWaitingForWorkerToYield is 3 because the impl relies on the following
+    // property.
+    static_assert((kWaitingForWorkerToYield & kWaitingForWorkerToSignal) ==
+                      kWaitingForWorkerToSignal,
+                  "");
+
+    JoinFlag();
+    ~JoinFlag();
+
+    // Returns true if the status is not kNotWaiting, using
+    // std::memory_order_relaxed.
+    bool IsWaiting() {
+      return value_.load(std::memory_order_relaxed) != kNotWaiting;
+    }
+
+    // Resets the status as kNotWaiting  using std::memory_order_relaxed.
+    void Reset();
+
+    // Sets the status as kWaitingForWorkerToYield using
+    // std::memory_order_relaxed.
+    void SetWaiting();
+
+    // If the flag is kWaitingForWorkerToYield, returns true indicating that the
+    // worker should yield, and atomically updates to kWaitingForWorkerToSignal
+    // (using std::memory_order_relaxed) to ensure that a single worker yields
+    // in response to SetWaiting().
+    bool ShouldWorkerYield();
+
+    // If the flag is kWaiting*, returns true indicating that the worker should
+    // signal, and atomically updates to kNotWaiting (using
+    // std::memory_order_relaxed) to ensure that a single worker signals in
+    // response to SetWaiting().
+    bool ShouldWorkerSignal();
+
+   private:
+    std::atomic<uint32_t> value_{kNotWaiting};
+  };
+
+  ~JobTaskSource() override;
 
   // Called from the joining thread. Waits for the worker count to be below or
-  // equal to max concurrency (may happen when "max concurrency" increases or
-  // the worker count is decremented). Returns true if the joining thread should
-  // run a task, or false if joining was completed and all other workers
-  // returned because either there's no work remaining or Job was cancelled.
-  bool WaitForParticipationOpportunity();
+  // equal to max concurrency (will happen when a worker calls
+  // DidProcessTask()). Returns true if the joining thread should run a task, or
+  // false if joining was completed and all other workers returned because
+  // either there's no work remaining or Job was cancelled.
+  bool WaitForParticipationOpportunity() EXCLUSIVE_LOCKS_REQUIRED(worker_lock_);
 
   size_t GetMaxConcurrency(size_t worker_count) const;
 
@@ -204,11 +202,18 @@
                      TaskSource::Transaction* transaction) override;
   bool OnBecomeReady() override;
 
-  State state_;
+  // Synchronizes access to workers state.
+  mutable CheckedLock worker_lock_{UniversalSuccessor()};
 
-  // Signaled when the joining thread wants to particpate and capacity is
-  // created by increasing "max concurrency" or decrementing the worker count.
-  WaitableEvent join_event_{WaitableEvent::ResetPolicy::AUTOMATIC};
+  // Current atomic state (atomic despite the lock to allow optimistic reads
+  // and cancellation without the lock).
+  State state_ GUARDED_BY(worker_lock_);
+  // Normally, |join_flag_| is protected by |lock_|, except in ShouldYield()
+  // hence the use of atomics.
+  JoinFlag join_flag_ GUARDED_BY(worker_lock_);
+  // Signaled when |join_flag_| is kWaiting* and a worker returns.
+  std::unique_ptr<ConditionVariable> worker_released_condition_
+      GUARDED_BY(worker_lock_);
 
   std::atomic<uint32_t> assigned_task_ids_{0};
 
@@ -225,6 +230,7 @@
   raw_ptr<PooledTaskRunnerDelegate, LeakedDanglingUntriaged> delegate_;
 };
 
-}  // namespace base::internal
+}  // namespace internal
+}  // namespace base
 
 #endif  // BASE_TASK_THREAD_POOL_JOB_TASK_SOURCE_H_
diff --git a/base/task/thread_pool/job_task_source_interface.h b/base/task/thread_pool/job_task_source_interface.h
deleted file mode 100644
index db65228..0000000
--- a/base/task/thread_pool/job_task_source_interface.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_TASK_THREAD_POOL_JOB_TASK_SOURCE_INTERFACE_H_
-#define BASE_TASK_THREAD_POOL_JOB_TASK_SOURCE_INTERFACE_H_
-
-#include "base/task/post_job.h"
-#include "base/task/thread_pool/task_source.h"
-
-namespace base {
-
-class TaskAnnotator;
-
-namespace internal {
-
-class PooledTaskRunnerDelegate;
-
-// Interface for a job task source, to facilitate using either JobTaskSourceNew
-// or JobTaskSourceOld depending on the UseNewJobImplementation feature state.
-class BASE_EXPORT JobTaskSource : public TaskSource {
- public:
-  JobTaskSource(const JobTaskSource&) = delete;
-  JobTaskSource& operator=(const JobTaskSource&) = delete;
-
-  static JobHandle CreateJobHandle(
-      scoped_refptr<internal::JobTaskSource> task_source) {
-    return JobHandle(std::move(task_source));
-  }
-
-  virtual void WillEnqueue(int sequence_num, TaskAnnotator& annotator) = 0;
-  virtual bool NotifyConcurrencyIncrease() = 0;
-  virtual bool WillJoin() = 0;
-  virtual bool RunJoinTask() = 0;
-  virtual void Cancel(TaskSource::Transaction* transaction = nullptr) = 0;
-  virtual bool IsActive() const = 0;
-  virtual size_t GetWorkerCount() const = 0;
-  virtual size_t GetMaxConcurrency() const = 0;
-  virtual uint8_t AcquireTaskId() = 0;
-  virtual void ReleaseTaskId(uint8_t task_id) = 0;
-  virtual bool ShouldYield() = 0;
-  virtual PooledTaskRunnerDelegate* GetDelegate() const = 0;
-
- protected:
-  JobTaskSource(const TaskTraits& traits,
-                TaskSourceExecutionMode execution_mode)
-      : TaskSource(traits, execution_mode) {}
-  ~JobTaskSource() override = default;
-};
-
-}  // namespace internal
-}  // namespace base
-
-#endif  // BASE_TASK_THREAD_POOL_JOB_TASK_SOURCE_INTERFACE_H_
diff --git a/base/task/thread_pool/job_task_source_old.cc b/base/task/thread_pool/job_task_source_old.cc
deleted file mode 100644
index 5fb4856b..0000000
--- a/base/task/thread_pool/job_task_source_old.cc
+++ /dev/null
@@ -1,427 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "base/task/thread_pool/job_task_source_old.h"
-
-#include <bit>
-#include <type_traits>
-#include <utility>
-
-#include "base/check_op.h"
-#include "base/functional/bind.h"
-#include "base/functional/callback_helpers.h"
-#include "base/memory/ptr_util.h"
-#include "base/notreached.h"
-#include "base/task/common/checked_lock.h"
-#include "base/task/task_features.h"
-#include "base/task/thread_pool/pooled_task_runner_delegate.h"
-#include "base/template_util.h"
-#include "base/threading/thread_restrictions.h"
-#include "base/time/time.h"
-#include "base/time/time_override.h"
-#include "base/trace_event/base_tracing.h"
-
-namespace base {
-namespace internal {
-
-namespace {
-
-// Capped to allow assigning task_ids from a bitfield.
-constexpr size_t kMaxWorkersPerJob = 32;
-static_assert(
-    kMaxWorkersPerJob <=
-        std::numeric_limits<
-            std::invoke_result<decltype(&JobDelegate::GetTaskId),
-                               JobDelegate>::type>::max(),
-    "AcquireTaskId return type isn't big enough to fit kMaxWorkersPerJob");
-
-}  // namespace
-
-JobTaskSourceOld::State::State() = default;
-JobTaskSourceOld::State::~State() = default;
-
-JobTaskSourceOld::State::Value JobTaskSourceOld::State::Cancel() {
-  return {value_.fetch_or(kCanceledMask, std::memory_order_relaxed)};
-}
-
-JobTaskSourceOld::State::Value JobTaskSourceOld::State::DecrementWorkerCount() {
-  const uint32_t value_before_sub =
-      value_.fetch_sub(kWorkerCountIncrement, std::memory_order_relaxed);
-  DCHECK((value_before_sub >> kWorkerCountBitOffset) > 0);
-  return {value_before_sub};
-}
-
-JobTaskSourceOld::State::Value JobTaskSourceOld::State::IncrementWorkerCount() {
-  uint32_t value_before_add =
-      value_.fetch_add(kWorkerCountIncrement, std::memory_order_relaxed);
-  // The worker count must not overflow a uint8_t.
-  DCHECK((value_before_add >> kWorkerCountBitOffset) < ((1 << 8) - 1));
-  return {value_before_add};
-}
-
-JobTaskSourceOld::State::Value JobTaskSourceOld::State::Load() const {
-  return {value_.load(std::memory_order_relaxed)};
-}
-
-JobTaskSourceOld::JoinFlag::JoinFlag() = default;
-JobTaskSourceOld::JoinFlag::~JoinFlag() = default;
-
-void JobTaskSourceOld::JoinFlag::Reset() {
-  value_.store(kNotWaiting, std::memory_order_relaxed);
-}
-
-void JobTaskSourceOld::JoinFlag::SetWaiting() {
-  value_.store(kWaitingForWorkerToYield, std::memory_order_relaxed);
-}
-
-bool JobTaskSourceOld::JoinFlag::ShouldWorkerYield() {
-  // The fetch_and() sets the state to kWaitingForWorkerToSignal if it was
-  // previously kWaitingForWorkerToYield, otherwise it leaves it unchanged.
-  return value_.fetch_and(kWaitingForWorkerToSignal,
-                          std::memory_order_relaxed) ==
-         kWaitingForWorkerToYield;
-}
-
-bool JobTaskSourceOld::JoinFlag::ShouldWorkerSignal() {
-  return value_.exchange(kNotWaiting, std::memory_order_relaxed) != kNotWaiting;
-}
-
-JobTaskSourceOld::JobTaskSourceOld(
-    const Location& from_here,
-    const TaskTraits& traits,
-    RepeatingCallback<void(JobDelegate*)> worker_task,
-    MaxConcurrencyCallback max_concurrency_callback,
-    PooledTaskRunnerDelegate* delegate)
-    : JobTaskSource(traits, TaskSourceExecutionMode::kJob),
-      max_concurrency_callback_(std::move(max_concurrency_callback)),
-      worker_task_(std::move(worker_task)),
-      primary_task_(base::BindRepeating(
-          [](JobTaskSourceOld* self) {
-            CheckedLock::AssertNoLockHeldOnCurrentThread();
-            // Each worker task has its own delegate with associated state.
-            JobDelegate job_delegate{self, self->delegate_};
-            self->worker_task_.Run(&job_delegate);
-          },
-          base::Unretained(this))),
-      task_metadata_(from_here),
-      ready_time_(TimeTicks::Now()),
-      delegate_(delegate) {
-  DCHECK(delegate_);
-}
-
-JobTaskSourceOld::~JobTaskSourceOld() {
-  // Make sure there's no outstanding active run operation left.
-  DCHECK_EQ(state_.Load().worker_count(), 0U);
-}
-
-ExecutionEnvironment JobTaskSourceOld::GetExecutionEnvironment() {
-  return {SequenceToken::Create()};
-}
-
-void JobTaskSourceOld::WillEnqueue(int sequence_num, TaskAnnotator& annotator) {
-  if (task_metadata_.sequence_num != -1) {
-    // WillEnqueue() was already called.
-    return;
-  }
-  task_metadata_.sequence_num = sequence_num;
-  annotator.WillQueueTask("ThreadPool_PostJob", &task_metadata_);
-}
-
-bool JobTaskSourceOld::WillJoin() {
-  TRACE_EVENT0("base", "Job.WaitForParticipationOpportunity");
-  CheckedAutoLock auto_lock(worker_lock_);
-  DCHECK(!worker_released_condition_);  // This may only be called once.
-  worker_released_condition_ = worker_lock_.CreateConditionVariable();
-  // Prevent wait from triggering a ScopedBlockingCall as this would cause
-  // |ThreadGroup::lock_| to be acquired, causing lock inversion.
-  worker_released_condition_->declare_only_used_while_idle();
-  const auto state_before_add = state_.IncrementWorkerCount();
-
-  if (!state_before_add.is_canceled() &&
-      state_before_add.worker_count() <
-          GetMaxConcurrency(state_before_add.worker_count())) {
-    return true;
-  }
-  return WaitForParticipationOpportunity();
-}
-
-bool JobTaskSourceOld::RunJoinTask() {
-  JobDelegate job_delegate{this, nullptr};
-  worker_task_.Run(&job_delegate);
-
-  // It is safe to read |state_| without a lock since this variable is atomic
-  // and the call to GetMaxConcurrency() is used for a best effort early exit.
-  // Stale values will only cause WaitForParticipationOpportunity() to be
-  // called.
-  const auto state = TS_UNCHECKED_READ(state_).Load();
-  // The condition is slightly different from the one in WillJoin() since we're
-  // using |state| that was already incremented to include the joining thread.
-  if (!state.is_canceled() &&
-      state.worker_count() <= GetMaxConcurrency(state.worker_count() - 1)) {
-    return true;
-  }
-
-  TRACE_EVENT0("base", "Job.WaitForParticipationOpportunity");
-  CheckedAutoLock auto_lock(worker_lock_);
-  return WaitForParticipationOpportunity();
-}
-
-void JobTaskSourceOld::Cancel(TaskSource::Transaction* transaction) {
-  // Sets the kCanceledMask bit on |state_| so that further calls to
-  // WillRunTask() never succeed. std::memory_order_relaxed without a lock is
-  // safe because this task source never needs to be re-enqueued after Cancel().
-  TS_UNCHECKED_READ(state_).Cancel();
-}
-
-// EXCLUSIVE_LOCK_REQUIRED(worker_lock_)
-bool JobTaskSourceOld::WaitForParticipationOpportunity() {
-  DCHECK(!join_flag_.IsWaiting());
-
-  // std::memory_order_relaxed is sufficient because no other state is
-  // synchronized with |state_| outside of |lock_|.
-  auto state = state_.Load();
-  // |worker_count - 1| to exclude the joining thread which is not active.
-  size_t max_concurrency = GetMaxConcurrency(state.worker_count() - 1);
-
-  // Wait until either:
-  //  A) |worker_count| is below or equal to max concurrency and state is not
-  //  canceled.
-  //  B) All other workers returned and |worker_count| is 1.
-  while (!((state.worker_count() <= max_concurrency && !state.is_canceled()) ||
-           state.worker_count() == 1)) {
-    // std::memory_order_relaxed is sufficient because no other state is
-    // synchronized with |join_flag_| outside of |lock_|.
-    join_flag_.SetWaiting();
-
-    // To avoid unnecessarily waiting, if either condition A) or B) change
-    // |lock_| is taken and |worker_released_condition_| signaled if necessary:
-    // 1- In DidProcessTask(), after worker count is decremented.
-    // 2- In NotifyConcurrencyIncrease(), following a max_concurrency increase.
-    worker_released_condition_->Wait();
-    state = state_.Load();
-    // |worker_count - 1| to exclude the joining thread which is not active.
-    max_concurrency = GetMaxConcurrency(state.worker_count() - 1);
-  }
-  // It's possible though unlikely that the joining thread got a participation
-  // opportunity without a worker signaling.
-  join_flag_.Reset();
-
-  // Case A:
-  if (state.worker_count() <= max_concurrency && !state.is_canceled()) {
-    return true;
-  }
-  // Case B:
-  // Only the joining thread remains.
-  DCHECK_EQ(state.worker_count(), 1U);
-  DCHECK(state.is_canceled() || max_concurrency == 0U);
-  state_.DecrementWorkerCount();
-  // Prevent subsequent accesses to user callbacks.
-  state_.Cancel();
-  return false;
-}
-
-TaskSource::RunStatus JobTaskSourceOld::WillRunTask() {
-  CheckedAutoLock auto_lock(worker_lock_);
-  auto state_before_add = state_.Load();
-
-  // Don't allow this worker to run the task if either:
-  //   A) |state_| was canceled.
-  //   B) |worker_count| is already at |max_concurrency|.
-  //   C) |max_concurrency| was lowered below or to |worker_count|.
-  // Case A:
-  if (state_before_add.is_canceled()) {
-    return RunStatus::kDisallowed;
-  }
-
-  const size_t max_concurrency =
-      GetMaxConcurrency(state_before_add.worker_count());
-  if (state_before_add.worker_count() < max_concurrency) {
-    state_before_add = state_.IncrementWorkerCount();
-  }
-  const size_t worker_count_before_add = state_before_add.worker_count();
-  // Case B) or C):
-  if (worker_count_before_add >= max_concurrency) {
-    return RunStatus::kDisallowed;
-  }
-
-  DCHECK_LT(worker_count_before_add, max_concurrency);
-  return max_concurrency == worker_count_before_add + 1
-             ? RunStatus::kAllowedSaturated
-             : RunStatus::kAllowedNotSaturated;
-}
-
-size_t JobTaskSourceOld::GetRemainingConcurrency() const {
-  // It is safe to read |state_| without a lock since this variable is atomic,
-  // and no other state is synchronized with GetRemainingConcurrency().
-  const auto state = TS_UNCHECKED_READ(state_).Load();
-  if (state.is_canceled()) {
-    return 0;
-  }
-  const size_t max_concurrency = GetMaxConcurrency(state.worker_count());
-  // Avoid underflows.
-  if (state.worker_count() > max_concurrency) {
-    return 0;
-  }
-  return max_concurrency - state.worker_count();
-}
-
-bool JobTaskSourceOld::IsActive() const {
-  CheckedAutoLock auto_lock(worker_lock_);
-  auto state = state_.Load();
-  return GetMaxConcurrency(state.worker_count()) != 0 ||
-         state.worker_count() != 0;
-}
-
-size_t JobTaskSourceOld::GetWorkerCount() const {
-  return TS_UNCHECKED_READ(state_).Load().worker_count();
-}
-
-bool JobTaskSourceOld::NotifyConcurrencyIncrease() {
-  // Avoid unnecessary locks when NotifyConcurrencyIncrease() is spuriously
-  // called.
-  if (GetRemainingConcurrency() == 0) {
-    return true;
-  }
-
-  {
-    // Lock is taken to access |join_flag_| below and signal
-    // |worker_released_condition_|.
-    CheckedAutoLock auto_lock(worker_lock_);
-    if (join_flag_.ShouldWorkerSignal()) {
-      worker_released_condition_->Signal();
-    }
-  }
-
-  // Make sure the task source is in the queue if not already.
-  // Caveat: it's possible but unlikely that the task source has already reached
-  // its intended concurrency and doesn't need to be enqueued if there
-  // previously were too many worker. For simplicity, the task source is always
-  // enqueued and will get discarded if already saturated when it is popped from
-  // the priority queue.
-  return delegate_->EnqueueJobTaskSource(this);
-}
-
-size_t JobTaskSourceOld::GetMaxConcurrency() const {
-  return GetMaxConcurrency(TS_UNCHECKED_READ(state_).Load().worker_count());
-}
-
-size_t JobTaskSourceOld::GetMaxConcurrency(size_t worker_count) const {
-  return std::min(max_concurrency_callback_.Run(worker_count),
-                  kMaxWorkersPerJob);
-}
-
-uint8_t JobTaskSourceOld::AcquireTaskId() {
-  static_assert(kMaxWorkersPerJob <= sizeof(assigned_task_ids_) * 8,
-                "TaskId bitfield isn't big enough to fit kMaxWorkersPerJob.");
-  uint32_t assigned_task_ids =
-      assigned_task_ids_.load(std::memory_order_relaxed);
-  uint32_t new_assigned_task_ids = 0;
-  int task_id = 0;
-  // memory_order_acquire on success, matched with memory_order_release in
-  // ReleaseTaskId() so that operations done by previous threads that had
-  // the same task_id become visible to the current thread.
-  do {
-    // Count trailing one bits. This is the id of the right-most 0-bit in
-    // |assigned_task_ids|.
-    task_id = std::countr_one(assigned_task_ids);
-    new_assigned_task_ids = assigned_task_ids | (uint32_t(1) << task_id);
-  } while (!assigned_task_ids_.compare_exchange_weak(
-      assigned_task_ids, new_assigned_task_ids, std::memory_order_acquire,
-      std::memory_order_relaxed));
-  return static_cast<uint8_t>(task_id);
-}
-
-void JobTaskSourceOld::ReleaseTaskId(uint8_t task_id) {
-  // memory_order_release to match AcquireTaskId().
-  uint32_t previous_task_ids = assigned_task_ids_.fetch_and(
-      ~(uint32_t(1) << task_id), std::memory_order_release);
-  DCHECK(previous_task_ids & (uint32_t(1) << task_id));
-}
-
-bool JobTaskSourceOld::ShouldYield() {
-  // It is safe to read |join_flag_| and |state_| without a lock since these
-  // variables are atomic, keeping in mind that threads may not immediately see
-  // the new value when it is updated.
-  return TS_UNCHECKED_READ(join_flag_).ShouldWorkerYield() ||
-         TS_UNCHECKED_READ(state_).Load().is_canceled();
-}
-
-PooledTaskRunnerDelegate* JobTaskSourceOld::GetDelegate() const {
-  return delegate_;
-}
-
-Task JobTaskSourceOld::TakeTask(TaskSource::Transaction* transaction) {
-  // JobTaskSource members are not lock-protected so no need to acquire a lock
-  // if |transaction| is nullptr.
-  DCHECK_GT(TS_UNCHECKED_READ(state_).Load().worker_count(), 0U);
-  DCHECK(primary_task_);
-  return {task_metadata_, primary_task_};
-}
-
-bool JobTaskSourceOld::DidProcessTask(
-    TaskSource::Transaction* /*transaction*/) {
-  // Lock is needed to access |join_flag_| below and signal
-  // |worker_released_condition_|.
-  CheckedAutoLock auto_lock(worker_lock_);
-  const auto state_before_sub = state_.DecrementWorkerCount();
-
-  if (join_flag_.ShouldWorkerSignal()) {
-    worker_released_condition_->Signal();
-  }
-
-  // A canceled task source should never get re-enqueued.
-  if (state_before_sub.is_canceled()) {
-    return false;
-  }
-
-  DCHECK_GT(state_before_sub.worker_count(), 0U);
-
-  // Re-enqueue the TaskSource if the task ran and the worker count is below the
-  // max concurrency.
-  // |worker_count - 1| to exclude the returning thread.
-  return state_before_sub.worker_count() <=
-         GetMaxConcurrency(state_before_sub.worker_count() - 1);
-}
-
-// This is a no-op and should always return true.
-bool JobTaskSourceOld::WillReEnqueue(TimeTicks now,
-                                     TaskSource::Transaction* /*transaction*/) {
-  return true;
-}
-
-// This is a no-op.
-bool JobTaskSourceOld::OnBecomeReady() {
-  return false;
-}
-
-TaskSourceSortKey JobTaskSourceOld::GetSortKey() const {
-  return TaskSourceSortKey(priority_racy(), ready_time_,
-                           TS_UNCHECKED_READ(state_).Load().worker_count());
-}
-
-// This function isn't expected to be called since a job is never delayed.
-// However, the class still needs to provide an override.
-TimeTicks JobTaskSourceOld::GetDelayedSortKey() const {
-  return TimeTicks();
-}
-
-// This function isn't expected to be called since a job is never delayed.
-// However, the class still needs to provide an override.
-bool JobTaskSourceOld::HasReadyTasks(TimeTicks now) const {
-  NOTREACHED();
-  return true;
-}
-
-absl::optional<Task> JobTaskSourceOld::Clear(
-    TaskSource::Transaction* transaction) {
-  Cancel();
-
-  // Nothing is cleared since other workers might still racily run tasks. For
-  // simplicity, the destructor will take care of it once all references are
-  // released.
-  return absl::nullopt;
-}
-
-}  // namespace internal
-}  // namespace base
diff --git a/base/task/thread_pool/job_task_source_old.h b/base/task/thread_pool/job_task_source_old.h
deleted file mode 100644
index efed0ae..0000000
--- a/base/task/thread_pool/job_task_source_old.h
+++ /dev/null
@@ -1,231 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_TASK_THREAD_POOL_JOB_TASK_SOURCE_OLD_H_
-#define BASE_TASK_THREAD_POOL_JOB_TASK_SOURCE_OLD_H_
-
-#include <stddef.h>
-
-#include <atomic>
-#include <limits>
-#include <memory>
-#include <utility>
-
-#include "base/base_export.h"
-#include "base/functional/callback.h"
-#include "base/memory/raw_ptr.h"
-#include "base/synchronization/condition_variable.h"
-#include "base/task/common/checked_lock.h"
-#include "base/task/post_job.h"
-#include "base/task/task_traits.h"
-#include "base/task/thread_pool/job_task_source_interface.h"
-#include "base/task/thread_pool/task.h"
-#include "base/task/thread_pool/task_source.h"
-#include "base/task/thread_pool/task_source_sort_key.h"
-
-namespace base {
-namespace internal {
-
-class PooledTaskRunnerDelegate;
-
-// A JobTaskSource generates many Tasks from a single RepeatingClosure.
-//
-// Derived classes control the intended concurrency with GetMaxConcurrency().
-class BASE_EXPORT JobTaskSourceOld : public JobTaskSource {
- public:
-  JobTaskSourceOld(const Location& from_here,
-                   const TaskTraits& traits,
-                   RepeatingCallback<void(JobDelegate*)> worker_task,
-                   MaxConcurrencyCallback max_concurrency_callback,
-                   PooledTaskRunnerDelegate* delegate);
-  JobTaskSourceOld(const JobTaskSource&) = delete;
-  JobTaskSourceOld& operator=(const JobTaskSourceOld&) = delete;
-
-  // Called before the task source is enqueued to initialize task metadata.
-  void WillEnqueue(int sequence_num, TaskAnnotator& annotator) override;
-
-  // Notifies this task source that max concurrency was increased, and the
-  // number of worker should be adjusted.
-  bool NotifyConcurrencyIncrease() override;
-
-  // Informs this JobTaskSource that the current thread would like to join and
-  // contribute to running |worker_task|. Returns true if the joining thread can
-  // contribute (RunJoinTask() can be called), or false if joining was completed
-  // and all other workers returned because either there's no work remaining or
-  // Job was cancelled.
-  bool WillJoin() override;
-
-  // Contributes to running |worker_task| and returns true if the joining thread
-  // can contribute again (RunJoinTask() can be called again), or false if
-  // joining was completed and all other workers returned because either there's
-  // no work remaining or Job was cancelled. This should be called only after
-  // WillJoin() or RunJoinTask() previously returned true.
-  bool RunJoinTask() override;
-
-  // Cancels this JobTaskSource, causing all workers to yield and WillRunTask()
-  // to return RunStatus::kDisallowed.
-  void Cancel(TaskSource::Transaction* transaction = nullptr) override;
-
-  // TaskSource:
-  ExecutionEnvironment GetExecutionEnvironment() override;
-  size_t GetRemainingConcurrency() const override;
-  TaskSourceSortKey GetSortKey() const override;
-  TimeTicks GetDelayedSortKey() const override;
-  bool HasReadyTasks(TimeTicks now) const override;
-
-  bool IsActive() const override;
-  size_t GetWorkerCount() const override;
-
-  // Returns the maximum number of tasks from this TaskSource that can run
-  // concurrently.
-  size_t GetMaxConcurrency() const override;
-
-  uint8_t AcquireTaskId() override;
-  void ReleaseTaskId(uint8_t task_id) override;
-
-  // Returns true if a worker should return from the worker task on the current
-  // thread ASAP.
-  bool ShouldYield() override;
-
-  PooledTaskRunnerDelegate* GetDelegate() const override;
-
- private:
-  // Atomic internal state to track the number of workers running a task from
-  // this JobTaskSource and whether this JobTaskSource is canceled. All
-  // operations are performed with std::memory_order_relaxed as State is only
-  // ever modified under a lock or read atomically (optimistic read).
-  class State {
-   public:
-    static constexpr uint32_t kCanceledMask = 1;
-    static constexpr int kWorkerCountBitOffset = 1;
-    static constexpr uint32_t kWorkerCountIncrement = 1
-                                                      << kWorkerCountBitOffset;
-
-    struct Value {
-      uint8_t worker_count() const {
-        return static_cast<uint8_t>(value >> kWorkerCountBitOffset);
-      }
-      // Returns true if canceled.
-      bool is_canceled() const { return value & kCanceledMask; }
-
-      uint32_t value;
-    };
-
-    State();
-    ~State();
-
-    // Sets as canceled. Returns the state
-    // before the operation.
-    Value Cancel();
-
-    // Increments the worker count by 1. Returns the state before the operation.
-    Value IncrementWorkerCount();
-
-    // Decrements the worker count by 1. Returns the state before the operation.
-    Value DecrementWorkerCount();
-
-    // Loads and returns the state.
-    Value Load() const;
-
-   private:
-    std::atomic<uint32_t> value_{0};
-  };
-
-  // Atomic flag that indicates if the joining thread is currently waiting on
-  // another worker to yield or to signal.
-  class JoinFlag {
-   public:
-    static constexpr uint32_t kNotWaiting = 0;
-    static constexpr uint32_t kWaitingForWorkerToSignal = 1;
-    static constexpr uint32_t kWaitingForWorkerToYield = 3;
-    // kWaitingForWorkerToYield is 3 because the impl relies on the following
-    // property.
-    static_assert((kWaitingForWorkerToYield & kWaitingForWorkerToSignal) ==
-                      kWaitingForWorkerToSignal,
-                  "");
-
-    JoinFlag();
-    ~JoinFlag();
-
-    // Returns true if the status is not kNotWaiting, using
-    // std::memory_order_relaxed.
-    bool IsWaiting() {
-      return value_.load(std::memory_order_relaxed) != kNotWaiting;
-    }
-
-    // Resets the status as kNotWaiting  using std::memory_order_relaxed.
-    void Reset();
-
-    // Sets the status as kWaitingForWorkerToYield using
-    // std::memory_order_relaxed.
-    void SetWaiting();
-
-    // If the flag is kWaitingForWorkerToYield, returns true indicating that the
-    // worker should yield, and atomically updates to kWaitingForWorkerToSignal
-    // (using std::memory_order_relaxed) to ensure that a single worker yields
-    // in response to SetWaiting().
-    bool ShouldWorkerYield();
-
-    // If the flag is kWaiting*, returns true indicating that the worker should
-    // signal, and atomically updates to kNotWaiting (using
-    // std::memory_order_relaxed) to ensure that a single worker signals in
-    // response to SetWaiting().
-    bool ShouldWorkerSignal();
-
-   private:
-    std::atomic<uint32_t> value_{kNotWaiting};
-  };
-
-  ~JobTaskSourceOld() override;
-
-  // Called from the joining thread. Waits for the worker count to be below or
-  // equal to max concurrency (will happen when a worker calls
-  // DidProcessTask()). Returns true if the joining thread should run a task, or
-  // false if joining was completed and all other workers returned because
-  // either there's no work remaining or Job was cancelled.
-  bool WaitForParticipationOpportunity() EXCLUSIVE_LOCKS_REQUIRED(worker_lock_);
-
-  size_t GetMaxConcurrency(size_t worker_count) const;
-
-  // TaskSource:
-  RunStatus WillRunTask() override;
-  Task TakeTask(TaskSource::Transaction* transaction) override;
-  absl::optional<Task> Clear(TaskSource::Transaction* transaction) override;
-  bool DidProcessTask(TaskSource::Transaction* transaction) override;
-  bool WillReEnqueue(TimeTicks now,
-                     TaskSource::Transaction* transaction) override;
-  bool OnBecomeReady() override;
-
-  // Synchronizes access to workers state.
-  mutable CheckedLock worker_lock_{UniversalSuccessor()};
-
-  // Current atomic state (atomic despite the lock to allow optimistic reads
-  // and cancellation without the lock).
-  State state_ GUARDED_BY(worker_lock_);
-  // Normally, |join_flag_| is protected by |lock_|, except in ShouldYield()
-  // hence the use of atomics.
-  JoinFlag join_flag_ GUARDED_BY(worker_lock_);
-  // Signaled when |join_flag_| is kWaiting* and a worker returns.
-  std::unique_ptr<ConditionVariable> worker_released_condition_
-      GUARDED_BY(worker_lock_);
-
-  std::atomic<uint32_t> assigned_task_ids_{0};
-
-  RepeatingCallback<size_t(size_t)> max_concurrency_callback_;
-
-  // Worker task set by the job owner.
-  RepeatingCallback<void(JobDelegate*)> worker_task_;
-  // Task returned from TakeTask(), that calls |worker_task_| internally.
-  RepeatingClosure primary_task_;
-
-  TaskMetadata task_metadata_;
-
-  const TimeTicks ready_time_;
-  raw_ptr<PooledTaskRunnerDelegate, LeakedDanglingUntriaged> delegate_;
-};
-
-}  // namespace internal
-}  // namespace base
-
-#endif  // BASE_TASK_THREAD_POOL_JOB_TASK_SOURCE_OLD_H_
diff --git a/base/task/thread_pool/job_task_source_unittest.cc b/base/task/thread_pool/job_task_source_unittest.cc
index 1abe373..c6668b6 100644
--- a/base/task/thread_pool/job_task_source_unittest.cc
+++ b/base/task/thread_pool/job_task_source_unittest.cc
@@ -2,19 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/task/thread_pool/job_task_source_interface.h"
+#include "base/task/thread_pool/job_task_source.h"
 
 #include <utility>
 
 #include "base/functional/callback_helpers.h"
 #include "base/memory/ptr_util.h"
-#include "base/task/post_job.h"
-#include "base/task/task_features.h"
 #include "base/task/thread_pool/pooled_task_runner_delegate.h"
 #include "base/task/thread_pool/test_utils.h"
 #include "base/test/bind.h"
 #include "base/test/gtest_util.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/test/test_timeouts.h"
 #include "build/build_config.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -44,43 +41,18 @@
                     TaskPriority priority));
 };
 
-class ThreadPoolJobTaskSourceTest : public testing::Test,
-                                    public testing::WithParamInterface<bool> {
+class ThreadPoolJobTaskSourceTest : public testing::Test {
  protected:
-  ThreadPoolJobTaskSourceTest() {
-    if (GetParam()) {
-      scoped_feature_list_.InitAndEnableFeature(kUseNewJobImplementation);
-    } else {
-      scoped_feature_list_.InitAndDisableFeature(kUseNewJobImplementation);
-    }
-  }
-
-  // Creates and starts a job which which runs `callback` with
-  // `initial_max_concurrency`.
-  std::pair<scoped_refptr<test::MockJobTask>, scoped_refptr<JobTaskSource>>
-  StartJob(size_t initial_max_concurrency,
-           base::RepeatingCallback<void(JobDelegate*)> callback = DoNothing()) {
-    auto job_task = base::MakeRefCounted<test::MockJobTask>(
-        std::move(callback), /* num_tasks_to_run */ initial_max_concurrency);
-    auto task_source = job_task->GetJobTaskSource(
-        FROM_HERE, {}, &pooled_task_runner_delegate_);
-    if (initial_max_concurrency > 0) {
-      EXPECT_CALL(pooled_task_runner_delegate_, EnqueueJobTaskSource(_));
-    }
-    task_source->NotifyConcurrencyIncrease();
-    return {job_task, task_source};
-  }
-
   testing::StrictMock<MockPooledTaskRunnerDelegate>
       pooled_task_runner_delegate_;
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 // Verifies the normal flow of running 2 tasks one after the other.
-TEST_P(ThreadPoolJobTaskSourceTest, RunTasks) {
-  auto [job_task, task_source] = StartJob(/* initial_max_concurrency=*/2);
+TEST_F(ThreadPoolJobTaskSourceTest, RunTasks) {
+  auto job_task = base::MakeRefCounted<test::MockJobTask>(
+      DoNothing(), /* num_tasks_to_run */ 2);
+  scoped_refptr<JobTaskSource> task_source =
+      job_task->GetJobTaskSource(FROM_HERE, {}, &pooled_task_runner_delegate_);
   auto registered_task_source =
       RegisteredTaskSource::CreateForTesting(task_source);
 
@@ -120,8 +92,11 @@
 
 // Verifies that a job task source doesn't allow any new RunStatus after Clear()
 // is called.
-TEST_P(ThreadPoolJobTaskSourceTest, Clear) {
-  auto [job_task, task_source] = StartJob(/* initial_max_concurrency=*/5);
+TEST_F(ThreadPoolJobTaskSourceTest, Clear) {
+  auto job_task = base::MakeRefCounted<test::MockJobTask>(
+      DoNothing(), /* num_tasks_to_run */ 5);
+  scoped_refptr<JobTaskSource> task_source =
+      job_task->GetJobTaskSource(FROM_HERE, {}, &pooled_task_runner_delegate_);
 
   EXPECT_EQ(5U, task_source->GetRemainingConcurrency());
   auto registered_task_source_a =
@@ -181,8 +156,11 @@
 
 // Verifies that a job task source doesn't return an "allowed" RunStatus after
 // Cancel() is called.
-TEST_P(ThreadPoolJobTaskSourceTest, Cancel) {
-  auto [job_task, task_source] = StartJob(/* initial_max_concurrency=*/3);
+TEST_F(ThreadPoolJobTaskSourceTest, Cancel) {
+  auto job_task = base::MakeRefCounted<test::MockJobTask>(
+      DoNothing(), /* num_tasks_to_run */ 3);
+  scoped_refptr<JobTaskSource> task_source = job_task->GetJobTaskSource(
+      FROM_HERE, {TaskPriority::BEST_EFFORT}, &pooled_task_runner_delegate_);
 
   auto registered_task_source_a =
       RegisteredTaskSource::CreateForTesting(task_source);
@@ -217,8 +195,11 @@
 }
 
 // Verifies that multiple tasks can run in parallel up to |max_concurrency|.
-TEST_P(ThreadPoolJobTaskSourceTest, RunTasksInParallel) {
-  auto [job_task, task_source] = StartJob(/* initial_max_concurrency=*/2);
+TEST_F(ThreadPoolJobTaskSourceTest, RunTasksInParallel) {
+  auto job_task = base::MakeRefCounted<test::MockJobTask>(
+      DoNothing(), /* num_tasks_to_run */ 2);
+  scoped_refptr<JobTaskSource> task_source =
+      job_task->GetJobTaskSource(FROM_HERE, {}, &pooled_task_runner_delegate_);
 
   auto registered_task_source_a =
       RegisteredTaskSource::CreateForTesting(task_source);
@@ -242,14 +223,11 @@
             TaskSource::RunStatus::kDisallowed);
 
   std::move(task_a.task).Run();
-  EXPECT_FALSE(registered_task_source_a.DidProcessTask());
-  EXPECT_EQ(1U, task_source->GetSortKey().worker_count());
-
-  // Increasing max concurrency above the number of workers should cause the
-  // task source to re-enqueue.
+  // Adding a task before closing the first run operation should cause the task
+  // source to re-enqueue.
   job_task->SetNumTasksToRun(2);
-  EXPECT_CALL(pooled_task_runner_delegate_, EnqueueJobTaskSource(_));
-  task_source->NotifyConcurrencyIncrease();
+  EXPECT_TRUE(registered_task_source_a.DidProcessTask());
+  EXPECT_EQ(1U, task_source->GetSortKey().worker_count());
 
   std::move(task_b.task).Run();
   EXPECT_TRUE(registered_task_source_b.DidProcessTask());
@@ -268,7 +246,7 @@
 }
 
 // Verifies the normal flow of running the join task until completion.
-TEST_P(ThreadPoolJobTaskSourceTest, RunJoinTask) {
+TEST_F(ThreadPoolJobTaskSourceTest, RunJoinTask) {
   auto job_task = base::MakeRefCounted<test::MockJobTask>(
       DoNothing(), /* num_tasks_to_run */ 2);
   scoped_refptr<JobTaskSource> task_source =
@@ -284,17 +262,17 @@
 
 // Verify that |worker_count| excludes the (inactive) returning thread calling
 // max_concurrency_callback.
-TEST_P(ThreadPoolJobTaskSourceTest, RunTaskWorkerCount) {
+TEST_F(ThreadPoolJobTaskSourceTest, RunTaskWorkerCount) {
   size_t max_concurrency = 1;
-  scoped_refptr<JobTaskSource> task_source = internal::CreateJobTaskSource(
-      FROM_HERE, TaskTraits(),
-      BindLambdaForTesting([&](JobDelegate* delegate) { --max_concurrency; }),
-      BindLambdaForTesting([&](size_t worker_count) -> size_t {
-        return max_concurrency + worker_count;
-      }),
-      &pooled_task_runner_delegate_);
-  EXPECT_CALL(pooled_task_runner_delegate_, EnqueueJobTaskSource(_));
-  task_source->NotifyConcurrencyIncrease();
+  scoped_refptr<JobTaskSource> task_source =
+      base::MakeRefCounted<JobTaskSource>(
+          FROM_HERE, TaskTraits(),
+          BindLambdaForTesting(
+              [&](JobDelegate* delegate) { --max_concurrency; }),
+          BindLambdaForTesting([&](size_t worker_count) -> size_t {
+            return max_concurrency + worker_count;
+          }),
+          &pooled_task_runner_delegate_);
 
   auto registered_task_source =
       RegisteredTaskSource::CreateForTesting(task_source);
@@ -311,15 +289,17 @@
 
 // Verify that |worker_count| excludes the (inactive) joining thread calling
 // max_concurrency_callback.
-TEST_P(ThreadPoolJobTaskSourceTest, RunJoinTaskWorkerCount) {
+TEST_F(ThreadPoolJobTaskSourceTest, RunJoinTaskWorkerCount) {
   size_t max_concurrency = 1;
-  scoped_refptr<JobTaskSource> task_source = internal::CreateJobTaskSource(
-      FROM_HERE, TaskTraits(),
-      BindLambdaForTesting([&](JobDelegate* delegate) { --max_concurrency; }),
-      BindLambdaForTesting([&](size_t worker_count) -> size_t {
-        return max_concurrency + worker_count;
-      }),
-      &pooled_task_runner_delegate_);
+  scoped_refptr<JobTaskSource> task_source =
+      base::MakeRefCounted<JobTaskSource>(
+          FROM_HERE, TaskTraits(),
+          BindLambdaForTesting(
+              [&](JobDelegate* delegate) { --max_concurrency; }),
+          BindLambdaForTesting([&](size_t worker_count) -> size_t {
+            return max_concurrency + worker_count;
+          }),
+          &pooled_task_runner_delegate_);
 
   EXPECT_TRUE(task_source->WillJoin());
   // Once the worker_task runs, |worker_count| should drop to 0 and the job
@@ -330,7 +310,7 @@
 
 // Verifies that WillJoin() doesn't allow a joining thread to contribute
 // after Cancel() is called.
-TEST_P(ThreadPoolJobTaskSourceTest, CancelJoinTask) {
+TEST_F(ThreadPoolJobTaskSourceTest, CancelJoinTask) {
   auto job_task = base::MakeRefCounted<test::MockJobTask>(
       DoNothing(), /* num_tasks_to_run */ 2);
   scoped_refptr<JobTaskSource> task_source =
@@ -342,7 +322,7 @@
 
 // Verifies that RunJoinTask() doesn't allow a joining thread to contribute
 // after Cancel() is called.
-TEST_P(ThreadPoolJobTaskSourceTest, JoinCancelTask) {
+TEST_F(ThreadPoolJobTaskSourceTest, JoinCancelTask) {
   auto job_task = base::MakeRefCounted<test::MockJobTask>(
       DoNothing(), /* num_tasks_to_run */ 2);
   scoped_refptr<JobTaskSource> task_source =
@@ -355,8 +335,11 @@
 
 // Verifies that the join task can run in parallel with worker tasks up to
 // |max_concurrency|.
-TEST_P(ThreadPoolJobTaskSourceTest, RunJoinTaskInParallel) {
-  auto [job_task, task_source] = StartJob(/* initial_max_concurrency=*/2);
+TEST_F(ThreadPoolJobTaskSourceTest, RunJoinTaskInParallel) {
+  auto job_task = base::MakeRefCounted<test::MockJobTask>(
+      DoNothing(), /* num_tasks_to_run */ 2);
+  scoped_refptr<JobTaskSource> task_source =
+      job_task->GetJobTaskSource(FROM_HERE, {}, &pooled_task_runner_delegate_);
 
   auto registered_task_source =
       RegisteredTaskSource::CreateForTesting(task_source);
@@ -376,8 +359,11 @@
 
 // Verifies that a call to NotifyConcurrencyIncrease() calls the delegate
 // and allows to run additional tasks.
-TEST_P(ThreadPoolJobTaskSourceTest, NotifyConcurrencyIncrease) {
-  auto [job_task, task_source] = StartJob(/* initial_max_concurrency=*/1);
+TEST_F(ThreadPoolJobTaskSourceTest, NotifyConcurrencyIncrease) {
+  auto job_task = base::MakeRefCounted<test::MockJobTask>(
+      DoNothing(), /* num_tasks_to_run */ 1);
+  scoped_refptr<JobTaskSource> task_source =
+      job_task->GetJobTaskSource(FROM_HERE, {}, &pooled_task_runner_delegate_);
 
   auto registered_task_source_a =
       RegisteredTaskSource::CreateForTesting(task_source);
@@ -409,15 +395,17 @@
 }
 
 // Verifies that ShouldYield() calls the delegate.
-TEST_P(ThreadPoolJobTaskSourceTest, ShouldYield) {
-  auto [job_task, task_source] = StartJob(
-      /*initial_max_concurrency=*/1,
+TEST_F(ThreadPoolJobTaskSourceTest, ShouldYield) {
+  auto job_task = base::MakeRefCounted<test::MockJobTask>(
       BindLambdaForTesting([](JobDelegate* delegate) {
         // As set up below, the mock will return false once and true the second
         // time.
         EXPECT_FALSE(delegate->ShouldYield());
         EXPECT_TRUE(delegate->ShouldYield());
-      }));
+      }),
+      /* num_tasks_to_run */ 1);
+  scoped_refptr<JobTaskSource> task_source =
+      job_task->GetJobTaskSource(FROM_HERE, {}, &pooled_task_runner_delegate_);
 
   auto registered_task_source =
       RegisteredTaskSource::CreateForTesting(task_source);
@@ -437,18 +425,17 @@
 
 // Verifies that max concurrency is allowed to stagnate when ShouldYield returns
 // true.
-TEST_P(ThreadPoolJobTaskSourceTest, MaxConcurrencyStagnateIfShouldYield) {
-  scoped_refptr<JobTaskSource> task_source = internal::CreateJobTaskSource(
-      FROM_HERE, TaskTraits(), BindRepeating([](JobDelegate* delegate) {
-        // As set up below, the mock will return true once.
-        ASSERT_TRUE(delegate->ShouldYield());
-      }),
-      BindRepeating([](size_t /*worker_count*/) -> size_t {
-        return 1;  // max concurrency is always 1.
-      }),
-      &pooled_task_runner_delegate_);
-  EXPECT_CALL(pooled_task_runner_delegate_, EnqueueJobTaskSource(_));
-  task_source->NotifyConcurrencyIncrease();
+TEST_F(ThreadPoolJobTaskSourceTest, MaxConcurrencyStagnateIfShouldYield) {
+  scoped_refptr<JobTaskSource> task_source =
+      base::MakeRefCounted<JobTaskSource>(
+          FROM_HERE, TaskTraits(), BindRepeating([](JobDelegate* delegate) {
+            // As set up below, the mock will return true once.
+            ASSERT_TRUE(delegate->ShouldYield());
+          }),
+          BindRepeating([](size_t /*worker_count*/) -> size_t {
+            return 1;  // max concurrency is always 1.
+          }),
+          &pooled_task_runner_delegate_);
 
   EXPECT_CALL(pooled_task_runner_delegate_, ShouldYield(_))
       .WillOnce(Return(true));
@@ -465,8 +452,12 @@
   registered_task_source.DidProcessTask();
 }
 
-TEST_P(ThreadPoolJobTaskSourceTest, InvalidTakeTask) {
-  auto [job_task, task_source] = StartJob(/* initial_max_concurrency=*/1);
+TEST_F(ThreadPoolJobTaskSourceTest, InvalidTakeTask) {
+  auto job_task =
+      base::MakeRefCounted<test::MockJobTask>(DoNothing(),
+                                              /* num_tasks_to_run */ 1);
+  scoped_refptr<JobTaskSource> task_source =
+      job_task->GetJobTaskSource(FROM_HERE, {}, &pooled_task_runner_delegate_);
 
   auto registered_task_source_a =
       RegisteredTaskSource::CreateForTesting(task_source);
@@ -485,7 +476,7 @@
   registered_task_source_a.DidProcessTask();
 }
 
-TEST_P(ThreadPoolJobTaskSourceTest, InvalidDidProcessTask) {
+TEST_F(ThreadPoolJobTaskSourceTest, InvalidDidProcessTask) {
   auto job_task =
       base::MakeRefCounted<test::MockJobTask>(DoNothing(),
                                               /* num_tasks_to_run */ 1);
@@ -499,7 +490,7 @@
   EXPECT_DCHECK_DEATH(registered_task_source.DidProcessTask());
 }
 
-TEST_P(ThreadPoolJobTaskSourceTest, AcquireTaskId) {
+TEST_F(ThreadPoolJobTaskSourceTest, AcquireTaskId) {
   auto job_task =
       base::MakeRefCounted<test::MockJobTask>(DoNothing(),
                                               /* num_tasks_to_run */ 4);
@@ -519,19 +510,24 @@
 }
 
 // Verifies that task id is released after worker_task returns.
-TEST_P(ThreadPoolJobTaskSourceTest, GetTaskId) {
-  auto [job_task, task_source] = StartJob(
-      /* initial_max_concurrency=*/2, BindRepeating([](JobDelegate* delegate) {
+TEST_F(ThreadPoolJobTaskSourceTest, GetTaskId) {
+  auto task_source = MakeRefCounted<JobTaskSource>(
+      FROM_HERE, TaskTraits{}, BindRepeating([](JobDelegate* delegate) {
         // Confirm that task id 0 is reused on the second run.
         EXPECT_EQ(0U, delegate->GetTaskId());
-      }));
+
+        // Allow running the task again.
+        delegate->NotifyConcurrencyIncrease();
+      }),
+      BindRepeating([](size_t /*worker_count*/) -> size_t { return 1; }),
+      &pooled_task_runner_delegate_);
 
   auto registered_task_source =
       RegisteredTaskSource::CreateForTesting(task_source);
 
   // Run the worker_task twice.
   ASSERT_EQ(registered_task_source.WillRunTask(),
-            TaskSource::RunStatus::kAllowedNotSaturated);
+            TaskSource::RunStatus::kAllowedSaturated);
   auto task1 = registered_task_source.TakeTask();
   std::move(task1.task).Run();
   registered_task_source.DidProcessTask();
@@ -543,15 +539,5 @@
   registered_task_source.DidProcessTask();
 }
 
-INSTANTIATE_TEST_SUITE_P(,
-                         ThreadPoolJobTaskSourceTest,
-                         testing::Bool(),
-                         [](const testing::TestParamInfo<bool>& info) {
-                           if (info.param) {
-                             return "NewJob";
-                           }
-                           return "OldJob";
-                         });
-
 }  // namespace internal
 }  // namespace base
diff --git a/base/task/thread_pool/test_utils.cc b/base/task/thread_pool/test_utils.cc
index effc1ed..5a68a3f 100644
--- a/base/task/thread_pool/test_utils.cc
+++ b/base/task/thread_pool/test_utils.cc
@@ -64,7 +64,8 @@
   auto job_task = base::MakeRefCounted<MockJobTask>(std::move(closure));
   scoped_refptr<JobTaskSource> task_source = job_task->GetJobTaskSource(
       from_here, traits_, pooled_task_runner_delegate_);
-  return task_source->NotifyConcurrencyIncrease();
+  return pooled_task_runner_delegate_->EnqueueJobTaskSource(
+      std::move(task_source));
 }
 
 scoped_refptr<TaskRunner> CreateJobTaskRunner(
@@ -312,7 +313,7 @@
     const Location& from_here,
     const TaskTraits& traits,
     PooledTaskRunnerDelegate* delegate) {
-  return CreateJobTaskSource(
+  return MakeRefCounted<JobTaskSource>(
       from_here, traits, base::BindRepeating(&test::MockJobTask::Run, this),
       base::BindRepeating(&test::MockJobTask::GetMaxConcurrency, this),
       delegate);
diff --git a/base/task/thread_pool/thread_group.cc b/base/task/thread_pool/thread_group.cc
index f1a8d22..00cc588 100644
--- a/base/task/thread_pool/thread_group.cc
+++ b/base/task/thread_pool/thread_group.cc
@@ -183,8 +183,6 @@
       thread_type_hint_ != ThreadType::kBackground
           ? kForegroundBlockedWorkersPoll
           : kBackgroundBlockedWorkersPoll;
-  in_start().ensure_enough_workers_at_end_of_get_work =
-      base::FeatureList::IsEnabled(kUseNewJobImplementation);
   in_start().max_num_workers_created = base::kMaxNumWorkersCreated.Get();
 
   CheckedAutoLock auto_lock(lock_);
diff --git a/base/task/thread_pool/thread_group.h b/base/task/thread_pool/thread_group.h
index efb68ae..ea9b1a3e 100644
--- a/base/task/thread_pool/thread_group.h
+++ b/base/task/thread_pool/thread_group.h
@@ -417,10 +417,6 @@
     // capacity.
     TimeDelta blocked_workers_poll_period;
 
-    // Whether EnsureEnoughWorkersLockRequired() should be called at the end of
-    // GetWork() instead of at the beginning.
-    bool ensure_enough_workers_at_end_of_get_work = false;
-
     // The max number of workers that a ThreadGroupSemaphore will create in any
     // one EnsureEnoughWorkers() call.
     int max_num_workers_created = 2;
diff --git a/base/task/thread_pool/thread_group_impl_unittest.cc b/base/task/thread_pool/thread_group_impl_unittest.cc
index 5888e5b..f91dbd1 100644
--- a/base/task/thread_pool/thread_group_impl_unittest.cc
+++ b/base/task/thread_pool/thread_group_impl_unittest.cc
@@ -319,7 +319,13 @@
   scoped_refptr<JobTaskSource> task_source =
       job_task->GetJobTaskSource(FROM_HERE, {TaskPriority::USER_VISIBLE},
                                  &mock_pooled_task_runner_delegate_);
-  task_source->NotifyConcurrencyIncrease();
+
+  auto registered_task_source = task_tracker_.RegisterTaskSource(task_source);
+  ASSERT_TRUE(registered_task_source);
+  static_cast<ThreadGroup*>(thread_group_.get())
+      ->PushTaskSourceAndWakeUpWorkers(
+          RegisteredTaskSourceAndTransaction::FromTaskSource(
+              std::move(registered_task_source)));
 
   threads_running.Wait();
 
diff --git a/base/task/thread_pool/thread_group_semaphore.cc b/base/task/thread_pool/thread_group_semaphore.cc
index 6649aa5..0440a6c 100644
--- a/base/task/thread_pool/thread_group_semaphore.cc
+++ b/base/task/thread_pool/thread_group_semaphore.cc
@@ -41,6 +41,8 @@
   ~SemaphoreScopedCommandsExecutor() override {
     CheckedLock::AssertNoLockHeldOnCurrentThread();
     for (int i = 0; i < semaphore_signal_count_; ++i) {
+      TRACE_EVENT_INSTANT("wakeup.flow", "WorkerThreadSemaphore::Signal",
+                          perfetto::Flow::FromPointer(&outer()->semaphore_));
       outer()->semaphore_.Signal();
     }
   }
@@ -446,7 +448,7 @@
                   ? workers_.size() >= after_start().initial_max_tasks
                   : true,
               &join_called_for_testing_),
-          task_tracker_, worker_sequence_num_++, &lock_);
+          task_tracker_, worker_sequence_num_++, &lock_, &semaphore_);
   DCHECK(worker);
   workers_.push_back(worker);
   DCHECK_LE(workers_.size(), max_tasks_);
diff --git a/base/task/thread_pool/thread_group_unittest.cc b/base/task/thread_pool/thread_group_unittest.cc
index d5fa06eb..c934e99 100644
--- a/base/task/thread_pool/thread_group_unittest.cc
+++ b/base/task/thread_pool/thread_group_unittest.cc
@@ -23,7 +23,6 @@
 #include "base/task/thread_pool/test_utils.h"
 #include "base/task/thread_pool/thread_group_impl.h"
 #include "base/test/bind.h"
-#include "base/test/scoped_feature_list.h"
 #include "base/test/test_timeouts.h"
 #include "base/test/test_waitable_event.h"
 #include "base/threading/platform_thread.h"
@@ -155,21 +154,6 @@
 
 using ThreadGroupTest = ThreadGroupTestBase;
 
-class ThreadGroupTestAllJobImpls : public ThreadGroupTestBase,
-                                   public testing::WithParamInterface<bool> {
- public:
-  ThreadGroupTestAllJobImpls() {
-    if (GetParam()) {
-      scoped_feature_list_.InitAndEnableFeature(kUseNewJobImplementation);
-    } else {
-      scoped_feature_list_.InitAndDisableFeature(kUseNewJobImplementation);
-    }
-  }
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
 // TODO(etiennep): Audit tests that don't need TaskSourceExecutionMode
 // parameter.
 class ThreadGroupTestAllExecutionModes
@@ -534,7 +518,7 @@
 }
 
 // Verify that tasks from a JobTaskSource run at the intended concurrency.
-TEST_P(ThreadGroupTestAllJobImpls, ScheduleJobTaskSource) {
+TEST_F(ThreadGroupTest, ScheduleJobTaskSource) {
   StartThreadGroup();
 
   TestWaitableEvent threads_running;
@@ -553,7 +537,13 @@
       /* num_tasks_to_run */ kMaxTasks);
   scoped_refptr<JobTaskSource> task_source = job_task->GetJobTaskSource(
       FROM_HERE, {}, &mock_pooled_task_runner_delegate_);
-  task_source->NotifyConcurrencyIncrease();
+
+  auto registered_task_source =
+      task_tracker_.RegisterTaskSource(std::move(task_source));
+  EXPECT_TRUE(registered_task_source);
+  thread_group_->PushTaskSourceAndWakeUpWorkers(
+      RegisteredTaskSourceAndTransaction::FromTaskSource(
+          std::move(registered_task_source)));
 
   threads_running.Wait();
   threads_continue.Signal();
@@ -564,7 +554,7 @@
 }
 
 // Verify that tasks from a JobTaskSource run at the intended concurrency.
-TEST_P(ThreadGroupTestAllJobImpls, ScheduleJobTaskSourceMultipleTime) {
+TEST_F(ThreadGroupTest, ScheduleJobTaskSourceMultipleTime) {
   StartThreadGroup();
 
   TestWaitableEvent thread_running;
@@ -579,16 +569,23 @@
   scoped_refptr<JobTaskSource> task_source = job_task->GetJobTaskSource(
       FROM_HERE, {}, &mock_pooled_task_runner_delegate_);
 
-  // Multiple calls to NotifyConcurrencyIncrease() should have the same effect
-  // as a single call.
-  task_source->NotifyConcurrencyIncrease();
-  task_source->NotifyConcurrencyIncrease();
+  thread_group_->PushTaskSourceAndWakeUpWorkers(
+      RegisteredTaskSourceAndTransaction::FromTaskSource(
+          task_tracker_.RegisterTaskSource(task_source)));
+
+  // Enqueuing the task source again shouldn't affect the number of time it's
+  // run.
+  thread_group_->PushTaskSourceAndWakeUpWorkers(
+      RegisteredTaskSourceAndTransaction::FromTaskSource(
+          task_tracker_.RegisterTaskSource(task_source)));
 
   thread_running.Wait();
   thread_continue.Signal();
 
   // Once the worker task ran, enqueuing the task source has no effect.
-  task_source->NotifyConcurrencyIncrease();
+  thread_group_->PushTaskSourceAndWakeUpWorkers(
+      RegisteredTaskSourceAndTransaction::FromTaskSource(
+          task_tracker_.RegisterTaskSource(task_source)));
 
   // Flush the task tracker to be sure that no local variables are accessed by
   // tasks after the end of the scope.
@@ -597,7 +594,7 @@
 
 // Verify that Cancel() on a job stops running the worker task and causes
 // current workers to yield.
-TEST_P(ThreadGroupTestAllJobImpls, CancelJobTaskSource) {
+TEST_F(ThreadGroupTest, CancelJobTaskSource) {
   StartThreadGroup();
 
   CheckedLock tasks_running_lock;
@@ -620,8 +617,8 @@
       /* num_tasks_to_run */ kTooManyTasks);
   scoped_refptr<JobTaskSource> task_source = job_task->GetJobTaskSource(
       FROM_HERE, {}, &mock_pooled_task_runner_delegate_);
-  task_source->NotifyConcurrencyIncrease();
 
+  mock_pooled_task_runner_delegate_.EnqueueJobTaskSource(task_source);
   JobHandle job_handle = internal::JobTaskSource::CreateJobHandle(task_source);
 
   // Wait for at least 1 task to start running.
@@ -640,7 +637,7 @@
 
 // Verify that calling JobTaskSource::NotifyConcurrencyIncrease() (re-)schedule
 // tasks with the intended concurrency.
-TEST_P(ThreadGroupTestAllJobImpls, JobTaskSourceConcurrencyIncrease) {
+TEST_F(ThreadGroupTest, JobTaskSourceConcurrencyIncrease) {
   StartThreadGroup();
 
   TestWaitableEvent threads_running_a;
@@ -660,7 +657,12 @@
       /* num_tasks_to_run */ kMaxTasks / 2);
   auto task_source = job_state->GetJobTaskSource(
       FROM_HERE, {}, &mock_pooled_task_runner_delegate_);
-  task_source->NotifyConcurrencyIncrease();
+
+  auto registered_task_source = task_tracker_.RegisterTaskSource(task_source);
+  EXPECT_TRUE(registered_task_source);
+  thread_group_->PushTaskSourceAndWakeUpWorkers(
+      RegisteredTaskSourceAndTransaction::FromTaskSource(
+          std::move(registered_task_source)));
 
   threads_running_a.Wait();
   // Reset |threads_running_barrier| for the remaining tasks.
@@ -684,7 +686,7 @@
 
 // Verify that a JobTaskSource that becomes empty while in the queue eventually
 // gets discarded.
-TEST_P(ThreadGroupTestAllJobImpls, ScheduleEmptyJobTaskSource) {
+TEST_F(ThreadGroupTest, ScheduleEmptyJobTaskSource) {
   StartThreadGroup();
 
   task_tracker_.SetCanRunPolicy(CanRunPolicy::kNone);
@@ -694,7 +696,13 @@
       /* num_tasks_to_run */ 1);
   scoped_refptr<JobTaskSource> task_source = job_task->GetJobTaskSource(
       FROM_HERE, {}, &mock_pooled_task_runner_delegate_);
-  task_source->NotifyConcurrencyIncrease();
+
+  auto registered_task_source =
+      task_tracker_.RegisterTaskSource(std::move(task_source));
+  EXPECT_TRUE(registered_task_source);
+  thread_group_->PushTaskSourceAndWakeUpWorkers(
+      RegisteredTaskSourceAndTransaction::FromTaskSource(
+          std::move(registered_task_source)));
 
   // The worker task will never run.
   job_task->SetNumTasksToRun(0);
@@ -708,7 +716,7 @@
 
 // Verify that Join() on a job contributes to max concurrency and waits for all
 // workers to return.
-TEST_P(ThreadGroupTestAllJobImpls, JoinJobTaskSource) {
+TEST_F(ThreadGroupTest, JoinJobTaskSource) {
   StartThreadGroup();
 
   TestWaitableEvent threads_continue;
@@ -724,8 +732,8 @@
       /* num_tasks_to_run */ kMaxTasks + 1);
   scoped_refptr<JobTaskSource> task_source = job_task->GetJobTaskSource(
       FROM_HERE, {}, &mock_pooled_task_runner_delegate_);
-  task_source->NotifyConcurrencyIncrease();
 
+  mock_pooled_task_runner_delegate_.EnqueueJobTaskSource(task_source);
   JobHandle job_handle = internal::JobTaskSource::CreateJobHandle(task_source);
   job_handle.Join();
   // All worker tasks should complete before Join() returns.
@@ -739,19 +747,19 @@
 
 // Verify that finishing work outside of a job unblocks workers with a stale
 // max concurrency.
-TEST_P(ThreadGroupTestAllJobImpls, JoinJobTaskSourceStaleConcurrency) {
+TEST_F(ThreadGroupTest, JoinJobTaskSourceStaleConcurrency) {
   StartThreadGroup();
 
   TestWaitableEvent thread_running;
   std::atomic_size_t max_concurrency(1);
-  auto task_source = CreateJobTaskSource(
+  auto task_source = MakeRefCounted<JobTaskSource>(
       FROM_HERE, TaskTraits{},
       BindLambdaForTesting([&](JobDelegate*) { thread_running.Signal(); }),
       BindLambdaForTesting(
           [&](size_t /*worker_count*/) -> size_t { return max_concurrency; }),
       &mock_pooled_task_runner_delegate_);
-  task_source->NotifyConcurrencyIncrease();
 
+  mock_pooled_task_runner_delegate_.EnqueueJobTaskSource(task_source);
   JobHandle job_handle = internal::JobTaskSource::CreateJobHandle(task_source);
   thread_running.Wait();
 
@@ -765,17 +773,17 @@
 }
 
 // Verify that cancelling a job unblocks workers with a stale max concurrency.
-TEST_P(ThreadGroupTestAllJobImpls, CancelJobTaskSourceWithStaleConcurrency) {
+TEST_F(ThreadGroupTest, CancelJobTaskSourceWithStaleConcurrency) {
   StartThreadGroup();
 
   TestWaitableEvent thread_running;
-  auto task_source = CreateJobTaskSource(
+  auto task_source = MakeRefCounted<JobTaskSource>(
       FROM_HERE, TaskTraits{},
       BindLambdaForTesting([&](JobDelegate*) { thread_running.Signal(); }),
       BindRepeating([](size_t /*worker_count*/) -> size_t { return 1; }),
       &mock_pooled_task_runner_delegate_);
-  task_source->NotifyConcurrencyIncrease();
 
+  mock_pooled_task_runner_delegate_.EnqueueJobTaskSource(task_source);
   JobHandle job_handle = internal::JobTaskSource::CreateJobHandle(task_source);
   thread_running.Wait();
   job_handle.Cancel();
@@ -787,7 +795,7 @@
 // Verify that the maximum number of BEST_EFFORT tasks that can run concurrently
 // in a thread group does not affect JobTaskSource with a priority that was
 // increased from BEST_EFFORT to USER_BLOCKING.
-TEST_P(ThreadGroupTestAllJobImpls, JobTaskSourceUpdatePriority) {
+TEST_F(ThreadGroupTest, JobTaskSourceUpdatePriority) {
   StartThreadGroup();
 
   CheckedLock num_tasks_running_lock;
@@ -816,7 +824,12 @@
   scoped_refptr<JobTaskSource> task_source =
       job_task->GetJobTaskSource(FROM_HERE, {TaskPriority::BEST_EFFORT},
                                  &mock_pooled_task_runner_delegate_);
-  task_source->NotifyConcurrencyIncrease();
+
+  auto registered_task_source = task_tracker_.RegisterTaskSource(task_source);
+  EXPECT_TRUE(registered_task_source);
+  thread_group_->PushTaskSourceAndWakeUpWorkers(
+      RegisteredTaskSourceAndTransaction::FromTaskSource(
+          std::move(registered_task_source)));
 
   // Wait until |kMaxBestEffort| tasks start running.
   {
@@ -856,15 +869,5 @@
                          ThreadGroupTestAllExecutionModes,
                          ::testing::Values(TaskSourceExecutionMode::kJob));
 
-INSTANTIATE_TEST_SUITE_P(,
-                         ThreadGroupTestAllJobImpls,
-                         testing::Bool(),
-                         [](const testing::TestParamInfo<bool>& info) {
-                           if (info.param) {
-                             return "NewJob";
-                           }
-                           return "OldJob";
-                         });
-
 }  // namespace internal
 }  // namespace base
diff --git a/base/task/thread_pool/thread_group_worker_delegate.cc b/base/task/thread_pool/thread_group_worker_delegate.cc
index 4ce5754..a9afd71c 100644
--- a/base/task/thread_pool/thread_group_worker_delegate.cc
+++ b/base/task/thread_pool/thread_group_worker_delegate.cc
@@ -227,17 +227,15 @@
   DCHECK_CALLED_ON_VALID_THREAD(worker_thread_checker_);
   DCHECK(ContainsWorker(outer_->workers_, worker));
 
-  if (!outer_->after_start().ensure_enough_workers_at_end_of_get_work) {
-    // Use this opportunity, before assigning work to this worker, to
-    // create/signal additional workers if needed (doing this here allows us to
-    // reduce potentially expensive create/wake directly on PostTask()).
-    //
-    // Note: FlushWorkerCreation() below releases |outer_->lock_|. It is thus
-    // important that all other operations come after it to keep this method
-    // transactional.
-    outer_->EnsureEnoughWorkersLockRequired(executor);
-    executor->FlushWorkerCreation(&outer_->lock_);
-  }
+  // Use this opportunity, before assigning work to this worker, to
+  // create/signal additional workers if needed (doing this here allows us to
+  // reduce potentially expensive create/wake directly on PostTask()).
+  //
+  // Note: FlushWorkerCreation() below releases |outer_->lock_|. It is thus
+  // important that all other operations come after it to keep this method
+  // transactional.
+  outer_->EnsureEnoughWorkersLockRequired(executor);
+  executor->FlushWorkerCreation(&outer_->lock_);
 
   if (!CanGetWorkLockRequired(executor, worker)) {
     return nullptr;
@@ -269,25 +267,6 @@
   write_worker().current_task_priority = priority;
   write_worker().current_shutdown_behavior = task_source->shutdown_behavior();
 
-  if (outer_->after_start().ensure_enough_workers_at_end_of_get_work) {
-    // Subtle: This must be after the call to WillRunTask() inside
-    // TakeRegisteredTaskSource(), so that any state used by WillRunTask() to
-    // determine that the task source must remain in the TaskQueue is also used
-    // to determine the desired number of workers. Concretely, this wouldn't
-    // work:
-    //
-    //   Thread 1: GetWork() calls EnsureEnoughWorkers(). No worker woken up
-    //             because the queue contains a job with max concurrency = 1 and
-    //             the current worker is awake.
-    //   Thread 2: Increases the job's max concurrency.
-    //             ShouldQueueUponCapacityIncrease() returns false because the
-    //             job is already queued.
-    //   Thread 1: Calls WillRunTask() on the job. It returns
-    //             kAllowedNotSaturated because max concurrency is not reached.
-    //             But no extra worker is woken up to run the job!
-    outer_->EnsureEnoughWorkersLockRequired(executor);
-  }
-
   return task_source;
 }
 
diff --git a/base/task/thread_pool/thread_pool_impl_unittest.cc b/base/task/thread_pool/thread_pool_impl_unittest.cc
index 79633a56..a014bfc 100644
--- a/base/task/thread_pool/thread_pool_impl_unittest.cc
+++ b/base/task/thread_pool/thread_pool_impl_unittest.cc
@@ -6,7 +6,6 @@
 
 #include <stddef.h>
 
-#include <atomic>
 #include <memory>
 #include <string>
 #include <tuple>
@@ -24,9 +23,7 @@
 #include "base/message_loop/message_pump_type.h"
 #include "base/metrics/field_trial.h"
 #include "base/metrics/field_trial_params.h"
-#include "base/strings/strcat.h"
 #include "base/system/sys_info.h"
-#include "base/task/post_job.h"
 #include "base/task/task_features.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool/environment_config.h"
@@ -294,7 +291,6 @@
   ThreadPoolImplTestBase& operator=(const ThreadPoolImplTestBase&) = delete;
 
   virtual bool GetUseResourceEfficientThreadGroup() const = 0;
-  virtual bool GetUseNewJobImplementation() const = 0;
 
   void set_worker_thread_observer(
       std::unique_ptr<WorkerThreadObserver> worker_thread_observer) {
@@ -331,22 +327,15 @@
 
  private:
   void SetupFeatures() {
-    std::vector<base::test::FeatureRef> enabled_features;
-    std::vector<base::test::FeatureRef> disabled_features;
+    std::vector<base::test::FeatureRef> features;
 
     if (GetUseResourceEfficientThreadGroup()) {
-      enabled_features.push_back(kUseUtilityThreadGroup);
-    } else {
-      disabled_features.push_back(kUseUtilityThreadGroup);
+      features.push_back(kUseUtilityThreadGroup);
     }
 
-    if (GetUseNewJobImplementation()) {
-      enabled_features.push_back(kUseNewJobImplementation);
-    } else {
-      disabled_features.push_back(kUseNewJobImplementation);
+    if (!features.empty()) {
+      feature_list_.InitWithFeatures(features, {});
     }
-
-    feature_list_.InitWithFeatures(enabled_features, disabled_features);
   }
 
   base::test::ScopedFeatureList feature_list_;
@@ -354,17 +343,13 @@
   bool did_tear_down_ = false;
 };
 
-class ThreadPoolImplTest
-    : public ThreadPoolImplTestBase,
-      public testing::WithParamInterface<
-          std::pair<bool, /* use_resource_efficient_thread_group */
-                    bool /* use_new_job_implementation */>> {
+class ThreadPoolImplTest : public ThreadPoolImplTestBase,
+                           public testing::WithParamInterface<
+                               bool /* use_resource_efficient_thread_group */> {
  public:
   bool GetUseResourceEfficientThreadGroup() const override {
-    return GetParam().first;
+    return GetParam();
   }
-
-  bool GetUseNewJobImplementation() const override { return GetParam().second; }
 };
 
 // Tests run for enough traits and execution mode combinations to cover all
@@ -385,7 +370,6 @@
   bool GetUseResourceEfficientThreadGroup() const override {
     return std::get<0>(GetParam());
   }
-  bool GetUseNewJobImplementation() const override { return true; }
   TaskTraits GetTraits() const { return std::get<1>(GetParam()).traits; }
   TaskSourceExecutionMode GetExecutionMode() const {
     return std::get<1>(GetParam()).execution_mode;
@@ -1320,8 +1304,8 @@
   observer->WaitCallsOnMainExit();
 }
 
-// Verify that a basic NotifyConcurrencyIncrease() runs the worker task.
-TEST_P(ThreadPoolImplTest, BasicJob) {
+// Verify a basic EnqueueJobTaskSource() runs the worker task.
+TEST_P(ThreadPoolImplTest, ScheduleJobTaskSource) {
   StartThreadPool();
 
   TestWaitableEvent threads_running;
@@ -1332,74 +1316,11 @@
       /* num_tasks_to_run */ 1);
   scoped_refptr<JobTaskSource> task_source =
       job_task->GetJobTaskSource(FROM_HERE, {}, thread_pool_.get());
-  task_source->NotifyConcurrencyIncrease();
 
+  thread_pool_->EnqueueJobTaskSource(task_source);
   threads_running.Wait();
 }
 
-// Verify that max concurrency is eventually reached, but not exceeded, when
-// concurrency is increased from many workers.
-TEST_P(ThreadPoolImplTest, ParallelJob) {
-  constexpr size_t kTargetMaxConcurrency = 14;
-  constexpr size_t kLargeThreadPoolSize = 15;
-  StartThreadPool(/* max_num_foreground_threads=*/kLargeThreadPoolSize,
-                  kLargeThreadPoolSize);
-
-  // This test times out with the old job implementation.
-  // Note: Exit after starting the Thread Pool since TearDown() expects the
-  // ThreadPool to be started.
-  if (!GetUseNewJobImplementation()) {
-    return;
-  }
-
-  std::atomic_size_t max_concurrency = 2;
-  std::atomic_size_t num_workers = 0;
-
-  auto worker_task = BindLambdaForTesting([&](JobDelegate* delegate) {
-    // Increase max concurrency if target is not reached.
-    size_t current_max_concurrency =
-        max_concurrency.load(std::memory_order_relaxed);
-    while (current_max_concurrency < kTargetMaxConcurrency) {
-      if (max_concurrency.compare_exchange_weak(current_max_concurrency,
-                                                current_max_concurrency + 1,
-                                                std::memory_order_relaxed)) {
-        delegate->NotifyConcurrencyIncrease();
-        break;
-      }
-    }
-
-    // Increase number of workers and verify that target max concurrency is not
-    // exceeded.
-    size_t current_num_workers =
-        num_workers.fetch_add(1, std::memory_order_relaxed);
-    EXPECT_LT(current_num_workers, kTargetMaxConcurrency);
-
-    // Busy wait until the target number of workers is reached.
-    while (num_workers.load(std::memory_order_relaxed) <
-           kTargetMaxConcurrency) {
-    }
-
-    // Sleep to detect if too many workers run the worker task.
-    PlatformThread::Sleep(TestTimeouts::tiny_timeout());
-
-    // Force the job to exit by setting max concurrency to 0.
-    max_concurrency.store(0, std::memory_order_relaxed);
-  });
-
-  auto max_concurrency_callback =
-      BindLambdaForTesting([&](size_t worker_count) {
-        return max_concurrency.load(std::memory_order_relaxed);
-      });
-
-  auto task_source = internal::CreateJobTaskSource(
-      FROM_HERE, TaskTraits(), worker_task, max_concurrency_callback,
-      thread_pool_.get());
-  task_source->NotifyConcurrencyIncrease();
-  thread_pool_->FlushForTesting();
-
-  EXPECT_EQ(num_workers, kTargetMaxConcurrency);
-}
-
 // Verify that calling ShouldYield() returns true for a job task source that
 // needs to change thread group because of a priority update.
 TEST_P(ThreadPoolImplTest, ThreadGroupChangeShouldYield) {
@@ -1423,8 +1344,8 @@
       /* num_tasks_to_run */ 1);
   scoped_refptr<JobTaskSource> task_source = job_task->GetJobTaskSource(
       FROM_HERE, {TaskPriority::USER_VISIBLE}, thread_pool_.get());
-  task_source->NotifyConcurrencyIncrease();
 
+  thread_pool_->EnqueueJobTaskSource(task_source);
   threads_running.Wait();
   thread_pool_->UpdatePriority(task_source, TaskPriority::BEST_EFFORT);
   threads_continue.Signal();
@@ -1691,20 +1612,7 @@
   }
 }
 
-INSTANTIATE_TEST_SUITE_P(
-    ,
-    ThreadPoolImplTest,
-    ::testing::Values(
-        // Param 1: Use resource efficient thread group.
-        // Param 2: Use new job implementation.
-        std::make_pair(true, false),
-        std::make_pair(false, false),
-        std::make_pair(false, true)),
-    [](const testing::TestParamInfo<std::pair<bool, bool>>& info) {
-      return base::StrCat(
-          {info.param.first ? "EfficientThreadGroup" : "NoEfficientThreadGroup",
-           info.param.second ? "NewJob" : "OldJob"});
-    });
+INSTANTIATE_TEST_SUITE_P(All, ThreadPoolImplTest, ::testing::Bool());
 
 INSTANTIATE_TEST_SUITE_P(
     All,
diff --git a/base/task/thread_pool/worker_thread.cc b/base/task/thread_pool/worker_thread.cc
index 7f8d2a0..3d112704 100644
--- a/base/task/thread_pool/worker_thread.cc
+++ b/base/task/thread_pool/worker_thread.cc
@@ -138,12 +138,16 @@
 WorkerThread::WorkerThread(ThreadType thread_type_hint,
                            TrackedRef<TaskTracker> task_tracker,
                            size_t sequence_num,
-                           const CheckedLock* predecessor_lock)
+                           const CheckedLock* predecessor_lock,
+                           void* flow_terminator)
     : thread_lock_(predecessor_lock),
       task_tracker_(std::move(task_tracker)),
       thread_type_hint_(thread_type_hint),
       current_thread_type_(GetDesiredThreadType()),
-      sequence_num_(sequence_num) {
+      sequence_num_(sequence_num),
+      flow_terminator_(flow_terminator == nullptr
+                           ? reinterpret_cast<intptr_t>(this)
+                           : reinterpret_cast<intptr_t>(flow_terminator)) {
   DCHECK(task_tracker_);
   DCHECK(CanUseBackgroundThreadTypeForWorkerThread() ||
          thread_type_hint_ != ThreadType::kBackground);
@@ -398,7 +402,8 @@
     hang_watch_scope.reset();
     delegate()->WaitForWork();
     TRACE_EVENT_BEGIN("base", "WorkerThread active",
-                      perfetto::TerminatingFlow::FromPointer(this));
+                      perfetto::TerminatingFlow::FromPointer(
+                          reinterpret_cast<void*>(flow_terminator_)));
 
     // Don't GetWork() in the case where we woke up for Cleanup().
     if (ShouldExit()) {
diff --git a/base/task/thread_pool/worker_thread.h b/base/task/thread_pool/worker_thread.h
index 575e66b..27817152 100644
--- a/base/task/thread_pool/worker_thread.h
+++ b/base/task/thread_pool/worker_thread.h
@@ -127,7 +127,8 @@
   WorkerThread(ThreadType thread_type_hint,
                TrackedRef<TaskTracker> task_tracker,
                size_t sequence_num,
-               const CheckedLock* predecessor_lock = nullptr);
+               const CheckedLock* predecessor_lock = nullptr,
+               void* flow_terminator = nullptr);
 
   WorkerThread(const WorkerThread&) = delete;
   WorkerThread& operator=(const WorkerThread&) = delete;
@@ -255,6 +256,9 @@
 
   const size_t sequence_num_;
 
+  // Used to terminate WorkerThread::WakeUp trace event flows.
+  const intptr_t flow_terminator_;
+
   // Service thread task runner.
   scoped_refptr<SingleThreadTaskRunner> io_thread_task_runner_;
 };
diff --git a/base/task/thread_pool/worker_thread_semaphore.cc b/base/task/thread_pool/worker_thread_semaphore.cc
index d1d12c4c..545de049 100644
--- a/base/task/thread_pool/worker_thread_semaphore.cc
+++ b/base/task/thread_pool/worker_thread_semaphore.cc
@@ -31,11 +31,13 @@
     std::unique_ptr<Delegate> delegate,
     TrackedRef<TaskTracker> task_tracker,
     size_t sequence_num,
-    const CheckedLock* predecessor_lock)
+    const CheckedLock* predecessor_lock,
+    void* flow_terminator)
     : WorkerThread(thread_type_hint,
                    task_tracker,
                    sequence_num,
-                   predecessor_lock),
+                   predecessor_lock,
+                   flow_terminator),
       delegate_(std::move(delegate)) {
   DCHECK(delegate_);
 }
diff --git a/base/task/thread_pool/worker_thread_semaphore.h b/base/task/thread_pool/worker_thread_semaphore.h
index 619de24e..e1065ea 100644
--- a/base/task/thread_pool/worker_thread_semaphore.h
+++ b/base/task/thread_pool/worker_thread_semaphore.h
@@ -38,7 +38,8 @@
                         std::unique_ptr<Delegate> delegate,
                         TrackedRef<TaskTracker> task_tracker,
                         size_t sequence_num,
-                        const CheckedLock* predecessor_lock = nullptr);
+                        const CheckedLock* predecessor_lock = nullptr,
+                        void* flow_terminator = nullptr);
 
   WorkerThreadSemaphore(const WorkerThread&) = delete;
   WorkerThreadSemaphore& operator=(const WorkerThread&) = delete;
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/ViewPrinter.java b/base/test/android/javatests/src/org/chromium/base/test/util/ViewPrinter.java
index 1dfabcc..bc4049b 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/util/ViewPrinter.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/ViewPrinter.java
@@ -31,6 +31,7 @@
         private String mLogTag = "ViewPrinter";
         private boolean mPrintNonVisibleViews;
         private boolean mPrintResourcePackage;
+        public boolean mPrintViewBounds;
 
         public Options setLogTag(String logTag) {
             mLogTag = logTag;
@@ -46,6 +47,11 @@
             mPrintResourcePackage = printResourcePackage;
             return this;
         }
+
+        public Options setPrintViewBounds(boolean printViewBounds) {
+            mPrintViewBounds = printViewBounds;
+            return this;
+        }
     }
 
     /**
@@ -146,6 +152,20 @@
 
         stringBuilder.append(rootView.getClass().getSimpleName());
 
+        if (options.mPrintViewBounds) {
+            int[] locationOnScreen = new int[2];
+            rootView.getLocationOnScreen(locationOnScreen);
+            stringBuilder.append(" | [l ");
+            stringBuilder.append(locationOnScreen[0]);
+            stringBuilder.append(", t ");
+            stringBuilder.append(locationOnScreen[1]);
+            stringBuilder.append(", w ");
+            stringBuilder.append(rootView.getWidth());
+            stringBuilder.append(", h ");
+            stringBuilder.append(rootView.getHeight());
+            stringBuilder.append("]");
+        }
+
         stringBuilder.append('\n');
 
         TreeOutput output = new TreeOutput(stringBuilder.toString());
diff --git a/base/tracing/stdlib/chrome/event_latency_description.sql b/base/tracing/stdlib/chrome/event_latency_description.sql
index ca89dae..edabe76 100644
--- a/base/tracing/stdlib/chrome/event_latency_description.sql
+++ b/base/tracing/stdlib/chrome/event_latency_description.sql
@@ -146,7 +146,7 @@
     'CompositorFrame.'),
   ('ReceiveCompositorFrameToStartDraw',
     'Interval between the first frame received to when all frames (or ' ||
-    'timeouts have occured) and we start drawing. It can be blocked by ' ||
+    'timeouts have occurred) and we start drawing. It can be blocked by ' ||
     'other processes (e.g to draw a toolbar it waiting for information from ' ||
     'the Browser) as it waits for timeouts or frames to be provided. This ' ||
     'is the tree of dependencies that the GPU VizCompositor is waiting for ' ||
diff --git a/base/tracing/stdlib/chrome/scroll_jank/utils.sql b/base/tracing/stdlib/chrome/scroll_jank/utils.sql
index 0184869..74141e6 100644
--- a/base/tracing/stdlib/chrome/scroll_jank/utils.sql
+++ b/base/tracing/stdlib/chrome/scroll_jank/utils.sql
@@ -15,7 +15,7 @@
 -- (1ns) divided by maximum value in denominator, giving 1e-9.
 
 -- Function : function takes scroll ids of frames to verify it's from
--- the same scroll, and makes sure the frame ts occured within the scroll
+-- the same scroll, and makes sure the frame ts occurred within the scroll
 -- timestamp of the neighbour and computes whether the frame was janky or not.
 CREATE PERFETTO FUNCTION _is_janky_frame(cur_gesture_id LONG,
                                       neighbour_gesture_id LONG,
diff --git a/build/config/clang/BUILD.gn b/build/config/clang/BUILD.gn
index 18e80f0..ba00640 100644
--- a/build/config/clang/BUILD.gn
+++ b/build/config/clang/BUILD.gn
@@ -31,12 +31,19 @@
       "-Xclang",
       "check-stack-allocated",
 
+      # TODO(danakj): Enable this after clang update.
+      #"-Xclang",
+      #"-plugin-arg-find-bad-constructs",
+      #"-Xclang",
+      #"check-allow-auto-typedefs",
+
       "-Xclang",
       "-plugin-arg-find-bad-constructs",
       "-Xclang",
       "check-raw-ptr-to-stack-allocated",
 
-      # TODO(https://crbug.com/1504043): Remove when raw_ptr check has been enabled for the dawn repo.
+      # TODO(https://crbug.com/1504043): Remove when raw_ptr check has been
+      # enabled for the dawn repo.
       "-Xclang",
       "-plugin-arg-find-bad-constructs",
       "-Xclang",
diff --git a/build_overrides/build.gni b/build_overrides/build.gni
index 619e86da..16767dc 100644
--- a/build_overrides/build.gni
+++ b/build_overrides/build.gni
@@ -40,7 +40,7 @@
   # TODO(crbug/1006541): Switch to perfetto's client library on all platforms.
   use_perfetto_client_library =
       (is_linux || is_android || (use_blink && is_ios) || is_win ||
-       is_chromeos) && !is_castos
+       is_chromeos || is_mac) && !is_castos
 
   # Limits the defined //third_party/android_deps targets to only "buildCompile"
   # and "buildCompileNoDeps" targets. This is useful for third-party
diff --git a/cc/layers/picture_layer_impl_unittest.cc b/cc/layers/picture_layer_impl_unittest.cc
index 75aaa4e..068e84f 100644
--- a/cc/layers/picture_layer_impl_unittest.cc
+++ b/cc/layers/picture_layer_impl_unittest.cc
@@ -6166,8 +6166,7 @@
   gfx::Size layer_bounds(1000, 1000);
 
   // Set up a raster source with 2 animated images.
-  auto recording_source = FakeRecordingSource::CreateRecordingSource(
-      gfx::Rect(layer_bounds), layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   std::vector<FrameMetadata> frames = {
       FrameMetadata(true, base::Milliseconds(1)),
       FrameMetadata(true, base::Milliseconds(1))};
@@ -6223,8 +6222,7 @@
       PaintWorkletInput::NativePropertyType::kClipPath, ElementId());
 
   // Set up a raster source with a PaintWorkletInput.
-  auto recording_source = FakeRecordingSource::CreateRecordingSource(
-      gfx::Rect(layer_bounds), layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   scoped_refptr<TestPaintWorkletInput> input1 =
       base::MakeRefCounted<TestPaintWorkletInput>(key, gfx::SizeF(100, 100));
   PaintImage image1 = CreatePaintWorkletPaintImage(input1);
@@ -6272,8 +6270,7 @@
   gfx::Size layer_bounds(1000, 1000);
 
   // Set up a raster source with 2 PaintWorkletInputs.
-  auto recording_source = FakeRecordingSource::CreateRecordingSource(
-      gfx::Rect(layer_bounds), layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   scoped_refptr<TestPaintWorkletInput> input1 =
       base::MakeRefCounted<TestPaintWorkletInput>(gfx::SizeF(100, 100));
   PaintImage image1 = CreatePaintWorkletPaintImage(input1);
@@ -6307,8 +6304,7 @@
 
   // Committing new PaintWorkletInputs (in a new raster source) should replace
   // the previous ones.
-  recording_source = FakeRecordingSource::CreateRecordingSource(
-      gfx::Rect(layer_bounds), layer_bounds);
+  recording_source = FakeRecordingSource::Create(layer_bounds);
   scoped_refptr<TestPaintWorkletInput> input3 =
       base::MakeRefCounted<TestPaintWorkletInput>(gfx::SizeF(12, 12));
   PaintImage image3 = CreatePaintWorkletPaintImage(input3);
@@ -6324,8 +6320,7 @@
 TEST_F(LegacySWPictureLayerImplTest, PaintWorkletInputsIdenticalEntries) {
   gfx::Size layer_bounds(1000, 1000);
 
-  auto recording_source = FakeRecordingSource::CreateRecordingSource(
-      gfx::Rect(layer_bounds), layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   scoped_refptr<TestPaintWorkletInput> input =
       base::MakeRefCounted<TestPaintWorkletInput>(gfx::SizeF(100, 100));
   PaintImage image = CreatePaintWorkletPaintImage(input);
diff --git a/cc/layers/recording_source_unittest.cc b/cc/layers/recording_source_unittest.cc
index 59b003f..2bff7d55 100644
--- a/cc/layers/recording_source_unittest.cc
+++ b/cc/layers/recording_source_unittest.cc
@@ -16,20 +16,8 @@
 namespace cc {
 namespace {
 
-std::unique_ptr<FakeRecordingSource> CreateRecordingSource(
-    const gfx::Rect& viewport) {
-  gfx::Rect layer_rect(viewport.right(), viewport.bottom());
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateRecordingSource(viewport, layer_rect.size());
-  return recording_source;
-}
-
 TEST(RecordingSourceTest, DiscardableImagesWithTransform) {
-  gfx::Rect recorded_viewport(256, 256);
-
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(
-          recorded_viewport.size());
+  auto recording_source = FakeRecordingSource::Create(gfx::Size(256, 256));
   PaintImage discardable_image[2][2];
   gfx::Transform identity_transform;
   discardable_image[0][0] = CreateDiscardablePaintImage(gfx::Size(32, 32));
@@ -116,10 +104,7 @@
 }
 
 TEST(RecordingSourceTest, EmptyImages) {
-  gfx::Rect recorded_viewport(0, 0, 256, 256);
-
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      CreateRecordingSource(recorded_viewport);
+  auto recording_source = FakeRecordingSource::Create(gfx::Size(256, 256));
   recording_source->Rerecord();
 
   scoped_refptr<RasterSource> raster_source =
@@ -149,10 +134,7 @@
 }
 
 TEST(RecordingSourceTest, NoDiscardableImages) {
-  gfx::Rect recorded_viewport(0, 0, 256, 256);
-
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      CreateRecordingSource(recorded_viewport);
+  auto recording_source = FakeRecordingSource::Create(gfx::Size(256, 256));
 
   PaintFlags simple_flags;
   simple_flags.setColor(SkColorSetARGB(255, 12, 23, 34));
@@ -199,10 +181,7 @@
 }
 
 TEST(RecordingSourceTest, DiscardableImages) {
-  gfx::Rect recorded_viewport(0, 0, 256, 256);
-
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      CreateRecordingSource(recorded_viewport);
+  auto recording_source = FakeRecordingSource::Create(gfx::Size(256, 256));
 
   PaintImage discardable_image[2][2];
   discardable_image[0][0] = CreateDiscardablePaintImage(gfx::Size(32, 32));
@@ -268,10 +247,7 @@
 }
 
 TEST(RecordingSourceTest, DiscardableImagesBaseNonDiscardable) {
-  gfx::Rect recorded_viewport(0, 0, 512, 512);
-
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      CreateRecordingSource(recorded_viewport);
+  auto recording_source = FakeRecordingSource::Create(gfx::Size(512, 512));
   PaintImage non_discardable_image =
       CreateNonDiscardablePaintImage(gfx::Size(512, 512));
 
@@ -342,8 +318,7 @@
   const std::vector<float> recording_scales = {1.f,   1.25f, 1.33f, 1.5f, 1.6f,
                                                1.66f, 2.f,   2.25f, 2.5f};
   for (float recording_scale : recording_scales) {
-    std::unique_ptr<FakeRecordingSource> recording_source =
-        FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+    auto recording_source = FakeRecordingSource::Create(layer_bounds);
     recording_source->SetRecordingScaleFactor(recording_scale);
 
     PaintFlags solid_flags;
@@ -386,8 +361,7 @@
 }
 
 TEST(RecordingSourceTest, RecordedBounds) {
-  gfx::Size layer_bounds(400, 400);
-  auto recording_source = FakeRecordingSource::Create(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(gfx::Size(400, 400));
   recording_source->add_draw_rect(gfx::Rect(100, 100, 100, 100));
   recording_source->add_draw_rect(gfx::Rect(50, 200, 200, 50));
   recording_source->Rerecord();
diff --git a/cc/paint/skottie_wrapper_unittest.cc b/cc/paint/skottie_wrapper_unittest.cc
index 8f488100..a91c0115 100644
--- a/cc/paint/skottie_wrapper_unittest.cc
+++ b/cc/paint/skottie_wrapper_unittest.cc
@@ -28,6 +28,7 @@
 namespace {
 
 using ::testing::_;
+using ::testing::AtLeast;
 using ::testing::Contains;
 using ::testing::Eq;
 using ::testing::FieldsAre;
@@ -258,9 +259,10 @@
 
   SkottieTextPropertyValueMap text_map = {
       {HashSkottieResourceId(kLottieDataWith2TextNode1),
-       SkottieTextPropertyValue("new-test-text-1", gfx::RectF(1, 1, 1, 1))},
+       SkottieTextPropertyValue("new-test-text-1", gfx::RectF(1, 1, 100, 100))},
       {HashSkottieResourceId(kLottieDataWith2TextNode2),
-       SkottieTextPropertyValue("new-test-text-2", gfx::RectF(2, 2, 2, 2))}};
+       SkottieTextPropertyValue("new-test-text-2",
+                                gfx::RectF(2, 2, 200, 200))}};
   skottie->Draw(&canvas, /*t=*/0, SkRect::MakeWH(500, 500),
                 SkottieWrapper::FrameDataCallback(), SkottieColorMap(),
                 text_map);
@@ -268,14 +270,16 @@
               UnorderedElementsAre(
                   Pair(HashSkottieResourceId(kLottieDataWith2TextNode1),
                        SkottieTextPropertyValue("new-test-text-1",
-                                                gfx::RectF(1, 1, 1, 1))),
+                                                gfx::RectF(1, 1, 100, 100))),
                   Pair(HashSkottieResourceId(kLottieDataWith2TextNode2),
                        SkottieTextPropertyValue("new-test-text-2",
-                                                gfx::RectF(2, 2, 2, 2)))));
+                                                gfx::RectF(2, 2, 200, 200)))));
+  // Check that we've actually drawn some text.
+  EXPECT_CALL(canvas, onDrawGlyphRunList).Times(AtLeast(1));
 
-  text_map = {
-      {HashSkottieResourceId(kLottieDataWith2TextNode2),
-       SkottieTextPropertyValue("new-test-text-2b", gfx::RectF(3, 3, 3, 3))}};
+  text_map = {{HashSkottieResourceId(kLottieDataWith2TextNode2),
+               SkottieTextPropertyValue("new-test-text-2b",
+                                        gfx::RectF(3, 3, 300, 300))}};
   skottie->Draw(&canvas, /*t=*/0.1, SkRect::MakeWH(500, 500),
                 SkottieWrapper::FrameDataCallback(), SkottieColorMap(),
                 text_map);
@@ -283,10 +287,26 @@
               UnorderedElementsAre(
                   Pair(HashSkottieResourceId(kLottieDataWith2TextNode1),
                        SkottieTextPropertyValue("new-test-text-1",
-                                                gfx::RectF(1, 1, 1, 1))),
+                                                gfx::RectF(1, 1, 100, 100))),
                   Pair(HashSkottieResourceId(kLottieDataWith2TextNode2),
                        SkottieTextPropertyValue("new-test-text-2b",
-                                                gfx::RectF(3, 3, 3, 3)))));
+                                                gfx::RectF(3, 3, 300, 300)))));
+
+  // Missing glyphs should not trigger a crash.
+  text_map = {
+      {HashSkottieResourceId(kLottieDataWith2TextNode1),
+       SkottieTextPropertyValue("hello 你好", gfx::RectF(4, 4, 400, 400))}};
+  skottie->Draw(&canvas, /*t=*/0.2, SkRect::MakeWH(500, 500),
+                SkottieWrapper::FrameDataCallback(), SkottieColorMap(),
+                text_map);
+  EXPECT_THAT(skottie->GetCurrentTextPropertyValues(),
+              UnorderedElementsAre(
+                  Pair(HashSkottieResourceId(kLottieDataWith2TextNode1),
+                       SkottieTextPropertyValue("hello 你好",
+                                                gfx::RectF(4, 4, 400, 400))),
+                  Pair(HashSkottieResourceId(kLottieDataWith2TextNode2),
+                       SkottieTextPropertyValue("new-test-text-2b",
+                                                gfx::RectF(3, 3, 300, 300)))));
 }
 
 TEST(SkottieWrapperTest, Marker) {
diff --git a/cc/raster/raster_source_unittest.cc b/cc/raster/raster_source_unittest.cc
index a6470f1..45feecf 100644
--- a/cc/raster/raster_source_unittest.cc
+++ b/cc/raster/raster_source_unittest.cc
@@ -48,8 +48,7 @@
 TEST(RasterSourceTest, AnalyzeIsSolidUnscaled) {
   gfx::Size layer_bounds(400, 400);
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
 
   PaintFlags solid_flags;
   SkColor solid_color = SkColorSetARGB(255, 12, 23, 34);
@@ -119,8 +118,7 @@
   const std::vector<float> recording_scales = {1.25f, 1.33f, 1.5f,  1.6f,
                                                1.66f, 2.f,   2.25f, 2.5f};
   for (float recording_scale : recording_scales) {
-    std::unique_ptr<FakeRecordingSource> recording_source =
-        FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+    auto recording_source = FakeRecordingSource::Create(layer_bounds);
     recording_source->SetRecordingScaleFactor(recording_scale);
 
     PaintFlags solid_flags;
@@ -204,8 +202,7 @@
 TEST(RasterSourceTest, PixelRefIteratorDiscardableRefsOneTile) {
   gfx::Size layer_bounds(512, 512);
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
 
   PaintImage discardable_image[2][2];
   discardable_image[0][0] = CreateDiscardablePaintImage(gfx::Size(32, 32));
@@ -276,8 +273,7 @@
   float contents_scale = 1.5f;
   float raster_divisions = 2.f;
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   recording_source->SetBackgroundColor(SkColors::kBlack);
 
   // Because the caller sets content opaque, it also promises that it
@@ -341,8 +337,7 @@
   gfx::Size layer_bounds(3, 5);
   float raster_divisions = 2.f;
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   recording_source->SetBackgroundColor(SkColors::kBlack);
 
   // Because the caller sets content opaque, it also promises that it
@@ -401,8 +396,7 @@
   gfx::Size layer_bounds(3, 5);
   float contents_scale = 1.5f;
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   recording_source->SetBackgroundColor(SkColors::kGreen);
 
   // First record everything as white.
@@ -475,8 +469,7 @@
 TEST(RasterSourceTest, RasterPartialContentsWithRasterTranslation) {
   gfx::Size layer_bounds(3, 5);
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   recording_source->SetBackgroundColor(SkColors::kGreen);
 
   // First record everything as white.
@@ -566,8 +559,7 @@
   gfx::Size partial_bounds(2, 4);
   float contents_scale = 1.5f;
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   recording_source->SetBackgroundColor(SkColors::kGreen);
   recording_source->SetRequiresClear(true);
 
@@ -604,8 +596,7 @@
       EXPECT_COLOR_EQ(pixel_dark, bitmap.getColor(i, j)) << i << "," << j;
   }
 
-  std::unique_ptr<FakeRecordingSource> recording_source_light =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source_light = FakeRecordingSource::Create(layer_bounds);
   recording_source_light->SetBackgroundColor(SkColors::kGreen);
   recording_source_light->SetRequiresClear(true);
 
@@ -641,8 +632,7 @@
   gfx::Size layer_bounds(5, 3);
   float contents_scale = 0.5f;
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   recording_source->SetBackgroundColor(SkColors::kTransparent);
   recording_source->SetRequiresClear(true);
   recording_source->Rerecord();
@@ -672,8 +662,7 @@
 TEST(RasterSourceTest, RasterTransformWithoutRecordingScale) {
   gfx::Size size(100, 100);
   float recording_scale = 2.f;
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(size);
+  auto recording_source = FakeRecordingSource::Create(size);
   recording_source->Rerecord();
   recording_source->SetRecordingScaleFactor(recording_scale);
   scoped_refptr<RasterSource> raster_source =
diff --git a/cc/resources/resource_pool_unittest.cc b/cc/resources/resource_pool_unittest.cc
index d26ed47..5120af91 100644
--- a/cc/resources/resource_pool_unittest.cc
+++ b/cc/resources/resource_pool_unittest.cc
@@ -76,8 +76,8 @@
   }
 
   viz::TestSharedBitmapManager shared_bitmap_manager_;
-  raw_ptr<MockContextSupport, DanglingUntriaged> context_support_;
   scoped_refptr<viz::TestContextProvider> context_provider_;
+  raw_ptr<MockContextSupport> context_support_;
   std::unique_ptr<viz::ClientResourceProvider> resource_provider_;
   scoped_refptr<base::TestMockTimeTaskRunner> test_task_runner_;
   std::unique_ptr<ResourcePool> resource_pool_;
diff --git a/cc/test/fake_raster_source.cc b/cc/test/fake_raster_source.cc
index 916df77..d916247 100644
--- a/cc/test/fake_raster_source.cc
+++ b/cc/test/fake_raster_source.cc
@@ -20,22 +20,6 @@
 
 namespace cc {
 
-namespace {
-
-std::unique_ptr<FakeRecordingSource> CreateFilledRecordingSource(
-    gfx::Size size) {
-  if (base::FeatureList::IsEnabled(features::kUseRecordedBoundsForTiling)) {
-    // This function actually just creates an empty recording source.
-    // Whether the recording source is filled depends on the DisplayItemList.
-    // TODO(crbug.com/1517714): Remove this function when cleaning up the
-    // feature.
-    return FakeRecordingSource::Create(size);
-  }
-  return FakeRecordingSource::CreateFilledRecordingSource(size);
-}
-
-}  // namespace
-
 scoped_refptr<FakeRasterSource> FakeRasterSource::CreateInfiniteFilled() {
   gfx::Size size(std::numeric_limits<int>::max() / 10,
                  std::numeric_limits<int>::max() / 10);
@@ -44,7 +28,7 @@
 
 scoped_refptr<FakeRasterSource> FakeRasterSource::CreateFilled(
     const gfx::Size& size) {
-  auto recording_source = CreateFilledRecordingSource(size);
+  auto recording_source = FakeRecordingSource::Create(size);
 
   PaintFlags red_flags;
   red_flags.setColor(SK_ColorRED);
@@ -71,7 +55,7 @@
 
 scoped_refptr<FakeRasterSource> FakeRasterSource::CreateFilledWithImages(
     const gfx::Size& size) {
-  auto recording_source = CreateFilledRecordingSource(size);
+  auto recording_source = FakeRecordingSource::Create(size);
 
   for (int y = 0; y < size.height(); y += 100) {
     for (int x = 0; x < size.width(); x += 100) {
@@ -85,7 +69,7 @@
 
 scoped_refptr<FakeRasterSource> FakeRasterSource::CreateFilledWithText(
     const gfx::Size& size) {
-  auto recording_source = CreateFilledRecordingSource(size);
+  auto recording_source = FakeRecordingSource::Create(size);
   recording_source->add_draw_rect(gfx::Rect(size));
   recording_source->set_has_draw_text_op();
   recording_source->Rerecord();
@@ -94,7 +78,7 @@
 
 scoped_refptr<FakeRasterSource> FakeRasterSource::CreateFilledWithPaintWorklet(
     const gfx::Size& size) {
-  auto recording_source = CreateFilledRecordingSource(size);
+  auto recording_source = FakeRecordingSource::Create(size);
 
   auto input = base::MakeRefCounted<TestPaintWorkletInput>(gfx::SizeF(size));
   recording_source->add_draw_image(
@@ -106,7 +90,7 @@
 
 scoped_refptr<FakeRasterSource> FakeRasterSource::CreateFilledSolidColor(
     const gfx::Size& size) {
-  auto recording_source = CreateFilledRecordingSource(size);
+  auto recording_source = FakeRecordingSource::Create(size);
 
   PaintFlags red_flags;
   red_flags.setColor(SK_ColorRED);
@@ -126,10 +110,7 @@
   DCHECK(!size.IsEmpty());
   DCHECK(!recorded_bounds.IsEmpty());
   DCHECK(gfx::Rect(size).Contains(recorded_bounds));
-  auto recording_source =
-      base::FeatureList::IsEnabled(features::kUseRecordedBoundsForTiling)
-          ? FakeRecordingSource::Create(size)
-          : FakeRecordingSource::CreateRecordingSource(recorded_bounds, size);
+  auto recording_source = FakeRecordingSource::Create(size);
 
   PaintFlags red_flags;
   red_flags.setColor(SK_ColorRED);
@@ -142,16 +123,12 @@
   recording_source->add_draw_rect_with_flags(smaller_rect, green_flags);
 
   recording_source->Rerecord();
-  if (!base::FeatureList::IsEnabled(features::kUseRecordedBoundsForTiling)) {
-    recording_source->SetRecordedViewport(recorded_bounds);
-  }
-
   return base::WrapRefCounted(new FakeRasterSource(recording_source.get()));
 }
 
 scoped_refptr<FakeRasterSource> FakeRasterSource::CreateEmpty(
     const gfx::Size& size) {
-  auto recording_source = CreateFilledRecordingSource(size);
+  auto recording_source = FakeRecordingSource::Create(size);
   return base::WrapRefCounted(new FakeRasterSource(recording_source.get()));
 }
 
diff --git a/cc/test/fake_recording_source.h b/cc/test/fake_recording_source.h
index 174cb31..d9adf6f 100644
--- a/cc/test/fake_recording_source.h
+++ b/cc/test/fake_recording_source.h
@@ -33,33 +33,6 @@
     return recording_source;
   }
 
-  // TODO(crbug.com/1517714): Remove the following two functions. The
-  // DisplayItemList determines whether the recording source fills the layer.
-
-  static std::unique_ptr<FakeRecordingSource> CreateRecordingSource(
-      const gfx::Rect& recorded_viewport,
-      const gfx::Size& layer_bounds) {
-    auto recording_source = std::make_unique<FakeRecordingSource>();
-    recording_source->SetCanUseRecordedBounds(false);
-    recording_source->SetRecordedViewport(recorded_viewport);
-    recording_source->SetLayerBounds(layer_bounds);
-    return recording_source;
-  }
-
-  static std::unique_ptr<FakeRecordingSource> CreateFilledRecordingSource(
-      const gfx::Size& layer_bounds) {
-    auto recording_source = std::make_unique<FakeRecordingSource>();
-    recording_source->SetCanUseRecordedBounds(false);
-    recording_source->SetRecordedViewport(gfx::Rect(layer_bounds));
-    recording_source->SetLayerBounds(layer_bounds);
-    return recording_source;
-  }
-
-  void SetRecordedViewport(const gfx::Rect& recorded_viewport) {
-    DCHECK(!can_use_recorded_bounds_);
-    recorded_bounds_ = recorded_viewport;
-  }
-
   void SetLayerBounds(const gfx::Size& layer_bounds) {
     size_ = layer_bounds;
     client_.set_bounds(layer_bounds);
diff --git a/cc/test/test_skcanvas.h b/cc/test/test_skcanvas.h
index c537e2b..5d31f16 100644
--- a/cc/test/test_skcanvas.h
+++ b/cc/test/test_skcanvas.h
@@ -76,6 +76,8 @@
   MOCK_METHOD2(didTranslate, void(SkScalar, SkScalar));
   MOCK_METHOD2(onDrawOval, void(const SkRect&, const SkPaint&));
   MOCK_METHOD2(onCustomCallback, void(SkCanvas*, uint32_t));
+  MOCK_METHOD2(onDrawGlyphRunList,
+               void(const sktext::GlyphRunList&, const SkPaint&));
 
   sk_sp<GrDirectContext> context_;
 };
diff --git a/cc/tiles/picture_layer_tiling_set_unittest.cc b/cc/tiles/picture_layer_tiling_set_unittest.cc
index fdad8a9..a43833e1 100644
--- a/cc/tiles/picture_layer_tiling_set_unittest.cc
+++ b/cc/tiles/picture_layer_tiling_set_unittest.cc
@@ -56,39 +56,6 @@
   return CreateTilingSetWithSettings(client, LayerTreeSettings());
 }
 
-TEST(PictureLayerTilingSetTest, NoResources) {
-  FakePictureLayerTilingClient client;
-  gfx::Size layer_bounds(1000, 800);
-  std::unique_ptr<TestablePictureLayerTilingSet> set = CreateTilingSet(&client);
-  client.SetTileSize(gfx::Size(256, 256));
-
-  scoped_refptr<FakeRasterSource> raster_source =
-      FakeRasterSource::CreateEmpty(layer_bounds);
-
-  set->AddTiling(gfx::AxisTransform2d(), raster_source);
-  set->AddTiling(gfx::AxisTransform2d(1.5, gfx::Vector2dF()), raster_source);
-  set->AddTiling(gfx::AxisTransform2d(2.0, gfx::Vector2dF()), raster_source);
-
-  float contents_scale = 2.0;
-  gfx::Size content_bounds(
-      gfx::ScaleToCeiledSize(layer_bounds, contents_scale));
-  gfx::Rect content_rect(content_bounds);
-
-  Region remaining(content_rect);
-  PictureLayerTilingSet::CoverageIterator iter(set.get(), contents_scale,
-                                               content_rect, contents_scale);
-  for (; iter; ++iter) {
-    gfx::Rect geometry_rect = iter.geometry_rect();
-    EXPECT_TRUE(content_rect.Contains(geometry_rect));
-    ASSERT_TRUE(remaining.Contains(geometry_rect));
-    remaining.Subtract(geometry_rect);
-
-    // No tiles have resources, so no iter represents a real tile.
-    EXPECT_FALSE(*iter);
-  }
-  EXPECT_TRUE(remaining.IsEmpty());
-}
-
 TEST(PictureLayerTilingSetTest, TilingRange) {
   FakePictureLayerTilingClient client;
   gfx::Size layer_bounds(10, 10);
@@ -526,7 +493,7 @@
 
   gfx::Size layer_bounds(100, 105);
   scoped_refptr<FakeRasterSource> raster_source =
-      FakeRasterSource::CreateEmpty(layer_bounds);
+      FakeRasterSource::CreateFilled(layer_bounds);
 
   // Tilings can be added of any scale, the tiling client can controls this.
   pending_set->AddTiling(gfx::AxisTransform2d(), raster_source);
diff --git a/cc/tiles/picture_layer_tiling_unittest.cc b/cc/tiles/picture_layer_tiling_unittest.cc
index 8b63080f..aeda364 100644
--- a/cc/tiles/picture_layer_tiling_unittest.cc
+++ b/cc/tiles/picture_layer_tiling_unittest.cc
@@ -1142,7 +1142,7 @@
 
   client_.SetTileSize(tile_size);
   scoped_refptr<FakeRasterSource> raster_source =
-      FakeRasterSource::CreateEmpty(layer_size);
+      FakeRasterSource::CreateFilled(layer_size);
   tiling_ = TestablePictureLayerTiling::Create(
       PENDING_TREE, gfx::AxisTransform2d(contents_scale, gfx::Vector2dF()),
       raster_source, &client_, LayerTreeSettings());
diff --git a/cc/tiles/tile_manager_unittest.cc b/cc/tiles/tile_manager_unittest.cc
index 6c9959d..d724a3ff 100644
--- a/cc/tiles/tile_manager_unittest.cc
+++ b/cc/tiles/tile_manager_unittest.cc
@@ -1674,8 +1674,7 @@
   gfx::Size size(10, 10);
   const gfx::Size layer_bounds(1000, 1000);
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
 
   PaintFlags solid_flags;
   SkColor4f solid_color{0.1f, 0.2f, 0.3f, 1.0f};
@@ -2069,8 +2068,7 @@
     surface->getCanvas()->clear(SK_ColorBLUE);
     sk_sp<SkImage> blue_image = surface->makeImageSnapshot();
 
-    std::unique_ptr<FakeRecordingSource> recording_source =
-        FakeRecordingSource::CreateFilledRecordingSource(size);
+    auto recording_source = FakeRecordingSource::Create(size);
     recording_source->SetBackgroundColor(SkColors::kTransparent);
     recording_source->SetRequiresClear(true);
     PaintFlags flags;
@@ -2165,8 +2163,7 @@
   EXPECT_TRUE(host_impl()->use_gpu_rasterization());
 
   // Active tree has no non-solid tiles, so it will generate no tile tasks.
-  std::unique_ptr<FakeRecordingSource> active_tree_recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto active_tree_recording_source = FakeRecordingSource::Create(layer_bounds);
 
   PaintFlags solid_flags;
   SkColor solid_color = SkColorSetARGB(255, 12, 23, 34);
@@ -2177,8 +2174,8 @@
   active_tree_recording_source->Rerecord();
 
   // Pending tree has non-solid tiles, so it will generate tile tasks.
-  std::unique_ptr<FakeRecordingSource> pending_tree_recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto pending_tree_recording_source =
+      FakeRecordingSource::Create(layer_bounds);
   SkColor non_solid_color = SkColorSetARGB(128, 45, 56, 67);
   PaintFlags non_solid_flags;
   non_solid_flags.setColor(non_solid_color);
@@ -2396,8 +2393,7 @@
 
   const gfx::Size layer_bounds(500, 500);
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   recording_source->set_fill_with_nonsolid_color(true);
 
   int dimension = 250;
@@ -2609,8 +2605,7 @@
 
     const gfx::Size layer_bounds(1000, 1000);
 
-    solid_color_recording_source_ =
-        FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+    solid_color_recording_source_ = FakeRecordingSource::Create(layer_bounds);
 
     PaintFlags solid_flags;
     SkColor solid_color = SkColorSetARGB(255, 12, 23, 34);
@@ -2620,8 +2615,7 @@
 
     solid_color_recording_source_->Rerecord();
 
-    recording_source_ =
-        FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+    recording_source_ = FakeRecordingSource::Create(layer_bounds);
     SkColor non_solid_color = SkColorSetARGB(128, 45, 56, 67);
     PaintFlags non_solid_flags;
     non_solid_flags.setColor(non_solid_color);
@@ -3243,8 +3237,7 @@
        NoImageDecodeDependencyForCheckeredTiles) {
   const gfx::Size layer_bounds(512, 512);
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   recording_source->set_fill_with_nonsolid_color(true);
 
   auto generator =
@@ -3297,8 +3290,7 @@
 TEST_F(EmptyCacheTileManagerTest, AtRasterOnScreenTileRasterTasks) {
   const gfx::Size layer_bounds(500, 500);
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   recording_source->set_fill_with_nonsolid_color(true);
 
   int dimension = 500;
@@ -3333,8 +3325,7 @@
 TEST_F(EmptyCacheTileManagerTest, AtRasterPrepaintTileRasterTasksSkipped) {
   const gfx::Size layer_bounds(500, 500);
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   recording_source->set_fill_with_nonsolid_color(true);
 
   int dimension = 500;
@@ -3369,8 +3360,7 @@
 TEST_F(CheckerImagingTileManagerTest, BuildsImageDecodeQueueAsExpected) {
   const gfx::Size layer_bounds(900, 900);
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   recording_source->set_fill_with_nonsolid_color(true);
 
   int dimension = 450;
@@ -3533,8 +3523,7 @@
        TileManagerCleanupClearsCheckerImagedDecodes) {
   const gfx::Size layer_bounds(512, 512);
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   recording_source->set_fill_with_nonsolid_color(true);
   PaintImage image = MakeCheckerablePaintImage(gfx::Size(512, 512));
   recording_source->add_draw_image(image, gfx::Point(0, 0));
@@ -3572,8 +3561,7 @@
        TileManagerCorrectlyPrioritizesCheckerImagedDecodes) {
   gfx::Size layer_bounds(500, 500);
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   recording_source->set_fill_with_nonsolid_color(true);
   PaintImage image = MakeCheckerablePaintImage(gfx::Size(512, 512));
   recording_source->add_draw_image(image, gfx::Point(0, 0));
@@ -3654,8 +3642,7 @@
 TEST_F(CheckerImagingTileManagerMemoryTest, AddsAllNowTilesToImageDecodeQueue) {
   const gfx::Size layer_bounds(900, 1400);
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   recording_source->set_fill_with_nonsolid_color(true);
 
   int dimension = 450;
@@ -3883,8 +3870,7 @@
 
   // Add images to a fake recording source.
   const gfx::Size layer_bounds(1000, 500);
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_bounds);
+  auto recording_source = FakeRecordingSource::Create(layer_bounds);
   recording_source->set_fill_with_nonsolid_color(true);
   recording_source->add_draw_image(image1, gfx::Point(0, 0));
   recording_source->add_draw_image(image2, gfx::Point(700, 0));
@@ -3958,8 +3944,7 @@
 
     // Add images to a fake recording source.
     constexpr gfx::Size kLayerBounds(1000, 500);
-    auto recording_source =
-        FakeRecordingSource::CreateFilledRecordingSource(kLayerBounds);
+    auto recording_source = FakeRecordingSource::Create(kLayerBounds);
     recording_source->set_fill_with_nonsolid_color(true);
     recording_source->add_draw_image(hdr_image, gfx::Point(0, 0));
     recording_source->Rerecord();
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index d550d730..0c4aa51 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -16542,8 +16542,7 @@
   CreateHostImpl(settings, CreateLayerTreeFrameSink());
   gfx::Size layer_size = gfx::Size(750, 750);
 
-  std::unique_ptr<FakeRecordingSource> recording_source =
-      FakeRecordingSource::CreateFilledRecordingSource(layer_size);
+  auto recording_source = FakeRecordingSource::Create(layer_size);
   PaintImage checkerable_image =
       PaintImageBuilder::WithCopy(
           CreateDiscardablePaintImage(gfx::Size(500, 500)))
diff --git a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
index 21a6bc2d..543a3b4 100644
--- a/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
+++ b/chrome/android/features/start_surface/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
@@ -123,7 +123,8 @@
                         new ExploreSurfaceActionDelegate(
                                 snackbarManager,
                                 BookmarkModel.getForProfile(profile),
-                                tabModelSelector),
+                                tabModelSelector,
+                                bottomSheetController),
                         HelpAndFeedbackLauncherImpl.getForProfile(profile),
                         tabStripHeightSupplier);
 
@@ -188,7 +189,8 @@
         ExploreSurfaceActionDelegate(
                 SnackbarManager snackbarManager,
                 BookmarkModel bookmarkModel,
-                TabModelSelector tabModelSelector) {
+                TabModelSelector tabModelSelector,
+                BottomSheetController bottomSheetController) {
             super(
                     mActivity,
                     snackbarManager,
@@ -196,7 +198,8 @@
                     bookmarkModel,
                     BrowserUiUtils.HostSurface.START_SURFACE,
                     tabModelSelector,
-                    mProfile);
+                    mProfile,
+                    bottomSheetController);
         }
 
         @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDelegateFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDelegateFactory.java
index 05d6f973..1332f67 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDelegateFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/TabbedModeTabDelegateFactory.java
@@ -153,11 +153,13 @@
     public ContextMenuPopulatorFactory createContextMenuPopulatorFactory(Tab tab) {
         return new ChromeContextMenuPopulatorFactory(
                 new TabContextMenuItemDelegate(
+                        mActivity,
                         tab,
                         mTabModelSelectorSupplier.get(),
                         mEphemeralTabCoordinatorSupplier,
                         mContextMenuCopyLinkObserver,
-                        mSnackbarManagerSupplier),
+                        mSnackbarManagerSupplier,
+                        () -> mBottomSheetController),
                 mShareDelegateSupplier,
                 ChromeContextMenuPopulator.ContextMenuMode.NORMAL,
                 ExternalAuthUtils.getInstance());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActionDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActionDelegateImpl.java
index fc054ed..950950a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActionDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActionDelegateImpl.java
@@ -4,7 +4,7 @@
 
 package org.chromium.chrome.browser.app.creator;
 
-import android.content.Context;
+import android.app.Activity;
 
 import androidx.annotation.StringRes;
 
@@ -35,23 +35,26 @@
 public class CreatorActionDelegateImpl implements FeedActionDelegate {
     private static final String TAG = "Cormorant";
 
-    private final Context mActivityContext;
+    private final Activity mActivity;
     private final Profile mProfile;
     private final SnackbarManager mSnackbarManager;
     private final CreatorCoordinator mCreatorCoordinator;
     private final int mParentId;
+    private final BottomSheetController mBottomSheetController;
 
     public CreatorActionDelegateImpl(
-            Context activityContext,
+            Activity activity,
             Profile profile,
             SnackbarManager snackbarManager,
             CreatorCoordinator creatorCoordinator,
-            int parentId) {
-        mActivityContext = activityContext;
+            int parentId,
+            BottomSheetController bottomSheetController) {
+        mActivity = activity;
         mProfile = profile;
         mSnackbarManager = snackbarManager;
         mCreatorCoordinator = creatorCoordinator;
         mParentId = parentId;
+        mBottomSheetController = bottomSheetController;
     }
 
     @Override
@@ -93,19 +96,20 @@
                 () -> {
                     assert ThreadUtils.runningOnUiThread();
                     BookmarkUtils.addToReadingList(
-                            new GURL(url),
-                            title,
-                            mSnackbarManager,
+                            mActivity,
                             bookmarkModel,
-                            mActivityContext,
-                            mProfile);
+                            title,
+                            new GURL(url),
+                            mSnackbarManager,
+                            mProfile,
+                            mBottomSheetController);
                 });
     }
 
     @Override
     public void showSyncConsentActivity(int signinAccessPoint) {
         SyncConsentActivityLauncherImpl.get()
-                .launchActivityForPromoDefaultFlow(mActivityContext, signinAccessPoint, null);
+                .launchActivityForPromoDefaultFlow(mActivity, signinAccessPoint, null);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActivity.java
index 4c967314..ea1daab4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/creator/CreatorActivity.java
@@ -141,7 +141,12 @@
         mShareDelegateSupplier.set(shareDelegate);
         mCreatorActionDelegate =
                 new CreatorActionDelegateImpl(
-                        this, mProfile, getSnackbarManager(), coordinator, mParentTabId);
+                        this,
+                        mProfile,
+                        getSnackbarManager(),
+                        coordinator,
+                        mParentTabId,
+                        mBottomSheetController);
 
         coordinator.queryFeedStream(
                 mCreatorActionDelegate,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/feed/FeedActionDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/app/feed/FeedActionDelegateImpl.java
index e9a7506..4eb5bb0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/feed/FeedActionDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/feed/FeedActionDelegateImpl.java
@@ -4,7 +4,7 @@
 
 package org.chromium.chrome.browser.app.feed;
 
-import android.content.Context;
+import android.app.Activity;
 import android.content.Intent;
 
 import org.chromium.base.Callback;
@@ -47,28 +47,31 @@
     private static final String NEW_TAB_URL_HELP = "https://support.google.com/chrome/?p=new_tab";
     private final NativePageNavigationDelegate mNavigationDelegate;
     private final BookmarkModel mBookmarkModel;
-    private final Context mActivityContext;
+    private final Activity mActivity;
     private final SnackbarManager mSnackbarManager;
     private final TabModelSelector mTabModelSelector;
     private final Profile mProfile;
+    private final BottomSheetController mBottomSheetController;
 
     @BrowserUiUtils.HostSurface private int mHostSurface;
 
     public FeedActionDelegateImpl(
-            Context activityContext,
+            Activity activity,
             SnackbarManager snackbarManager,
             NativePageNavigationDelegate navigationDelegate,
             BookmarkModel bookmarkModel,
             @BrowserUiUtils.HostSurface int hostSurface,
             TabModelSelector tabModelSelector,
-            Profile profile) {
-        mActivityContext = activityContext;
+            Profile profile,
+            BottomSheetController bottomSheetController) {
+        mActivity = activity;
         mNavigationDelegate = navigationDelegate;
         mBookmarkModel = bookmarkModel;
         mSnackbarManager = snackbarManager;
         mHostSurface = hostSurface;
         mTabModelSelector = tabModelSelector;
         mProfile = profile;
+        mBottomSheetController = bottomSheetController;
     }
 
     @Override
@@ -138,12 +141,13 @@
                 () -> {
                     assert ThreadUtils.runningOnUiThread();
                     BookmarkUtils.addToReadingList(
-                            new GURL(url),
-                            title,
-                            mSnackbarManager,
+                            mActivity,
                             mBookmarkModel,
-                            mActivityContext,
-                            mProfile);
+                            title,
+                            new GURL(url),
+                            mSnackbarManager,
+                            mProfile,
+                            mBottomSheetController);
                 });
     }
 
@@ -155,18 +159,18 @@
 
         assert ThreadUtils.runningOnUiThread();
         Class<?> creatorActivityClass = CreatorActivity.class;
-        Intent intent = new Intent(mActivityContext, creatorActivityClass);
+        Intent intent = new Intent(mActivity, creatorActivityClass);
         intent.putExtra(CreatorIntentConstants.CREATOR_WEB_FEED_ID, webFeedName.getBytes());
         intent.putExtra(CreatorIntentConstants.CREATOR_ENTRY_POINT, entryPoint);
         intent.putExtra(CreatorIntentConstants.CREATOR_TAB_ID, mTabModelSelector.getCurrentTabId());
-        mActivityContext.startActivity(intent);
+        mActivity.startActivity(intent);
     }
 
     @Override
     public void showSyncConsentActivity(@SigninAccessPoint int signinAccessPoint) {
         if (ChromeFeatureList.isEnabled(ChromeFeatureList.FEED_SHOW_SIGN_IN_COMMAND)) {
             SyncConsentActivityLauncherImpl.get()
-                    .launchActivityIfAllowed(mActivityContext, signinAccessPoint);
+                    .launchActivityIfAllowed(mActivity, signinAccessPoint);
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
index 84ebeb0..d840818 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
@@ -14,6 +14,7 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.LocaleList;
+import android.os.Looper;
 import android.provider.Browser;
 import android.text.TextUtils;
 
@@ -64,6 +65,7 @@
 import org.chromium.components.embedder_support.util.UrlConstants;
 import org.chromium.components.feature_engagement.EventConstants;
 import org.chromium.components.profile_metrics.BrowserProfileType;
+import org.chromium.components.signin.identitymanager.IdentityManager;
 import org.chromium.ui.UiUtils;
 import org.chromium.ui.base.DeviceFormFactor;
 import org.chromium.url.GURL;
@@ -123,53 +125,61 @@
         showSaveFlow(
                 activity,
                 bottomSheetController,
-                fromExplicitTrackUi,
+                tab.getProfile(),
                 newBookmarkId,
+                fromExplicitTrackUi,
                 /* wasBookmarkMoved= */ false,
-                /* isNewBookmark= */ true,
-                tab.getProfile());
+                /* isNewBookmark= */ true);
         callback.onResult(newBookmarkId);
     }
 
     /**
-     * Shows the bookmark save flow.
+     * Shows the bookmark save flow with the given {@link BookmarkId}.
      *
      * @param activity The current Activity.
      * @param bottomSheetController The BottomSheetController, used to show the save flow.
-     * @param fromExplicitTrackUi Whether the bookmark was added from the explicit UI.
-     * @param bookmarkId The BookmarkId to show the save flow for. Can be null in some cases.
+     * @param profile The profile currently used.
+     * @param bookmarkId The BookmarkId to show the save flow for. Can be null in some cases (e.g. a
+     *     bookmark fails to be added) and in this case, the function writes to logcat and exits
+     *     without any action.
+     * @param fromExplicitTrackUi Whether the bookmark was added from the explicit UI (e.g. the
+     *     price-track menu item).
      * @param wasBookmarkMoved Whether the save flow is shown as a result of a moved bookmark.
      * @param isNewBookmark Whether the bookmark is newly created.
-     * @param profile The profile currently used.
      */
-    public static void showSaveFlow(
+    static void showSaveFlow(
             @NonNull Activity activity,
             @NonNull BottomSheetController bottomSheetController,
-            boolean fromExplicitTrackUi,
+            @NonNull Profile profile,
             @Nullable BookmarkId bookmarkId,
+            boolean fromExplicitTrackUi,
             boolean wasBookmarkMoved,
-            boolean isNewBookmark,
-            @NonNull Profile profile) {
+            boolean isNewBookmark) {
         if (bookmarkId == null) {
             Log.e(TAG, "Null bookmark found when showing the save flow, aborting.");
             return;
         }
 
         ShoppingService shoppingService = ShoppingServiceFactory.getForProfile(profile);
+        UserEducationHelper userEducationHelper =
+                new UserEducationHelper(activity, new Handler(Looper.myLooper()));
+        IdentityManager identityManager =
+                IdentityServicesProvider.get().getIdentityManager(profile);
 
         BookmarkSaveFlowCoordinator bookmarkSaveFlowCoordinator =
                 new BookmarkSaveFlowCoordinator(
                         activity,
                         bottomSheetController,
                         shoppingService,
-                        new UserEducationHelper(activity, new Handler()),
+                        userEducationHelper,
                         profile,
-                        IdentityServicesProvider.get().getIdentityManager(profile));
+                        identityManager);
         bookmarkSaveFlowCoordinator.show(
                 bookmarkId, fromExplicitTrackUi, wasBookmarkMoved, isNewBookmark);
     }
 
     // The legacy code path to add or edit bookmark without triggering the bookmark bottom sheet.
+    // Used for feed and GTS.
     private static BookmarkId addBookmarkAndShowSnackbar(
             BookmarkModel bookmarkModel,
             Tab tab,
@@ -177,14 +187,9 @@
             Activity activity,
             boolean fromCustomTab,
             @BookmarkType int bookmarkType) {
+        BookmarkId parentId = null;
         if (bookmarkType == BookmarkType.READING_LIST) {
-            return addToReadingList(
-                    tab.getOriginalUrl(),
-                    tab.getTitle(),
-                    snackbarManager,
-                    bookmarkModel,
-                    activity,
-                    tab.getProfile());
+            parentId = bookmarkModel.getDefaultReadingListFolder();
         }
         BookmarkId bookmarkId =
                 addBookmarkInternal(
@@ -192,7 +197,7 @@
                         bookmarkModel,
                         tab.getTitle(),
                         tab.getOriginalUrl(),
-                        /* parent= */ null,
+                        /* parent= */ parentId,
                         BookmarkType.NORMAL);
 
         if (bookmarkId != null && bookmarkId.getType() == BookmarkType.NORMAL) {
@@ -263,36 +268,63 @@
      * overwritten. After successful addition, a snackbar will be shown notifying the user about the
      * result of the operation.
      *
-     * @param url The associated URL.
+     * @param activity The associated activity which is adding this reading list item.
+     * @param bookmarkModel The bookmark model that talks to the bookmark backend.
      * @param title The title of the reading list item being added.
+     * @param url The associated URL.
      * @param snackbarManager The snackbar manager that will be used to show a snackbar.
-     * @param bookmarkBridge The bookmark bridge that talks to the bookmark backend.
-     * @param context The associated context.
-     * @return The bookmark ID created after saving the article to the reading list.
      * @param profile The profile currently used.
+     * @param bottomSheetController The {@link BottomSheetController} which is used to show the
+     *     BookmarkSaveFlow.
+     * @return The bookmark ID created after saving the article to the reading list.
+     * @deprecated Used only by feed, new users should rely on addOrEditBookmark (or the tab
+     *     bookmarker).
      */
+    @Deprecated
     public static BookmarkId addToReadingList(
-            GURL url,
-            String title,
-            SnackbarManager snackbarManager,
-            BookmarkBridge bookmarkBridge,
-            Context context,
-            @NonNull Profile profile) {
-        assert bookmarkBridge.isBookmarkModelLoaded();
-        BookmarkId bookmarkId = bookmarkBridge.addToDefaultReadingList(title, url);
+            @NonNull Activity activity,
+            @NonNull BookmarkModel bookmarkModel,
+            @NonNull String title,
+            @NonNull GURL url,
+            @NonNull SnackbarManager snackbarManager,
+            @NonNull Profile profile,
+            @NonNull BottomSheetController bottomSheetController) {
+        assert bookmarkModel.isBookmarkModelLoaded();
+        BookmarkId bookmarkId =
+                addBookmarkInternal(
+                        activity,
+                        bookmarkModel,
+                        title,
+                        url,
+                        bookmarkModel.getDefaultReadingListFolder(),
+                        BookmarkType.READING_LIST);
+        if (bookmarkId == null) {
+            return null;
+        }
 
-        if (bookmarkId != null) {
+        // Reading list is aligned with the bookmark save flow used by all other bookmark saves.
+        // This is bundled with account bookmarks to modernize the infra.
+        if (BookmarkFeatures.isBookmarksAccountStorageEnabled()) {
+            showSaveFlow(
+                    activity,
+                    bottomSheetController,
+                    profile,
+                    bookmarkId,
+                    /* fromExplicitTrackUi= */ false,
+                    /* wasBookmarkMoved= */ false,
+                    /* isNewBookmark= */ true);
+        } else {
             Snackbar snackbar =
                     Snackbar.make(
-                            context.getString(R.string.reading_list_saved),
+                            activity.getString(R.string.reading_list_saved),
                             new SnackbarController() {},
                             Snackbar.TYPE_ACTION,
                             Snackbar.UMA_READING_LIST_BOOKMARK_ADDED);
             snackbarManager.showSnackbar(snackbar);
-
-            TrackerFactory.getTrackerForProfile(profile)
-                    .notifyEvent(EventConstants.READ_LATER_ARTICLE_SAVED);
         }
+
+        TrackerFactory.getTrackerForProfile(profile)
+                .notifyEvent(EventConstants.READ_LATER_ARTICLE_SAVED);
         return bookmarkId;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/TabBookmarker.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/TabBookmarker.java
index 4f356e2..8c83504 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/TabBookmarker.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/TabBookmarker.java
@@ -91,11 +91,11 @@
             BookmarkUtils.showSaveFlow(
                     mActivity,
                     mBottomSheetControllerSupplier.get(),
-                    /* fromExplicitTrackUi= */ true,
+                    currentTab.getProfile(),
                     bookmarkId,
+                    /* fromExplicitTrackUi= */ true,
                     /* wasBookmarkMoved= */ false,
-                    /* isNewBookmark= */ false,
-                    currentTab.getProfile());
+                    /* isNewBookmark= */ false);
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java
index 9b1ee1d3..5cfba54 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDelegateFactory.java
@@ -51,6 +51,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.browser.ui.native_page.NativePage;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.util.BrowserControlsVisibilityDelegate;
 import org.chromium.components.browser_ui.util.ComposedBrowserControlsVisibilityDelegate;
 import org.chromium.components.embedder_support.delegate.WebContentsDelegateAndroid;
@@ -239,8 +240,11 @@
     private final Supplier<TabModelSelector> mTabModelSelectorSupplier;
     private final Supplier<CompositorViewHolder> mCompositorViewHolderSupplier;
     private final Supplier<ModalDialogManager> mModalDialogManagerSupplier;
+    // Should only be used after inflation.
     private final Lazy<SnackbarManager> mSnackbarManager;
     private final Supplier<ShareDelegate> mShareDelegateSupplier;
+    // Should only be used after inflation.
+    private final Lazy<BottomSheetController> mBottomSheetController;
 
     private TabWebContentsDelegateAndroid mWebContentsDelegateAndroid;
     private ExternalNavigationDelegateImpl mNavigationDelegate;
@@ -251,16 +255,16 @@
      * @param shouldHideBrowserControls Whether or not the browser controls may auto-hide.
      * @param isOpenedByChrome Whether the CustomTab was originally opened by Chrome.
      * @param webApkScopeUrl The URL of the WebAPK web manifest scope. Null if the delegate is not
-     *                       for a WebAPK.
-     * @param displayMode  The activity's display mode.
+     *     for a WebAPK.
+     * @param displayMode The activity's display mode.
      * @param shouldEnableEmbeddedMediaExperience Whether embedded media experience is enabled.
      * @param visibilityDelegate The delegate that handles browser control visibility associated
-     *                           with browser actions (as opposed to tab state).
+     *     with browser actions (as opposed to tab state).
      * @param authUtils To determine whether apps are Google signed.
      * @param multiWindowUtils To use to determine which ChromeTabbedActivity to open new tabs in.
      * @param verifier Decides how to handle navigation to a new URL.
      * @param ephemeralTabCoordinatorSupplier A provider of {@link EphemeralTabCoordinator} that
-     *                                        shows preview tab.
+     *     shows preview tab.
      * @param chromeActivityNativeDelegate Delegate for native initialziation.
      * @param browserControlsStateProvider Provides state of the browser controls.
      * @param fullscreenManager Manages the fullscreen state.
@@ -271,6 +275,7 @@
      * @param snackbarManager Manages the snackbar.
      * @param shareDelegateSupplier Supplies the share delegate.
      * @param activityType The type of the current activity.
+     * @param bottomSheetController Controls the bottom sheet.
      */
     private CustomTabDelegateFactory(
             Activity activity,
@@ -293,7 +298,8 @@
             Supplier<ModalDialogManager> modalDialogManagerSupplier,
             Lazy<SnackbarManager> snackbarManager,
             Supplier<ShareDelegate> shareDelegateSupplier,
-            @Named(ACTIVITY_TYPE) @ActivityType int activityType) {
+            @Named(ACTIVITY_TYPE) @ActivityType int activityType,
+            Lazy<BottomSheetController> bottomSheetController) {
         mActivity = activity;
         mShouldHideBrowserControls = shouldHideBrowserControls;
         mIsOpenedByChrome = isOpenedByChrome;
@@ -315,6 +321,7 @@
         mSnackbarManager = snackbarManager;
         mShareDelegateSupplier = shareDelegateSupplier;
         mActivityType = activityType;
+        mBottomSheetController = bottomSheetController;
     }
 
     @Inject
@@ -335,7 +342,8 @@
             Supplier<ModalDialogManager> modalDialogManagerSupplier,
             Lazy<SnackbarManager> snackbarManager,
             Supplier<ShareDelegate> shareDelegateSupplier,
-            @Named(ACTIVITY_TYPE) @ActivityType int activityType) {
+            @Named(ACTIVITY_TYPE) @ActivityType int activityType,
+            Lazy<BottomSheetController> bottomSheetController) {
         this(
                 activity,
                 intentDataProvider.shouldEnableUrlBarHiding(),
@@ -357,7 +365,8 @@
                 modalDialogManagerSupplier,
                 snackbarManager,
                 shareDelegateSupplier,
-                activityType);
+                activityType,
+                bottomSheetController);
     }
 
     /**
@@ -386,7 +395,8 @@
                 () -> null,
                 null,
                 null,
-                ActivityType.CUSTOM_TAB);
+                ActivityType.CUSTOM_TAB,
+                null);
     }
 
     @Override
@@ -451,11 +461,13 @@
     TabContextMenuItemDelegate createTabContextMenuItemDelegate(Tab tab) {
         TabModelSelector tabModelSelector = mTabModelSelectorSupplier.get();
         return new TabContextMenuItemDelegate(
+                mActivity,
                 tab,
                 tabModelSelector,
                 EphemeralTabCoordinator.isSupported() ? mEphemeralTabCoordinator::get : () -> null,
                 () -> {},
-                () -> mSnackbarManager.get());
+                () -> mSnackbarManager.get(),
+                () -> mBottomSheetController.get());
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
index 59de416..bffd786 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
@@ -608,7 +608,8 @@
                         BookmarkModel.getForProfile(profile),
                         BrowserUiUtils.HostSurface.NEW_TAB_PAGE,
                         mTabModelSelector,
-                        profile) {
+                        profile,
+                        mBottomSheetController) {
                     @Override
                     public void openHelpPage() {
                         NewTabPageUma.recordAction(NewTabPageUma.ACTION_CLICKED_LEARN_MORE);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java
index e3340f6..e9cad65f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabContextMenuItemDelegate.java
@@ -38,6 +38,7 @@
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.tabmodel.document.ChromeAsyncTabLauncher;
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.embedder_support.util.UrlUtilities;
 import org.chromium.components.feature_engagement.EventConstants;
 import org.chromium.content_public.browser.AdditionalNavigationParams;
@@ -52,24 +53,30 @@
  * A default {@link ContextMenuItemDelegate} that supports the context menu functionality in Tab.
  */
 public class TabContextMenuItemDelegate implements ContextMenuItemDelegate {
+    private final Activity mActivity;
     private final TabImpl mTab;
     private final TabModelSelector mTabModelSelector;
     private final Supplier<EphemeralTabCoordinator> mEphemeralTabCoordinatorSupplier;
     private final Runnable mContextMenuCopyLinkObserver;
-    private final Supplier<SnackbarManager> mSnackbarManager;
+    private final Supplier<SnackbarManager> mSnackbarManagerSupplier;
+    private final Supplier<BottomSheetController> mBottomSheetControllerSupplier;
 
     /** Builds a {@link TabContextMenuItemDelegate} instance. */
     public TabContextMenuItemDelegate(
+            Activity activity,
             Tab tab,
             TabModelSelector tabModelSelector,
             Supplier<EphemeralTabCoordinator> ephemeralTabCoordinatorSupplier,
             Runnable contextMenuCopyLinkObserver,
-            Supplier<SnackbarManager> snackbarManager) {
+            Supplier<SnackbarManager> snackbarManagerSupplier,
+            Supplier<BottomSheetController> bottomSheetControllerSupplier) {
+        mActivity = activity;
         mTab = (TabImpl) tab;
         mTabModelSelector = tabModelSelector;
         mEphemeralTabCoordinatorSupplier = ephemeralTabCoordinatorSupplier;
         mContextMenuCopyLinkObserver = contextMenuCopyLinkObserver;
-        mSnackbarManager = snackbarManager;
+        mSnackbarManagerSupplier = snackbarManagerSupplier;
+        mBottomSheetControllerSupplier = bottomSheetControllerSupplier;
     }
 
     @Override
@@ -290,12 +297,13 @@
                 () -> {
                     // Add to reading list.
                     BookmarkUtils.addToReadingList(
-                            url,
-                            title,
-                            mSnackbarManager.get(),
+                            mActivity,
                             bookmarkModel,
-                            mTab.getContext(),
-                            profile);
+                            title,
+                            url,
+                            mSnackbarManagerSupplier.get(),
+                            mTab.getProfile(),
+                            mBottomSheetControllerSupplier.get());
                     TrackerFactory.getTrackerForProfile(profile)
                             .notifyEvent(EventConstants.READ_LATER_CONTEXT_MENU_TAPPED);
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkIntentDataProviderFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkIntentDataProviderFactory.java
index a0e496b..681b30fb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkIntentDataProviderFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkIntentDataProviderFactory.java
@@ -11,6 +11,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
@@ -271,9 +272,12 @@
         PackageManager pm = appContext.getPackageManager();
         Resources res = null;
         int apkVersion = 0;
+        long lastUpdateTime = 0;
         try {
             res = pm.getResourcesForApplication(webApkPackageName);
-            apkVersion = pm.getPackageInfo(webApkPackageName, 0).versionCode;
+            PackageInfo packageInfo = pm.getPackageInfo(webApkPackageName, 0);
+            apkVersion = packageInfo.versionCode;
+            lastUpdateTime = packageInfo.lastUpdateTime;
         } catch (PackageManager.NameNotFoundException e) {
             return null;
         }
@@ -418,51 +422,53 @@
                 isSplashProvidedByWebApk,
                 shareData,
                 parseShortcutItems(webApkPackageName, res),
-                apkVersion);
+                apkVersion,
+                lastUpdateTime);
     }
 
     /**
      * Construct a {@link BrowserServicesIntentDataProvider} instance.
-     * @param intent                   Intent used to launch activity.
-     * @param url                      URL that the WebAPK should navigate to when launched.
-     * @param scope                    Scope for the WebAPK.
-     * @param primaryIcon              Primary icon to show for the WebAPK.
-     * @param splashIcon               Splash icon to use for the splash screen.
-     * @param name                     Name of the WebAPK.
-     * @param shortName                The short name of the WebAPK.
-     * @param displayMode              Display mode of the WebAPK.
-     * @param orientation              Orientation of the WebAPK.
-     * @param source                   Source that the WebAPK was launched from.
-     * @param themeColor               The theme color of the WebAPK.
-     * @param backgroundColor          The background color of the WebAPK.
-     * @param darkThemeColor           The theme color of the WebAPK's dark mode.
-     * @param darkBackgroundColor      The background color of the WebAPK's dark mode.
-     * @param defaultBackgroundColor   The background color to use if the Web Manifest does not
-     *                                 provide a background color.
-     * @param isPrimaryIconMaskable    Is the primary icon maskable.
-     * @param isSplashIconMaskable     Is the splash icon maskable.
-     * @param webApkPackageName        The package of the WebAPK.
-     * @param shellApkVersion          Version of the code in //chrome/android/webapk/shell_apk.
-     * @param manifestUrl              URL of the Web Manifest.
-     * @param manifestStartUrl         URL that the WebAPK should navigate to when launched from
-     *                                 the homescreen. Different from the {@link url} parameter if
-     *                                 the WebAPK is launched from a deep link.
-     * @param manifestId               Id of the WebAPK.
-     * @param appKey                   Key used to identified the WebAPK. This is either the
-     *                                 Manifest URL or the Manifest Unique ID depending on the
-     *                                 situation.
-     * @param distributor              The source from where the WebAPK is installed.
-     * @param iconUrlToMurmur2HashMap  Map of the WebAPK's icon URLs to Murmur2 hashes of the
-     *                                 icon untransformed bytes.
-     * @param shareTarget              Specifies what share data is supported by WebAPK.
-     * @param forceNavigation          Whether the WebAPK should navigate to {@link url} if the
-     *                                 WebAPK is already open.
+     *
+     * @param intent Intent used to launch activity.
+     * @param url URL that the WebAPK should navigate to when launched.
+     * @param scope Scope for the WebAPK.
+     * @param primaryIcon Primary icon to show for the WebAPK.
+     * @param splashIcon Splash icon to use for the splash screen.
+     * @param name Name of the WebAPK.
+     * @param shortName The short name of the WebAPK.
+     * @param displayMode Display mode of the WebAPK.
+     * @param orientation Orientation of the WebAPK.
+     * @param source Source that the WebAPK was launched from.
+     * @param themeColor The theme color of the WebAPK.
+     * @param backgroundColor The background color of the WebAPK.
+     * @param darkThemeColor The theme color of the WebAPK's dark mode.
+     * @param darkBackgroundColor The background color of the WebAPK's dark mode.
+     * @param defaultBackgroundColor The background color to use if the Web Manifest does not
+     *     provide a background color.
+     * @param isPrimaryIconMaskable Is the primary icon maskable.
+     * @param isSplashIconMaskable Is the splash icon maskable.
+     * @param webApkPackageName The package of the WebAPK.
+     * @param shellApkVersion Version of the code in //chrome/android/webapk/shell_apk.
+     * @param manifestUrl URL of the Web Manifest.
+     * @param manifestStartUrl URL that the WebAPK should navigate to when launched from the
+     *     homescreen. Different from the {@link url} parameter if the WebAPK is launched from a
+     *     deep link.
+     * @param manifestId Id of the WebAPK.
+     * @param appKey Key used to identified the WebAPK. This is either the Manifest URL or the
+     *     Manifest Unique ID depending on the situation.
+     * @param distributor The source from where the WebAPK is installed.
+     * @param iconUrlToMurmur2HashMap Map of the WebAPK's icon URLs to Murmur2 hashes of the icon
+     *     untransformed bytes.
+     * @param shareTarget Specifies what share data is supported by WebAPK.
+     * @param forceNavigation Whether the WebAPK should navigate to {@link url} if the WebAPK is
+     *     already open.
      * @param isSplashProvidedByWebApk Whether the WebAPK (1) launches an internal activity to
-     *                                 display the splash screen and (2) has a content provider
-     *                                 which provides a screenshot of the splash screen.
-     * @param shareData                Shared information from the share intent.
-     * @param shortcutItems            A list of shortcut items.
-     * @param webApkVersionCode        WebAPK's version code.
+     *     display the splash screen and (2) has a content provider which provides a screenshot of
+     *     the splash screen.
+     * @param shareData Shared information from the share intent.
+     * @param shortcutItems A list of shortcut items.
+     * @param webApkVersionCode WebAPK's version code.
+     * @param lastUpdateTime WebAPK's last update timestamp.
      */
     public static BrowserServicesIntentDataProvider create(
             Intent intent,
@@ -495,7 +501,8 @@
             boolean isSplashProvidedByWebApk,
             ShareData shareData,
             List<ShortcutItem> shortcutItems,
-            int webApkVersionCode) {
+            int webApkVersionCode,
+            long lastUpdateTime) {
         if (manifestStartUrl == null || webApkPackageName == null) {
             Log.e(TAG, "Incomplete data provided: " + manifestStartUrl + ", " + webApkPackageName);
             return null;
@@ -556,7 +563,8 @@
                         shareTarget,
                         isSplashProvidedByWebApk,
                         shortcutItems,
-                        webApkVersionCode);
+                        webApkVersionCode,
+                        lastUpdateTime);
         boolean hasCustomToolbarColor = WebappIntentUtils.isLongColorValid(themeColor);
         int toolbarColor =
                 hasCustomToolbarColor
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcher.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcher.java
index f0020de7..ce561601 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateDataFetcher.java
@@ -220,7 +220,8 @@
                         mOldInfo.isSplashProvidedByWebApk(),
                         null,
                         shortcutItems,
-                        mOldInfo.webApkVersionCode());
+                        mOldInfo.webApkVersionCode(),
+                        mOldInfo.lastUpdateTime());
         mObserver.onGotManifestData(intentDataProvider, primaryIconUrl, splashIconUrl);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
index dc6039d..4fec066 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkUpdateManager.java
@@ -26,6 +26,7 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.ResettersForTesting;
+import org.chromium.base.TimeUtils;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.task.AsyncTask;
 import org.chromium.blink.mojom.DisplayMode;
@@ -72,6 +73,11 @@
     // Maximum wait time for WebAPK update to be scheduled.
     private static final long UPDATE_TIMEOUT_MILLISECONDS = DateUtils.SECOND_IN_MILLIS * 30;
 
+    // Number of milliseconds that a WebAPK shell is outdated from last
+    // install/update and should be updated again.
+    @VisibleForTesting
+    static final long OLD_SHELL_NEEDS_UPDATE_INTERVAL = DateUtils.DAY_IN_MILLIS * 360;
+
     // Flags for AppIdentity Update dialog histograms.
     private static final int CHANGING_NOTHING = 0;
     private static final int CHANGING_ICON = 1 << 0;
@@ -621,9 +627,12 @@
         return sWebApkTargetShellVersion;
     }
 
-    /** Whether there is a new version of the //chrome/android/webapk/shell_apk code. */
+    /** Whether the shell version is outdated. */
     private static boolean isShellApkVersionOutOfDate(WebappInfo info) {
-        return info.shellApkVersion() < webApkTargetShellVersion();
+        return info.shellApkVersion() < webApkTargetShellVersion()
+                || (info.lastUpdateTime() > 0
+                        && TimeUtils.currentTimeMillis() - info.lastUpdateTime()
+                                > OLD_SHELL_NEEDS_UPDATE_INTERVAL);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
index c92dd493..8df4178 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappDataStorage.java
@@ -17,6 +17,7 @@
 import org.chromium.base.Log;
 import org.chromium.base.PackageUtils;
 import org.chromium.base.ResettersForTesting;
+import org.chromium.base.TimeUtils;
 import org.chromium.base.task.AsyncTask;
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
@@ -155,7 +156,7 @@
          * @return Current time in milliseconds.
          */
         public long currentTimeMillis() {
-            return System.currentTimeMillis();
+            return TimeUtils.currentTimeMillis();
         }
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java
index 923aacb4..2f37ed0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerTest.java
@@ -23,6 +23,7 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.FeatureList;
+import org.chromium.base.TimeUtils;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.test.params.ParameterAnnotations;
 import org.chromium.base.test.params.ParameterProvider;
@@ -379,7 +380,8 @@
                                     /* isSplashProvidedByWebApk= */ false,
                                     /* shareData= */ null,
                                     creationData.shortcuts,
-                                    /* webApkVersionCode= */ 1);
+                                    /* webApkVersionCode= */ 1,
+                                    /* lastUpdateTime= */ TimeUtils.currentTimeMillis());
                     updateManager.updateIfNeeded(storage, intentDataProvider);
                 });
         waiter.waitForCallback(0);
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkUtilsTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkUtilsTest.java
index b5028b63..46524ff 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkUtilsTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/BookmarkUtilsTest.java
@@ -7,7 +7,10 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
 
 import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.DESKTOP_BOOKMARK_ID;
 import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.FOLDER_BOOKMARK_ID_A;
@@ -17,12 +20,17 @@
 import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.READING_LIST_BOOKMARK_ID;
 import static org.chromium.chrome.browser.bookmarks.SharedBookmarkModelMocks.ROOT_BOOKMARK_ID;
 
+import android.app.Activity;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 import org.robolectric.annotation.Config;
@@ -30,9 +38,27 @@
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.Features;
+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.chrome.browser.commerce.ShoppingServiceFactory;
+import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
+import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.components.bookmarks.BookmarkId;
 import org.chromium.components.bookmarks.BookmarkItem;
 import org.chromium.components.bookmarks.BookmarkType;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
+import org.chromium.components.commerce.core.ShoppingService;
+import org.chromium.components.favicon.LargeIconBridge;
+import org.chromium.components.favicon.LargeIconBridgeJni;
+import org.chromium.components.feature_engagement.EventConstants;
+import org.chromium.components.feature_engagement.Tracker;
+import org.chromium.components.signin.identitymanager.IdentityManager;
+import org.chromium.components.sync.SyncFeatureMap;
+import org.chromium.ui.base.TestActivity;
+import org.chromium.url.GURL;
 
 /** Unit tests for {@link BookmarkUtils}. */
 @Batch(Batch.UNIT_TESTS)
@@ -41,12 +67,76 @@
 public class BookmarkUtilsTest {
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Rule public TestRule mFeaturesProcessorRule = new Features.JUnitProcessor();
+    @Rule public JniMocker mJniMocker = new JniMocker();
+
+    @Rule
+    public ActivityScenarioRule<TestActivity> mActivityScenarioRule =
+            new ActivityScenarioRule<>(TestActivity.class);
 
     @Mock private BookmarkModel mBookmarkModel;
+    @Mock private SnackbarManager mSnackbarManager;
+    @Mock private Profile mProfile;
+    @Mock private BottomSheetController mBottomSheetController;
+    @Mock private Tracker mTracker;
+    @Mock private ShoppingService mShoppingService;
+    @Mock private IdentityManager mIdentityManager;
+    @Mock private LargeIconBridge mLargeIconBridge;
+    @Mock private LargeIconBridge.Natives mLargeIconBridgeNatives;
+
+    private Activity mActivity;
 
     @Before
     public void setup() {
         SharedBookmarkModelMocks.initMocks(mBookmarkModel);
+        TrackerFactory.setTrackerForTests(mTracker);
+        ShoppingServiceFactory.setShoppingServiceForTesting(mShoppingService);
+
+        IdentityServicesProvider identityServicesProvider =
+                Mockito.mock(IdentityServicesProvider.class);
+        doReturn(mIdentityManager).when(identityServicesProvider).getIdentityManager(mProfile);
+        IdentityServicesProvider.setInstanceForTests(identityServicesProvider);
+
+        mJniMocker.mock(LargeIconBridgeJni.TEST_HOOKS, mLargeIconBridgeNatives);
+
+        mActivityScenarioRule.getScenario().onActivity(this::onActivity);
+    }
+
+    private void onActivity(Activity activity) {
+        mActivity = activity;
+    }
+
+    @Test
+    @DisableFeatures({SyncFeatureMap.ENABLE_BOOKMARK_FOLDERS_FOR_ACCOUNT_STORAGE})
+    public void testAddToReadingList() {
+        BookmarkModel bookmarkModel = FakeBookmarkModel.createModel();
+        BookmarkUtils.addToReadingList(
+                mActivity,
+                bookmarkModel,
+                "Test title",
+                new GURL("https://test.com"),
+                mSnackbarManager,
+                mProfile,
+                mBottomSheetController);
+        // Normally, a snackbar is shown.
+        verify(mSnackbarManager).showSnackbar(any());
+        verify(mTracker).notifyEvent(EventConstants.READ_LATER_ARTICLE_SAVED);
+    }
+
+    @Test
+    @EnableFeatures({SyncFeatureMap.ENABLE_BOOKMARK_FOLDERS_FOR_ACCOUNT_STORAGE})
+    public void testAddToReadingList_withAccountBookmarks() {
+        BookmarkModel bookmarkModel = FakeBookmarkModel.createModel();
+        BookmarkUtils.addToReadingList(
+                mActivity,
+                bookmarkModel,
+                "Test title",
+                new GURL("https://test.com"),
+                mSnackbarManager,
+                mProfile,
+                mBottomSheetController);
+        // When account bookmarks are enabled, reading list saves use the regular save flow.
+        verify(mBottomSheetController).requestShowContent(any(), anyBoolean());
+        verify(mTracker).notifyEvent(EventConstants.READ_LATER_ARTICLE_SAVED);
     }
 
     @Test
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/FakeBookmarkModel.java b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/FakeBookmarkModel.java
index c80a1f9..9a947d6 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/FakeBookmarkModel.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/bookmarks/FakeBookmarkModel.java
@@ -300,7 +300,9 @@
 
         @Override
         public BookmarkId getDefaultReadingListFolder(long nativeBookmarkBridge) {
-            return mMobileFolderId;
+            return BookmarkFeatures.isBookmarksAccountStorageEnabled()
+                    ? mAccountReadingListFolderId
+                    : mLocalOrSyncableReadingListFolderId;
         }
 
         @Override
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedActionDelegateImplTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedActionDelegateImplTest.java
index 0b406fc4..c87960c 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedActionDelegateImplTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedActionDelegateImplTest.java
@@ -10,7 +10,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.Context;
+import android.app.Activity;
 import android.content.Intent;
 
 import com.google.common.collect.ImmutableMap;
@@ -40,6 +40,7 @@
 import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
 import org.chromium.chrome.browser.ui.signin.SyncConsentActivityLauncher;
 import org.chromium.chrome.browser.util.BrowserUiUtils;
+import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.signin.metrics.SigninAccessPoint;
 
 /** Tests for FeedActionDelegateImpl. */
@@ -57,12 +58,14 @@
 
     @Mock private BookmarkModel mMockBookmarkModel;
 
-    @Mock private Context mActivityContext;
+    @Mock private Activity mActivity;
 
     @Mock private TabModelSelector mTabModelSelector;
 
     @Mock private Profile mProfile;
 
+    @Mock private BottomSheetController mBottomSheetController;
+
     @Captor ArgumentCaptor<Intent> mIntentCaptor;
 
     private FeedActionDelegateImpl mFeedActionDelegateImpl;
@@ -74,13 +77,14 @@
         SyncConsentActivityLauncherImpl.setLauncherForTest(mMockSyncConsentActivityLauncher);
         mFeedActionDelegateImpl =
                 new FeedActionDelegateImpl(
-                        mActivityContext,
+                        mActivity,
                         mMockSnackbarManager,
                         mMockNavigationDelegate,
                         mMockBookmarkModel,
                         BrowserUiUtils.HostSurface.NOT_SET,
                         mTabModelSelector,
-                        mProfile);
+                        mProfile,
+                        mBottomSheetController);
         jniMocker.mock(WebFeedBridgeJni.TEST_HOOKS, mWebFeedBridgeJniMock);
 
         when(mWebFeedBridgeJniMock.isCormorantEnabledForLocale()).thenReturn(true);
@@ -111,7 +115,7 @@
 
         mFeedActionDelegateImpl.openWebFeed(webFeedName, SingleWebFeedEntryPoint.OTHER);
 
-        verify(mActivityContext).startActivity(mIntentCaptor.capture());
+        verify(mActivity).startActivity(mIntentCaptor.capture());
         Assert.assertArrayEquals(
                 "Feed ID not passed correctly.",
                 webFeedName.getBytes(),
@@ -123,6 +127,6 @@
         when(mWebFeedBridgeJniMock.isCormorantEnabledForLocale()).thenReturn(false);
         FeatureList.setTestFeatures(ImmutableMap.of(ChromeFeatureList.CORMORANT, false));
         mFeedActionDelegateImpl.openWebFeed("SomeFeedName", SingleWebFeedEntryPoint.OTHER);
-        verify(mActivityContext, never()).startActivity(any());
+        verify(mActivity, never()).startActivity(any());
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java
index 0f6eae2a..fa1c62b 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebApkUpdateManagerUnitTest.java
@@ -14,6 +14,7 @@
 
 import android.app.Activity;
 import android.content.Intent;
+import android.content.pm.PackageInfo;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -32,14 +33,18 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
 import org.robolectric.android.util.concurrent.RoboExecutorService;
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.LooperMode;
 import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.shadows.ShadowPackageManager;
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.Callback;
+import org.chromium.base.FakeTimeTestRule;
 import org.chromium.base.PathUtils;
+import org.chromium.base.TimeUtils;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.UmaRecorderHolder;
 import org.chromium.base.task.PostTask;
@@ -79,6 +84,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Observer;
 import java.util.concurrent.TimeoutException;
 
 /** Unit tests for WebApkUpdateManager. */
@@ -90,7 +96,7 @@
 public class WebApkUpdateManagerUnitTest {
     @Mock private Activity mActivityMock;
 
-    @Rule public MockWebappDataStorageClockRule mClockRule = new MockWebappDataStorageClockRule();
+    @Rule public FakeTimeTestRule mClockRule = new FakeTimeTestRule();
 
     @Rule public JniMocker mJniMocker = new JniMocker();
 
@@ -439,13 +445,12 @@
     }
 
     /**
-     * Registers WebAPK with default package name. Overwrites previous registrations.
-     * @param packageName         Package name for which to register the WebApk.
-     * @param manifestData        <meta-data> values for WebAPK's Android Manifest.
+     * Create a WebAPK metadata bundle from ManifestData.
+     *
+     * @param manifestData <meta-data> values for WebAPK's Android Manifest.
      * @param shellApkVersionCode WebAPK's version of the //chrome/android/webapk/shell_apk code.
      */
-    private void registerWebApk(
-            String packageName, ManifestData manifestData, int shellApkVersionCode) {
+    private Bundle createWebApkMetadata(ManifestData manifestData, int shellApkVersionCode) {
         Bundle metaData = new Bundle();
         metaData.putInt(WebApkMetaDataKeys.SHELL_APK_VERSION, shellApkVersionCode);
         metaData.putString(WebApkMetaDataKeys.START_URL, manifestData.startUrl);
@@ -475,7 +480,19 @@
         iconUrlsAndIconMurmur2Hashes = iconUrlsAndIconMurmur2Hashes.trim();
         metaData.putString(
                 WebApkMetaDataKeys.ICON_URLS_AND_ICON_MURMUR2_HASHES, iconUrlsAndIconMurmur2Hashes);
+        return metaData;
+    }
 
+    /**
+     * Registers WebAPK with default package name. Overwrites previous registrations.
+     *
+     * @param packageName Package name for which to register the WebApk.
+     * @param manifestData <meta-data> values for WebAPK's Android Manifest.
+     * @param shellApkVersionCode WebAPK's version of the //chrome/android/webapk/shell_apk code.
+     */
+    private void registerWebApk(
+            String packageName, ManifestData manifestData, int shellApkVersionCode) {
+        Bundle metadata = createWebApkMetadata(manifestData, shellApkVersionCode);
         Bundle shareTargetMetaData = new Bundle();
         shareTargetMetaData.putString(
                 WebApkMetaDataKeys.SHARE_ACTION, manifestData.shareTargetAction);
@@ -507,7 +524,7 @@
         }
 
         WebApkTestHelper.registerWebApkWithMetaData(
-                packageName, metaData, new Bundle[] {shareTargetMetaData});
+                packageName, metadata, new Bundle[] {shareTargetMetaData});
         WebApkTestHelper.setResource(
                 packageName,
                 new FakeDefaultBackgroundColorResource(manifestData.defaultBackgroundColor));
@@ -599,7 +616,8 @@
                 /* isSplashProvidedByWebApk= */ false,
                 /* shareData= */ null,
                 /* shortcutItems= */ manifestData.shortcuts,
-                /* webApkVersionCode= */ 1);
+                /* webApkVersionCode= */ 1,
+                /* lastUpdateTime= */ TimeUtils.currentTimeMillis());
     }
 
     /**
@@ -715,7 +733,7 @@
             boolean iconUpdatesEnabled) {
         registerWebApk(
                 WEBAPK_PACKAGE_NAME, androidManifestData, REQUEST_UPDATE_FOR_SHELL_APK_VERSION);
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL);
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
 
         TestWebApkUpdateManager updateManager =
                 new TestWebApkUpdateManager(mActivityMock, nameUpdatesEnabled, iconUpdatesEnabled);
@@ -757,7 +775,7 @@
      */
     @Test
     public void testCheckOnNextLaunchIfClosePriorToFirstPageLoad() {
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL);
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
         {
             TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mActivityMock);
             updateIfNeeded(WEBAPK_PACKAGE_NAME, updateManager);
@@ -790,8 +808,8 @@
      */
     @Test
     public void testUpdateNotNeeded() {
-        long initialTime = mClockRule.currentTimeMillis();
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL);
+        long initialTime = TimeUtils.currentTimeMillis();
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
 
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mActivityMock);
         updateIfNeeded(WEBAPK_PACKAGE_NAME, updateManager);
@@ -814,7 +832,7 @@
     public void testMarkUpdateAsSucceededIfUpdateNoLongerNeeded() {
         WebappDataStorage storage = getStorage(WEBAPK_PACKAGE_NAME);
         storage.updateDidLastWebApkUpdateRequestSucceed(false);
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL);
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
 
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mActivityMock);
         updateIfNeeded(WEBAPK_PACKAGE_NAME, updateManager);
@@ -824,7 +842,7 @@
 
         assertTrue(storage.getDidLastWebApkUpdateRequestSucceed());
         assertEquals(
-                mClockRule.currentTimeMillis(),
+                TimeUtils.currentTimeMillis(),
                 storage.getLastWebApkUpdateRequestCompletionTimeMs());
     }
 
@@ -834,7 +852,7 @@
      */
     @Test
     public void testMarkUpdateAsFailedIfClosePriorToUpdateCompleting() {
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL);
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
 
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mActivityMock);
         updateIfNeeded(WEBAPK_PACKAGE_NAME, updateManager);
@@ -848,7 +866,7 @@
         WebappDataStorage storage = getStorage(WEBAPK_PACKAGE_NAME);
         assertFalse(storage.getDidLastWebApkUpdateRequestSucceed());
         assertEquals(
-                mClockRule.currentTimeMillis(),
+                TimeUtils.currentTimeMillis(),
                 storage.getLastWebApkUpdateRequestCompletionTimeMs());
     }
 
@@ -858,7 +876,7 @@
      */
     @Test
     public void testPendingUpdateFileDeletedAfterUpdateCompletion() {
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL);
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
 
         WebappDataStorage storage = getStorage(WEBAPK_PACKAGE_NAME);
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mActivityMock);
@@ -883,7 +901,7 @@
      */
     @Test
     public void testFileDeletedIfStoreWebApkUpdateRequestToFileFails() {
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL);
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
 
         WebappDataStorage storage = getStorage(WEBAPK_PACKAGE_NAME);
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mActivityMock);
@@ -916,7 +934,7 @@
                 WEBAPK_PACKAGE_NAME,
                 defaultManifestData(),
                 REQUEST_UPDATE_FOR_SHELL_APK_VERSION - 1);
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL);
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
 
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mActivityMock);
         updateIfNeeded(WEBAPK_PACKAGE_NAME, updateManager);
@@ -944,7 +962,7 @@
                 WEBAPK_PACKAGE_NAME,
                 defaultManifestData(),
                 REQUEST_UPDATE_FOR_SHELL_APK_VERSION - 1);
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL);
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
 
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mActivityMock);
         updateIfNeeded(WEBAPK_PACKAGE_NAME, updateManager);
@@ -966,7 +984,7 @@
                 WEBAPK_PACKAGE_NAME,
                 defaultManifestData(),
                 REQUEST_UPDATE_FOR_SHELL_APK_VERSION - 1);
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL);
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
 
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mActivityMock);
         updateIfNeeded(WEBAPK_PACKAGE_NAME, updateManager);
@@ -990,7 +1008,7 @@
      */
     @Test
     public void testStartUrlRedirectsToPageWithUpdatedWebManifest() {
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL);
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
 
         TestWebApkUpdateManager updateManager =
                 new TestWebApkUpdateManager(
@@ -1029,7 +1047,7 @@
      */
     @Test
     public void testStartUrlRedirectsToPageWithUnchangedWebManifest() {
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL);
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
 
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mActivityMock);
         updateIfNeeded(WEBAPK_PACKAGE_NAME, updateManager);
@@ -1059,7 +1077,7 @@
                 UNBOUND_WEBAPK_PACKAGE_NAME,
                 androidManifestData,
                 REQUEST_UPDATE_FOR_SHELL_APK_VERSION);
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL);
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
 
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mActivityMock);
         updateIfNeeded(UNBOUND_WEBAPK_PACKAGE_NAME, updateManager);
@@ -1421,13 +1439,51 @@
         assertTrue(updateManager.updateRequested());
         tryCompletingUpdate(updateManager, storage, WebApkInstallResult.FAILURE);
 
-        mClockRule.advance(1);
+        mClockRule.advanceMillis(1);
         updateIfNeeded(WEBAPK_PACKAGE_NAME, updateManager);
         assertFalse(updateManager.updateCheckStarted());
 
         // A previous update request was made for the current ShellAPK version. A WebAPK update
         // should be requested after the regular delay.
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL - 1);
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL - 1);
+        updateIfNeeded(WEBAPK_PACKAGE_NAME, updateManager);
+        assertTrue(updateManager.updateCheckStarted());
+        onGotManifestData(updateManager, defaultManifestData());
+        assertTrue(updateManager.updateRequested());
+    }
+
+    /**
+     * Tests that a WebAPK update is requested if the Shell APK hasn't been updated for a long time
+     * even if is is newer than the requested version.
+     */
+    @Test
+    public void testShellApkOutOfDateInterval() {
+        ShadowPackageManager pm =
+                Shadows.shadowOf(RuntimeEnvironment.getApplication().getPackageManager());
+        PackageInfo packageInfo =
+                WebApkTestHelper.newPackageInfo(
+                        WEBAPK_PACKAGE_NAME,
+                        createWebApkMetadata(
+                                defaultManifestData(), REQUEST_UPDATE_FOR_SHELL_APK_VERSION),
+                        null,
+                        null);
+        packageInfo.lastUpdateTime = TimeUtils.currentTimeMillis();
+        pm.addPackage(packageInfo);
+
+        WebappDataStorage storage = getStorage(WEBAPK_PACKAGE_NAME);
+        TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mActivityMock);
+
+        // Update is not needed when no manifest change and shell version is the same as
+        // REQUEST_UPDATE_FOR_SHELL_APK_VERSION.
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
+        updateIfNeeded(WEBAPK_PACKAGE_NAME, updateManager);
+        assertTrue(updateManager.updateCheckStarted());
+        onGotManifestData(updateManager, defaultManifestData());
+        assertFalse(updateManager.updateRequested());
+        tryCompletingUpdate(updateManager, storage, WebApkInstallResult.FAILURE);
+
+        // Update is requested when a shell hasn't been updated for a long time.
+        mClockRule.advanceMillis(WebApkUpdateManager.OLD_SHELL_NEEDS_UPDATE_INTERVAL);
         updateIfNeeded(WEBAPK_PACKAGE_NAME, updateManager);
         assertTrue(updateManager.updateCheckStarted());
         onGotManifestData(updateManager, defaultManifestData());
@@ -1753,7 +1809,7 @@
                 WEBAPK_PACKAGE_NAME,
                 defaultManifestData(),
                 REQUEST_UPDATE_FOR_SHELL_APK_VERSION - 1);
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL);
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
 
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mActivityMock);
         updateIfNeeded(WEBAPK_PACKAGE_NAME, updateManager);
@@ -1781,7 +1837,7 @@
         ManifestData androidManifestData = defaultManifestData();
         registerWebApk(
                 WEBAPK_PACKAGE_NAME, androidManifestData, REQUEST_UPDATE_FOR_SHELL_APK_VERSION);
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL);
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
 
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mActivityMock);
         updateIfNeeded(WEBAPK_PACKAGE_NAME, updateManager, androidManifestData.shortcuts);
@@ -1805,7 +1861,7 @@
         ManifestData androidData = defaultManifestData();
         androidData.appKey = WEB_MANIFEST_URL;
         registerWebApk(WEBAPK_PACKAGE_NAME, androidData, REQUEST_UPDATE_FOR_SHELL_APK_VERSION);
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL);
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
 
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mActivityMock);
         updateIfNeeded(WEBAPK_PACKAGE_NAME, updateManager);
@@ -1832,7 +1888,7 @@
         androidData.appKey = null;
 
         registerWebApk(WEBAPK_PACKAGE_NAME, androidData, REQUEST_UPDATE_FOR_SHELL_APK_VERSION);
-        mClockRule.advance(WebappDataStorage.UPDATE_INTERVAL);
+        mClockRule.advanceMillis(WebappDataStorage.UPDATE_INTERVAL);
 
         TestWebApkUpdateManager updateManager = new TestWebApkUpdateManager(mActivityMock);
         updateIfNeeded(WEBAPK_PACKAGE_NAME, updateManager);
diff --git a/chrome/android/webapk/test/src/org/chromium/webapk/test/WebApkTestHelper.java b/chrome/android/webapk/test/src/org/chromium/webapk/test/WebApkTestHelper.java
index d853204..b4ff1e1 100644
--- a/chrome/android/webapk/test/src/org/chromium/webapk/test/WebApkTestHelper.java
+++ b/chrome/android/webapk/test/src/org/chromium/webapk/test/WebApkTestHelper.java
@@ -101,7 +101,7 @@
         ShadowPackageManager.resources.put(packageName, res);
     }
 
-    private static PackageInfo newPackageInfo(
+    public static PackageInfo newPackageInfo(
             String webApkPackageName,
             Bundle metaData,
             String[] shareTargetActivityClassNames,
diff --git a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebApkExtras.java b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebApkExtras.java
index 605f5ee..65aeed1 100644
--- a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebApkExtras.java
+++ b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebApkExtras.java
@@ -74,6 +74,9 @@
     /** WebAPK's version code. */
     public final int webApkVersionCode;
 
+    /** WebAPK's last update timestamp. */
+    public final long lastUpdateTime;
+
     /** A class that stores information from shortcut items. */
     public static class ShortcutItem {
         public String name;
@@ -116,7 +119,8 @@
                 /* isSplashProvidedByWebApk= */ false,
                 new ArrayList<>()
                 /* shortcutItems= */ ,
-                /* webApkVersionCode= */ 0);
+                /* webApkVersionCode= */ 0,
+                /* lastUpdateTime= */ 0);
     }
 
     public WebApkExtras(
@@ -133,7 +137,8 @@
             @Nullable WebApkShareTarget shareTarget,
             boolean isSplashProvidedByWebApk,
             @NonNull List<ShortcutItem> shortcutItems,
-            int webApkVersionCode) {
+            int webApkVersionCode,
+            long lastUpdateTime) {
         this.webApkPackageName = webApkPackageName;
         this.splashIcon = splashIcon;
         this.isSplashIconMaskable = isSplashIconMaskable;
@@ -148,5 +153,6 @@
         this.isSplashProvidedByWebApk = isSplashProvidedByWebApk;
         this.shortcutItems = shortcutItems;
         this.webApkVersionCode = webApkVersionCode;
+        this.lastUpdateTime = lastUpdateTime;
     }
 }
diff --git a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappInfo.java b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappInfo.java
index 58eb5cbb..6c9be49 100644
--- a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappInfo.java
+++ b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappInfo.java
@@ -240,6 +240,10 @@
         return getWebApkExtras().shortcutItems;
     }
 
+    public long lastUpdateTime() {
+        return getWebApkExtras().lastUpdateTime;
+    }
+
     /**
      * Returns true if the WebappInfo was created for an Intent fired from a launcher shortcut (as
      * opposed to an intent from a push notification or other internal source).
diff --git a/chrome/browser/apps/platform_apps/app_browsertest.cc b/chrome/browser/apps/platform_apps/app_browsertest.cc
index 68b2410..c7e741b 100644
--- a/chrome/browser/apps/platform_apps/app_browsertest.cc
+++ b/chrome/browser/apps/platform_apps/app_browsertest.cc
@@ -1371,8 +1371,9 @@
 IN_PROC_BROWSER_TEST_F(PlatformAppIncognitoBrowserTest,
                        MAYBE_IncognitoComponentApp) {
   // Get the file manager app.
-  const Extension* file_manager = extension_registry()->GetExtensionById(
-      extension_misc::kFilesManagerAppId, ExtensionRegistry::ENABLED);
+  const Extension* file_manager =
+      extension_registry()->enabled_extensions().GetByID(
+          extension_misc::kFilesManagerAppId);
   ASSERT_TRUE(file_manager != nullptr);
   Profile* incognito_profile =
       profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true);
diff --git a/chrome/browser/ash/accessibility/live_caption/live_caption_ui_remote_driver_browsertest.cc b/chrome/browser/ash/accessibility/live_caption/live_caption_ui_remote_driver_browsertest.cc
index 51e487a..533e5f6 100644
--- a/chrome/browser/ash/accessibility/live_caption/live_caption_ui_remote_driver_browsertest.cc
+++ b/chrome/browser/ash/accessibility/live_caption/live_caption_ui_remote_driver_browsertest.cc
@@ -158,8 +158,7 @@
     // many tests.
     if (stub_bounds) {
       EXPECT_CALL(*surface, GetBounds(_))
-          .WillOnce(
-              [&](auto cb) { std::move(cb).Run(gfx::Rect(1, 1, 600, 800)); })
+          .WillOnce([&](auto cb) { std::move(cb).Run(gfx::Rect(1, 2, 3, 4)); })
           .RetiresOnSaturation();
     }
 
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 0e94fe9..ffac25a 100644
--- a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
+++ b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ash/arc/input_overlay/display_overlay_controller.h"
 
+#include <algorithm>
 #include <memory>
 
 #include "ash/frame/non_client_frame_view_ash.h"
@@ -87,6 +88,94 @@
 
 }  // namespace
 
+// -----------------------------------------------------------------------------
+// DisplayOverlayController::FocusCycler:
+
+class DisplayOverlayController::FocusCycler {
+ public:
+  FocusCycler() {}
+  ~FocusCycler() = default;
+
+  // Adds `widget` to `widget_list_` if `widget` is visible and not in
+  // `widget_list_`. Otherwise, removes `widget` from `widget_list_`.
+  void RefreshWidget(views::Widget* widget) {
+    if (widget->IsVisible()) {
+      AddWidget(widget);
+    } else {
+      RemoveWidget(widget);
+    }
+  }
+
+  void MaybeChangeFocusWidget(ui::KeyEvent& event) {
+    // Only tab pressed is checked because the focus change is triggered by the
+    // tab pressed event. No need to change widget focus again on tab key
+    // released event.
+    if (event.type() == ui::ET_KEY_RELEASED ||
+        !views::FocusManager::IsTabTraversalKeyEvent(event)) {
+      return;
+    }
+
+    const bool reverse = event.IsShiftDown();
+    auto* target_widget = views::Widget::GetWidgetForNativeWindow(
+        static_cast<aura::Window*>(event.target()));
+    auto* focus_manager = target_widget->GetFocusManager();
+
+    // 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(
+            /*starting_view=*/focus_manager->GetFocusedView(),
+            /*starting_widget=*/target_widget, /*reverse=*/reverse,
+            /*dont_loop=*/true)) {
+      return;
+    }
+
+    // Change focus to the next widget.
+    if (auto* next_widget = GetNextWidgetToFocus(target_widget, reverse)) {
+      next_widget->GetFocusManager()->AdvanceFocus(reverse);
+      // Change the event target.
+      ui::Event::DispatcherApi(&event).set_target(
+          next_widget->GetNativeWindow());
+    }
+  }
+
+ private:
+  void AddWidget(views::Widget* widget) {
+    if (auto it = std::find(widget_list_.begin(), widget_list_.end(), widget);
+        it == widget_list_.end()) {
+      widget_list_.emplace_back(widget);
+    }
+  }
+
+  void RemoveWidget(views::Widget* widget) {
+    if (auto it = std::find(widget_list_.begin(), widget_list_.end(), widget);
+        it != widget_list_.end()) {
+      widget_list_.erase(it);
+    }
+  }
+
+  views::Widget* GetNextWidgetToFocus(views::Widget* focused_widget,
+                                      bool reverse) {
+    if (auto it =
+            std::find(widget_list_.begin(), widget_list_.end(), focused_widget);
+        it != widget_list_.end()) {
+      const int index = std::distance(widget_list_.begin(), it);
+      const size_t widget_list_size = widget_list_.size();
+      const size_t next_index =
+          reverse ? (index - 1u + widget_list_size) % widget_list_size
+                  : (index + 1u) % widget_list_size;
+      return widget_list_[next_index];
+    }
+    return nullptr;
+  }
+
+  // Only contains visible and unique widgets.
+  std::vector<views::Widget*> widget_list_;
+};
+
+// -----------------------------------------------------------------------------
+// DisplayOverlayController:
+
 DisplayOverlayController::DisplayOverlayController(
     TouchInjector* touch_injector,
     bool first_launch)
@@ -113,6 +202,7 @@
   touch_injector_->set_display_overlay_controller(nullptr);
 
   if (IsBeta()) {
+    widget_observations_.RemoveAllObservations();
     touch_injector_->window()->RemoveObserver(this);
     RemoveAllWidgets();
   } else {
@@ -487,6 +577,7 @@
       RemoveActionHighlightWidget();
       RemoveDeleteEditShortcutWidget();
       RemoveEditingListWidget();
+      RemoveFocusCycler();
       if (GetActiveActionsSize() == 0u) {
         // If there is no active action in `kView` mode, it doesn't create
         // `input_mapping_widget_` to save resources. When
@@ -516,10 +607,17 @@
         AddInputMappingWidget();
       }
 
+      AddFocusCycler();
+
       // No matter if the mapping hint is hidden, `input_mapping_widget_` needs
       // to show up in `kEdit` mode.
       input_mapping_widget_->ShowInactive();
 
+      // Since `focus_cycler_` was added in `kEdit` mode after
+      // `input_mapping_widget_` in general. Refresh `input_mapping_widget_` to
+      // make sure it is added in `focus_cycler_`.
+      focus_cycler_->RefreshWidget(input_mapping_widget_.get());
+
       if (auto* input_mapping = GetInputMapping()) {
         input_mapping->SetDisplayMode(mode);
       }
@@ -697,6 +795,7 @@
   button_options_widget_ = CreateTransientWidget(
       touch_injector_->window(), /*widget_name=*/kButtonOptionsMenu,
       /*accept_events=*/true, /*is_floating=*/true);
+  widget_observations_.AddObservation(button_options_widget_.get());
   button_options_widget_->SetContentsView(
       std::make_unique<ButtonOptionsMenu>(this, action));
   UpdateButtonOptionsMenuWidgetBounds();
@@ -704,7 +803,7 @@
   // Always hide editing list when button options menu shows up.
   SetEditingListVisibility(/*visible=*/false);
 
-  button_options_widget_->ShowInactive();
+  button_options_widget_->Show();
 }
 
 void DisplayOverlayController::RemoveButtonOptionsMenuWidget() {
@@ -734,7 +833,7 @@
     if (GetButtonOptionsMenu()) {
       UpdateButtonOptionsMenuWidgetBounds();
     }
-    button_options_widget_->ShowInactive();
+    button_options_widget_->Show();
   } else {
     button_options_widget_->Hide();
   }
@@ -746,6 +845,7 @@
     delete_edit_shortcut_widget_ =
         views::BubbleDialogDelegateView::CreateBubble(
             std::make_unique<DeleteEditShortcut>(this, anchor_view));
+    widget_observations_.AddObservation(delete_edit_shortcut_widget_);
   }
 
   if (auto* shortcut = GetDeleteEditShortcut();
@@ -773,12 +873,16 @@
         std::make_unique<ActionHighlight>(this, anchor_view));
   }
 
-  auto* highlight = views::AsViewClass<ActionHighlight>(
-      action_highlight_widget_->GetContentsView());
-  highlight->UpdateAnchorView(anchor_view);
+  if (auto* highlight = views::AsViewClass<ActionHighlight>(
+          action_highlight_widget_->GetContentsView())) {
+    highlight->UpdateAnchorView(anchor_view);
 
-  action_highlight_widget_->ShowInactive();
-  input_mapping_widget_->StackAboveWidget(action_highlight_widget_.get());
+    // Show `action_highlight_widget_` if it is hidden.
+    if (!action_highlight_widget_->IsVisible()) {
+      action_highlight_widget_->ShowInactive();
+      input_mapping_widget_->StackAboveWidget(action_highlight_widget_.get());
+    }
+  }
 }
 
 void DisplayOverlayController::RemoveActionHighlightWidget() {
@@ -864,6 +968,12 @@
   ProcessPressedEvent(*event);
 }
 
+void DisplayOverlayController::OnKeyEvent(ui::KeyEvent* event) {
+  if (focus_cycler_) {
+    focus_cycler_->MaybeChangeFocusWidget(*event);
+  }
+}
+
 void DisplayOverlayController::OnWindowBoundsChanged(
     aura::Window* window,
     const gfx::Rect& old_bounds,
@@ -922,6 +1032,17 @@
   }
 }
 
+void DisplayOverlayController::OnWidgetVisibilityChanged(views::Widget* widget,
+                                                         bool visible) {
+  if (focus_cycler_) {
+    focus_cycler_->RefreshWidget(widget);
+  }
+}
+
+void DisplayOverlayController::OnWidgetDestroying(views::Widget* widget) {
+  widget_observations_.RemoveObservation(widget);
+}
+
 bool DisplayOverlayController::HasMenuView() const {
   return input_menu_view_ != nullptr;
 }
@@ -1098,6 +1219,7 @@
   input_mapping_widget_ = CreateTransientWidget(
       touch_injector_->window(), /*widget_name=*/kInputMapping,
       /*accept_events=*/false, /*is_floating=*/false);
+  widget_observations_.AddObservation(input_mapping_widget_.get());
   input_mapping_widget_->SetContentsView(
       std::make_unique<InputMappingView>(this));
   UpdateInputMappingWidgetBounds();
@@ -1126,11 +1248,12 @@
   editing_list_widget_ = CreateTransientWidget(
       touch_injector_->window(), /*widget_name=*/kEditingList,
       /*accept_events=*/true, /*is_floating=*/true);
+  widget_observations_.AddObservation(editing_list_widget_.get());
   editing_list_widget_->SetContentsView(std::make_unique<EditingList>(this));
   auto* window = editing_list_widget_->GetNativeWindow();
   window->parent()->StackChildAtTop(window);
 
-  editing_list_widget_->ShowInactive();
+  editing_list_widget_->Show();
   UpdateEditingListWidgetBounds();
 }
 
@@ -1152,7 +1275,7 @@
   }
 
   if (visible) {
-    editing_list_widget_->ShowInactive();
+    editing_list_widget_->Show();
   } else {
     editing_list_widget_->Hide();
   }
@@ -1176,6 +1299,18 @@
       button_options_widget_->GetContentsView());
 }
 
+void DisplayOverlayController::AddFocusCycler() {
+  if (!focus_cycler_) {
+    focus_cycler_ = std::make_unique<FocusCycler>();
+  }
+}
+
+void DisplayOverlayController::RemoveFocusCycler() {
+  if (focus_cycler_) {
+    focus_cycler_.reset();
+  }
+}
+
 void DisplayOverlayController::EnterButtonPlaceMode(ActionType action_type) {
   RemoveDeleteEditShortcutWidget();
   SetEditingListVisibility(/*visible=*/false);
diff --git a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h
index e3e2d3d..6c903d8 100644
--- a/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h
+++ b/chrome/browser/ash/arc/input_overlay/display_overlay_controller.h
@@ -10,6 +10,7 @@
 #include "ash/public/cpp/arc_game_controls_flag.h"
 #include "ash/public/cpp/window_properties.h"
 #include "base/memory/raw_ptr.h"
+#include "base/scoped_multi_source_observation.h"
 #include "chrome/browser/ash/arc/input_overlay/actions/input_element.h"
 #include "ui/aura/window_observer.h"
 #include "ui/compositor/property_change_reason.h"
@@ -17,6 +18,7 @@
 #include "ui/events/event_handler.h"
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/views/widget/widget_observer.h"
 
 namespace views {
 class View;
@@ -47,7 +49,8 @@
 // menu, and educational dialog. It also handles the visibility of the
 // `ActionEditMenu` and `MessageView` by listening to the `LocatedEvent`.
 class DisplayOverlayController : public ui::EventHandler,
-                                 public aura::WindowObserver {
+                                 public aura::WindowObserver,
+                                 public views::WidgetObserver {
  public:
   DisplayOverlayController(TouchInjector* touch_injector, bool first_launch);
   DisplayOverlayController(const DisplayOverlayController&) = delete;
@@ -132,6 +135,7 @@
   // ui::EventHandler:
   void OnMouseEvent(ui::MouseEvent* event) override;
   void OnTouchEvent(ui::TouchEvent* event) override;
+  void OnKeyEvent(ui::KeyEvent* event) override;
 
   // aura::WindowObserver:
   void OnWindowBoundsChanged(aura::Window* window,
@@ -142,6 +146,10 @@
                                const void* key,
                                intptr_t old) override;
 
+  // views::WidgetObserver:
+  void OnWidgetVisibilityChanged(views::Widget* widget, bool visible) override;
+  void OnWidgetDestroying(views::Widget* widget) override;
+
   const TouchInjector* touch_injector() const { return touch_injector_; }
 
  private:
@@ -162,6 +170,8 @@
   friend class OverlayViewTestBase;
   friend class RichNudgeTest;
 
+  class FocusCycler;
+
   // Display overlay is added for starting `display_mode`.
   void AddOverlay(DisplayMode display_mode);
   void RemoveOverlayIfAny();
@@ -243,6 +253,10 @@
 
   ButtonOptionsMenu* GetButtonOptionsMenu();
 
+  // Focus cycler operations.
+  void AddFocusCycler();
+  void RemoveFocusCycler();
+
   // Shows or removes target view when in or out button place mode.
   void AddTargetWidget(ActionType action_type);
   void RemoveTargetWidget();
@@ -274,6 +288,9 @@
 
   const raw_ptr<TouchInjector> touch_injector_;
 
+  base::ScopedMultiSourceObservation<views::Widget, views::WidgetObserver>
+      widget_observations_{this};
+
   // References to UI elements owned by the overlay widget.
   raw_ptr<InputMappingView, DanglingUntriaged> input_mapping_view_ = nullptr;
   raw_ptr<InputMenuView, DanglingUntriaged> input_menu_view_ = nullptr;
@@ -293,6 +310,8 @@
   std::unique_ptr<views::Widget> action_highlight_widget_;
   raw_ptr<views::Widget> delete_edit_shortcut_widget_;
   raw_ptr<views::Widget> rich_nudge_widget_;
+
+  std::unique_ptr<FocusCycler> focus_cycler_;
 };
 
 }  // namespace arc::input_overlay
diff --git a/chrome/browser/ash/arc/input_overlay/display_overlay_controller_unittest.cc b/chrome/browser/ash/arc/input_overlay/display_overlay_controller_unittest.cc
index 64fd46e..7addd29 100644
--- a/chrome/browser/ash/arc/input_overlay/display_overlay_controller_unittest.cc
+++ b/chrome/browser/ash/arc/input_overlay/display_overlay_controller_unittest.cc
@@ -6,9 +6,15 @@
 
 #include "ash/public/cpp/arc_game_controls_flag.h"
 #include "chrome/browser/ash/arc/input_overlay/test/game_controls_test_base.h"
+#include "chrome/browser/ash/arc/input_overlay/test/overlay_view_test_base.h"
 #include "chrome/browser/ash/arc/input_overlay/touch_injector.h"
+#include "chrome/browser/ash/arc/input_overlay/ui/button_options_menu.h"
+#include "chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut.h"
+#include "chrome/browser/ash/arc/input_overlay/ui/editing_list.h"
+#include "chrome/browser/ash/arc/input_overlay/ui/input_mapping_view.h"
 #include "chrome/browser/ash/arc/input_overlay/util.h"
 #include "ui/aura/window.h"
+#include "ui/events/event_constants.h"
 #include "ui/views/widget/widget.h"
 
 namespace arc::input_overlay {
@@ -135,4 +141,146 @@
   EXPECT_FALSE(CanRewriteEvent());
 }
 
+// -----------------------------------------------------------------------------
+// EditModeDisplayOverlayControllerTest:
+// For test in the edit mode.
+class EditModeDisplayOverlayControllerTest : public OverlayViewTestBase {
+ public:
+  EditModeDisplayOverlayControllerTest() = default;
+  ~EditModeDisplayOverlayControllerTest() override = default;
+
+  void CheckWidgetsVisible(bool input_mapping_visible,
+                           bool editing_list_visible,
+                           bool button_options_visible,
+                           bool delete_edit_menu_visible) {
+    EXPECT_EQ(
+        input_mapping_visible,
+        input_mapping_view_ && input_mapping_view_->GetWidget()->IsVisible());
+
+    auto* editing_list = GetEditingList();
+    EXPECT_EQ(editing_list_visible,
+              editing_list && editing_list->GetWidget()->IsVisible());
+
+    auto* button_options_menu = GetButtonOptionsMenu();
+    EXPECT_EQ(
+        button_options_visible,
+        button_options_menu && button_options_menu->GetWidget()->IsVisible());
+
+    auto* delete_edit_view = GetDeleteEditShortcut();
+    EXPECT_EQ(delete_edit_menu_visible,
+              delete_edit_view && delete_edit_view->GetWidget()->IsVisible());
+  }
+
+  // Presses key tab until it focuses on the first focusable view in
+  // `contents_view` when `reverse` is true, or the last focusable view on
+  // `contents_view` when `reverse` is false.
+  void PressTabKeyToFirstOrLastElement(views::View* contents_view,
+                                       bool reverse) {
+    auto* event_generator = GetEventGenerator();
+    auto* focus_manager = contents_view->GetFocusManager();
+
+    while (true) {
+      auto* next_focus = focus_manager->GetNextFocusableView(
+          /*starting_view=*/focus_manager->GetFocusedView(),
+          /*starting_widget=*/contents_view->GetWidget(), reverse,
+          /*dont_loop=*/true);
+      if (!next_focus) {
+        break;
+      }
+      event_generator->PressAndReleaseKey(
+          ui::KeyboardCode::VKEY_TAB,
+          (reverse ? ui::EF_SHIFT_DOWN : ui::EF_NONE));
+      EXPECT_TRUE(focus_manager->GetFocusedView());
+    }
+  }
+};
+
+TEST_F(EditModeDisplayOverlayControllerTest, TestFocusCycler) {
+  CheckWidgetsVisible(
+      /*input_mapping_visible=*/true, /*editing_list_visible=*/true,
+      /*button_options_visible=*/false, /*delete_edit_menu_visible=*/false);
+
+  // Case 1: in edit mode default view. The tab focus will cycle between the
+  // editing list and input mapping. Press key tab to the last element of the
+  // editing list.
+  auto* editing_list = GetEditingList();
+  PressTabKeyToFirstOrLastElement(editing_list, /*reverse=*/false);
+  auto* list_focus_manager = editing_list->GetFocusManager();
+  EXPECT_TRUE(list_focus_manager->GetFocusedView());
+
+  // Press key tab to focus on the input mapping.
+  auto* event_generator = GetEventGenerator();
+  event_generator->PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB);
+  EXPECT_FALSE(list_focus_manager->GetFocusedView());
+  auto* mapping_focus_manager = input_mapping_view_->GetFocusManager();
+  EXPECT_TRUE(mapping_focus_manager->GetFocusedView());
+
+  // Keep pressing key tap to the last element of the input mapping.
+  PressTabKeyToFirstOrLastElement(input_mapping_view_, /*reverse=*/false);
+  EXPECT_TRUE(mapping_focus_manager->GetFocusedView());
+  EXPECT_FALSE(list_focus_manager->GetFocusedView());
+
+  // Press key tab to focus on the editing list.
+  event_generator->PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB);
+  EXPECT_FALSE(mapping_focus_manager->GetFocusedView());
+  EXPECT_TRUE(list_focus_manager->GetFocusedView());
+
+  // Press tab + shift and it focuses back to the input mapping.
+  event_generator->PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB,
+                                      ui::EF_SHIFT_DOWN);
+  EXPECT_TRUE(mapping_focus_manager->GetFocusedView());
+  EXPECT_FALSE(list_focus_manager->GetFocusedView());
+
+  // Case 2: show button options menu. The tab focus cycles between the
+  // button options menu and input mapping. (editing list is hidden when button
+  // options menu shows up.)
+  ShowButtonOptionsMenu(tap_action_);
+  CheckWidgetsVisible(
+      /*input_mapping_visible=*/true, /*editing_list_visible=*/false,
+      /*button_options_visible=*/true, /*delete_edit_menu_visible=*/false);
+  auto* button_options_menu = GetButtonOptionsMenu();
+  auto* options_focus_manager = button_options_menu->GetFocusManager();
+  EXPECT_FALSE(mapping_focus_manager->GetFocusedView());
+  EXPECT_FALSE(list_focus_manager->GetFocusedView());
+  EXPECT_FALSE(options_focus_manager->GetFocusedView());
+
+  // Keep pressing key tap to the last element of the button options menu.
+  PressTabKeyToFirstOrLastElement(button_options_menu, /*reverse=*/false);
+  EXPECT_FALSE(mapping_focus_manager->GetFocusedView());
+  EXPECT_TRUE(options_focus_manager->GetFocusedView());
+
+  // Press key tab to focus on the input mapping.
+  event_generator->PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB);
+  EXPECT_TRUE(mapping_focus_manager->GetFocusedView());
+  EXPECT_FALSE(options_focus_manager->GetFocusedView());
+
+  // Close button options menu and editing list shows back.
+  PressDeleteButtonOnButtonOptionsMenu();
+  CheckWidgetsVisible(
+      /*input_mapping_visible=*/true, /*editing_list_visible=*/true,
+      /*button_options_visible=*/false, /*delete_edit_menu_visible=*/false);
+
+  // Case 3: show delete-edit menu. The tab focus cycles among the delete-edit
+  // menu, editing list and input mapping.
+  HoverAtActionViewListItem(/*index=*/1u);
+  CheckWidgetsVisible(
+      /*input_mapping_visible=*/true, /*editing_list_visible=*/true,
+      /*button_options_visible=*/false, /*delete_edit_menu_visible=*/true);
+  auto* delete_edit_shortcut = GetDeleteEditShortcut();
+  auto* delete_edit_focus_manager = delete_edit_shortcut->GetFocusManager();
+  EXPECT_FALSE(mapping_focus_manager->GetFocusedView());
+  EXPECT_FALSE(list_focus_manager->GetFocusedView());
+  EXPECT_FALSE(delete_edit_focus_manager->GetFocusedView());
+
+  PressTabKeyToFirstOrLastElement(delete_edit_shortcut, /*reverse=*/false);
+  EXPECT_FALSE(mapping_focus_manager->GetFocusedView());
+  EXPECT_FALSE(list_focus_manager->GetFocusedView());
+  EXPECT_TRUE(delete_edit_focus_manager->GetFocusedView());
+  // Press key tab to focus on the input mapping.
+  event_generator->PressAndReleaseKey(ui::KeyboardCode::VKEY_TAB);
+  EXPECT_TRUE(mapping_focus_manager->GetFocusedView());
+  EXPECT_FALSE(list_focus_manager->GetFocusedView());
+  EXPECT_FALSE(delete_edit_focus_manager->GetFocusedView());
+}
+
 }  // namespace arc::input_overlay
diff --git a/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.cc b/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.cc
index 1d124836..6048cca 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.cc
+++ b/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.cc
@@ -39,6 +39,34 @@
   controller_->HideActionHighlightWidget();
 }
 
+bool ActionViewListItem::OnKeyPressed(const ui::KeyEvent& event) {
+  if (event.key_code() == ui::VKEY_RIGHT) {
+    controller_->AddDeleteEditShortcutWidget(this);
+    return true;
+  }
+
+  // Don't hide the action view highlight because the focus may traverse inside
+  // of this view. If the next focus view is not inside of this view, then hide
+  // the action view highlight.
+  if (views::FocusManager::IsTabTraversalKeyEvent(event)) {
+    auto* focus_manager = GetFocusManager();
+    if (auto* next_view = focus_manager->GetNextFocusableView(
+            /*starting_view=*/focus_manager->GetFocusedView(),
+            /*starting_widget=*/GetWidget(), /*reverse=*/event.IsShiftDown(),
+            /*dont_loop=*/false);
+        !next_view || !Contains(next_view)) {
+      controller_->HideActionHighlightWidget();
+    }
+    // Tab key is not considered as processed here, so it falls to the end to
+    // return false.
+  }
+  return false;
+}
+
+void ActionViewListItem::OnFocus() {
+  controller_->AddActionHighlightWidget(action_);
+}
+
 BEGIN_METADATA(ActionViewListItem)
 END_METADATA
 
diff --git a/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.h b/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.h
index 5ddeaa9a..84865777 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.h
+++ b/chrome/browser/ash/arc/input_overlay/ui/action_view_list_item.h
@@ -15,7 +15,6 @@
 // ----------------------------
 // | |Name tag|        |keys| |
 // ----------------------------
-
 class ActionViewListItem : public ActionEditView {
   METADATA_HEADER(ActionViewListItem, views::View)
 
@@ -36,6 +35,8 @@
   // views::View:
   void OnMouseEntered(const ui::MouseEvent& event) override;
   void OnMouseExited(const ui::MouseEvent& event) override;
+  bool OnKeyPressed(const ui::KeyEvent& event) override;
+  void OnFocus() override;
 };
 
 }  // namespace arc::input_overlay
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 59c1f1e..d31f0d62 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
@@ -13,10 +13,14 @@
 #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/ui/action_view_list_item.h"
+#include "chrome/browser/ash/arc/input_overlay/util.h"
+#include "chromeos/strings/grit/chromeos_strings.h"
+#include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/chromeos/styles/cros_tokens_color_mappings.h"
 #include "ui/color/color_provider.h"
 #include "ui/gfx/geometry/insets.h"
+#include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/bubble/bubble_border.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/views/layout/fill_layout.h"
@@ -36,7 +40,7 @@
                                        ActionViewListItem* anchor_view)
     : views::BubbleDialogDelegateView(anchor_view,
                                       views::BubbleBorder::LEFT_CENTER,
-                                      views::BubbleBorder::NO_SHADOW),
+                                      views::BubbleBorder::DIALOG_SHADOW),
       controller_(controller) {
   set_margins(gfx::Insets(12));
   set_corner_radius(20);
@@ -45,29 +49,55 @@
   set_internal_name(kDeleteEditShortcut);
   set_parent_window(anchor_view->GetWidget()->GetNativeWindow());
   SetButtons(ui::DIALOG_BUTTON_NONE);
+  SetAccessibleWindowRole(ax::mojom::Role::kMenu);
 
   SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical,
       /*inside_border_insets=*/gfx::Insets(),
       /*between_child_spacing=*/12));
 
-  AddChildView(std::make_unique<ash::IconButton>(
+  // Create the buttons with empty accessibility names. They will be updated in
+  // `UpdateTooltipText`.
+  edit_button_ = AddChildView(std::make_unique<ash::IconButton>(
       base::BindRepeating(&DeleteEditShortcut::OnEditButtonPressed,
                           base::Unretained(this)),
-      ash::IconButton::Type::kMedium, &kGameControlsEditPenIcon,
-      IDS_APP_LIST_FOLDER_NAME_PLACEHOLDER));
+      ash::IconButton::Type::kMedium, &kGameControlsEditPenIcon, u"",
+      /*is_togglable=*/false, /*has_border=*/false));
 
-  AddChildView(std::make_unique<ash::IconButton>(
+  delete_button_ = AddChildView(std::make_unique<ash::IconButton>(
       base::BindRepeating(&DeleteEditShortcut::OnDeleteButtonPressed,
                           base::Unretained(this)),
-      ash::IconButton::Type::kMedium, &kGameControlsDeleteIcon,
-      IDS_APP_LIST_FOLDER_NAME_PLACEHOLDER));
+      ash::IconButton::Type::kMedium, &kGameControlsDeleteIcon, u"",
+      /*is_togglable=*/false, /*has_border=*/false));
+  UpdateTooltipText(anchor_view);
 }
 
 DeleteEditShortcut::~DeleteEditShortcut() = default;
 
 void DeleteEditShortcut::UpdateAnchorView(ActionViewListItem* anchor_view) {
+  // Reset the highlight of previous anchor view.
+  SetHighlightedButton(anchor_view);
+
   SetAnchorView(anchor_view);
+  UpdateTooltipText(anchor_view);
+
+  // Clear focus when changing anchor view.
+  if (auto* focus_manager = GetFocusManager()) {
+    focus_manager->ClearFocus();
+  }
+}
+
+void DeleteEditShortcut::UpdateTooltipText(ActionViewListItem* anchor_view) {
+  const std::u16string action_name = anchor_view->CalculateActionName();
+  // e.g. "Edit Button m"
+  const std::u16string edit_text = l10n_util::GetStringFUTF16(
+      IDS_INPUT_OVERLAY_SHORTCUT_EDIT_A11Y_LABEL_TEMPLATE, action_name);
+  edit_button_->SetTooltipText(edit_text);
+
+  // e.g. "Delete Joystick wasd"
+  const std::u16string delete_text = l10n_util::GetStringFUTF16(
+      IDS_INPUT_OVERLAY_SHORTCUT_DELETE_A11Y_LABEL_TEMPLATE, action_name);
+  delete_button_->SetTooltipText(delete_text);
 }
 
 void DeleteEditShortcut::OnEditButtonPressed() {
diff --git a/chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut.h b/chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut.h
index c7dbd194..3fa7de6 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut.h
+++ b/chrome/browser/ash/arc/input_overlay/ui/delete_edit_shortcut.h
@@ -13,7 +13,11 @@
 
 namespace views {
 class NonClientFrameView;
-}
+}  // namespace views
+
+namespace ash {
+class IconButton;
+}  // namespace ash
 
 namespace arc::input_overlay {
 
@@ -44,6 +48,10 @@
  private:
   friend class DeleteEditShortcutTest;
 
+  // Updates tooltip text for both `edit_button_` and `delete_button_`. A11y
+  // name is updated as well.
+  void UpdateTooltipText(ActionViewListItem* anchor_view);
+
   // Handle button functions.
   void OnEditButtonPressed();
   void OnDeleteButtonPressed();
@@ -58,6 +66,9 @@
 
   // DisplayOverlayController owns this class, no need to deallocate.
   const raw_ptr<DisplayOverlayController> controller_ = nullptr;
+
+  raw_ptr<ash::IconButton> edit_button_;
+  raw_ptr<ash::IconButton> delete_button_;
 };
 
 }  // namespace arc::input_overlay
diff --git a/chrome/browser/ash/arc/input_overlay/ui/name_tag.h b/chrome/browser/ash/arc/input_overlay/ui/name_tag.h
index f8aa540..203e64b 100644
--- a/chrome/browser/ash/arc/input_overlay/ui/name_tag.h
+++ b/chrome/browser/ash/arc/input_overlay/ui/name_tag.h
@@ -52,6 +52,7 @@
   void SetState(bool is_error, const std::u16string& error_tooltip);
 
   views::ImageView* error_icon() const { return error_icon_; }
+  views::Label* title_label() { return title_label_; }
 
  private:
   friend class EditLabelTest;
diff --git a/chrome/browser/ash/remote_apps/remote_apps_manager_browsertest.cc b/chrome/browser/ash/remote_apps/remote_apps_manager_browsertest.cc
index 9ca57abf..12b5913 100644
--- a/chrome/browser/ash/remote_apps/remote_apps_manager_browsertest.cc
+++ b/chrome/browser/ash/remote_apps/remote_apps_manager_browsertest.cc
@@ -365,7 +365,9 @@
 
 // Adds an app with an empty icon URL and checks if the app gets assigned the
 // default placeholder icon.
-IN_PROC_BROWSER_TEST_F(RemoteAppsManagerBrowsertest, AddAppPlaceholderIcon) {
+// Flaky (b/41483673)
+IN_PROC_BROWSER_TEST_F(RemoteAppsManagerBrowsertest,
+                       DISABLED_AddAppPlaceholderIcon) {
   // Show launcher UI so that app icons are loaded.
   ShowLauncherAppsGrid(/*wait_for_opening_animation=*/true);
 
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.cc b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.cc
index f706d95..be03fbbc 100644
--- a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.cc
+++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.cc
@@ -47,66 +47,6 @@
 
 constexpr int kSeaPenImageThumbnailSizeDip = 512;
 
-/**
- * Serializes a sea pen query information `query` into json
- * string format based on the query type. Such as {creation_time:<number>,
- * freeform_query:<string>} or {creation_time:<number>,
- * user_visible_query_text:<string>, user_visible_query_template:<string>,
- * template_id:<number>, options:{<chip_number>:<option_number>, ...}}. For
- * example:
- * {"creation_time":"13349580387513653", "freeform_query":"test freeform query"}
- * {"creation_time":"13349580387513653", "user_visible_query_text": "test
- * template query", "user_visible_query_template": "test template",
- * "template_id":"2","options":{"4":"34","5":"40"}}
- *
- * @param query  pointer to the sea pen query
- * @return query information in string format
- */
-std::string SeaPenQueryToJsonString(const mojom::SeaPenQueryPtr& query) {
-  base::Value::Dict query_dict = base::Value::Dict();
-  query_dict.Set(wallpaper_constants::kSeaPenCreationTimeKey,
-                 base::TimeToValue(base::Time::Now()));
-
-  switch (query->which()) {
-    case mojom::SeaPenQuery::Tag::kTextQuery:
-      query_dict.Set(wallpaper_constants::kSeaPenFreeformQueryKey,
-                     query->get_text_query());
-      break;
-    case mojom::SeaPenQuery::Tag::kTemplateQuery:
-      query_dict.Set(wallpaper_constants::kSeaPenTemplateIdKey,
-                     base::NumberToString(static_cast<int32_t>(
-                         query->get_template_query()->id)));
-      base::Value::Dict options_dict = base::Value::Dict();
-      for (const auto& [chip, option] : query->get_template_query()->options) {
-        options_dict.Set(base::NumberToString(static_cast<int32_t>(chip)),
-                         base::NumberToString(static_cast<int32_t>(option)));
-      }
-      query_dict.Set(wallpaper_constants::kSeaPenTemplateOptionsKey,
-                     std::move(options_dict));
-      query_dict.Set(wallpaper_constants::kSeaPenUserVisibleQueryTextKey,
-                     query->get_template_query()->user_visible_query->text);
-      query_dict.Set(
-          wallpaper_constants::kSeaPenUserVisibleQueryTemplateKey,
-          query->get_template_query()->user_visible_query->template_title);
-      break;
-  }
-
-  return base::WriteJson(query_dict).value_or("");
-}
-
-// Constructs the xmp metadata string from the string query information.
-std::string QueryInfoToXmpString(const std::string& query_info) {
-  static constexpr char kXmpData[] = R"(
-            <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0">
-               <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-                  <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
-                     <dc:description>%s</dc:description>
-                  </rdf:Description>
-               </rdf:RDF>
-            </x:xmpmeta>)";
-  return base::StringPrintf(kXmpData, query_info.c_str());
-}
-
 }  // namespace
 
 PersonalizationAppSeaPenProviderBase::PersonalizationAppSeaPenProviderBase(
@@ -256,10 +196,7 @@
   }
 
   CHECK(last_query_);
-  const std::string query_info =
-      QueryInfoToXmpString(SeaPenQueryToJsonString(last_query_));
-
-  OnFetchWallpaperDoneInternal(*image, query_info, std::move(callback));
+  OnFetchWallpaperDoneInternal(*image, last_query_, std::move(callback));
 }
 
 void PersonalizationAppSeaPenProviderBase::OnRecentSeaPenImageSelected(
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.h b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.h
index ff78fd3..66f79ee 100644
--- a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.h
+++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.h
@@ -94,7 +94,7 @@
 
   virtual void OnFetchWallpaperDoneInternal(
       const SeaPenImage& sea_pen_image,
-      const std::string& query_info,
+      const mojom::SeaPenQueryPtr& query,
       base::OnceCallback<void(bool success)> callback) = 0;
 
   manta::proto::FeatureName feature_name_;
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl.cc b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl.cc
index dd4c0611..d9a9c0c 100644
--- a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl.cc
+++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl.cc
@@ -11,6 +11,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/image_util.h"
 #include "ash/public/cpp/wallpaper/wallpaper_controller.h"
+#include "ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.h"
 #include "ash/webui/common/mojom/sea_pen.mojom.h"
 #include "base/path_service.h"
 #include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.h"
@@ -88,8 +89,11 @@
 
 void PersonalizationAppSeaPenProviderImpl::OnFetchWallpaperDoneInternal(
     const SeaPenImage& sea_pen_image,
-    const std::string& query_info,
+    const mojom::SeaPenQueryPtr& query,
     base::OnceCallback<void(bool success)> callback) {
+  // TODO(b/321778818): move the query_info string construction to
+  // SeaPenWallpaperManager.
+  const std::string query_info = QueryDictToXmpString(SeaPenQueryToDict(query));
   auto* wallpaper_controller = ash::WallpaperController::Get();
   wallpaper_controller->SetSeaPenWallpaper(
       GetAccountId(profile_), sea_pen_image, query_info, std::move(callback));
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl.h b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl.h
index d500d4e..d7f764a 100644
--- a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl.h
+++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_impl.h
@@ -58,7 +58,7 @@
 
   void OnFetchWallpaperDoneInternal(
       const SeaPenImage& sea_pen_image,
-      const std::string& query_info,
+      const mojom::SeaPenQueryPtr& query,
       base::OnceCallback<void(bool success)> callback) override;
 };
 
diff --git a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_wallpaper_provider_impl.cc b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_wallpaper_provider_impl.cc
index 1b51f4e..42127dc3 100644
--- a/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_wallpaper_provider_impl.cc
+++ b/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_wallpaper_provider_impl.cc
@@ -25,6 +25,7 @@
 #include "ash/public/cpp/wallpaper/wallpaper_types.h"
 #include "ash/public/cpp/window_backdrop.h"
 #include "ash/wallpaper/wallpaper_constants.h"
+#include "ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.h"
 #include "ash/wallpaper/wallpaper_utils/wallpaper_online_variant_utils.h"
 #include "ash/wallpaper/wallpaper_utils/wallpaper_resizer.h"
 #include "ash/webui/personalization_app/mojom/personalization_app.mojom.h"
@@ -1117,8 +1118,8 @@
 
   std::vector<std::string> attribution;
 
-  auto* freeform_query = sea_pen_metadata->FindString(
-      wallpaper_constants::kSeaPenFreeformQueryKey);
+  auto* freeform_query =
+      sea_pen_metadata->FindString(ash::kSeaPenFreeformQueryKey);
   if (freeform_query) {
     // The Sea Pen wallpaper was generated from a freeform query.
     attribution.push_back(*freeform_query);
@@ -1130,13 +1131,13 @@
 
   // Otherwise, it should be generated from a template query, get the user
   // visible query and add into `attributions`.
-  auto* user_visible_query_text = sea_pen_metadata->FindString(
-      wallpaper_constants::kSeaPenUserVisibleQueryTextKey);
+  auto* user_visible_query_text =
+      sea_pen_metadata->FindString(ash::kSeaPenUserVisibleQueryTextKey);
   if (user_visible_query_text) {
     attribution.push_back(*user_visible_query_text);
   }
-  auto* user_visible_query_template = sea_pen_metadata->FindString(
-      wallpaper_constants::kSeaPenUserVisibleQueryTemplateKey);
+  auto* user_visible_query_template =
+      sea_pen_metadata->FindString(ash::kSeaPenUserVisibleQueryTemplateKey);
   if (user_visible_query_template) {
     attribution.push_back(*user_visible_query_template);
   }
diff --git a/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.cc b/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.cc
index 479183c..8137dd7 100644
--- a/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.cc
+++ b/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.cc
@@ -11,7 +11,7 @@
 #include "ash/constants/ash_features.h"
 #include "ash/shell.h"
 #include "ash/system/camera/camera_effects_controller.h"
-#include "ash/webui/common/mojom/sea_pen.mojom.h"
+#include "ash/wallpaper/wallpaper_utils/sea_pen_metadata_utils.h"
 #include "base/functional/bind.h"
 #include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_sea_pen_provider_base.h"
 #include "chrome/browser/ash/wallpaper_handlers/wallpaper_fetcher_delegate.h"
@@ -100,10 +100,11 @@
 
 void VcBackgroundUISeaPenProviderImpl::OnFetchWallpaperDoneInternal(
     const SeaPenImage& sea_pen_image,
-    const std::string& query_info,
+    const ash::personalization_app::mojom::SeaPenQueryPtr& query,
     base::OnceCallback<void(bool success)> callback) {
+  const std::string metadata = QueryDictToXmpString(SeaPenQueryToDict(query));
   GetCameraEffectsController()->SetBackgroundImageFromContent(
-      sea_pen_image, query_info, std::move(callback));
+      sea_pen_image, metadata, std::move(callback));
 }
 
 }  // namespace ash::vc_background_ui
diff --git a/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.h b/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.h
index bab6e3c5..58c93d33 100644
--- a/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.h
+++ b/chrome/browser/ash/system_web_apps/apps/vc_background_ui/vc_background_ui_sea_pen_provider_impl.h
@@ -58,7 +58,7 @@
 
   void OnFetchWallpaperDoneInternal(
       const SeaPenImage& sea_pen_image,
-      const std::string& query_info,
+      const ash::personalization_app::mojom::SeaPenQueryPtr& query,
       base::OnceCallback<void(bool success)> callback) override;
 };
 
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index ffe74cfb..bb34a18 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -7401,22 +7401,24 @@
 }
 
 void ChromeContentBrowserClient::IsClipboardCopyAllowedByPolicy(
-    content::BrowserContext* browser_context,
-    const GURL& url,
-    size_t data_size_in_bytes,
+    const content::ClipboardEndpoint& source,
+    const content::ClipboardMetadata& metadata,
+    const std::u16string& data,
     IsClipboardCopyAllowedCallback callback) {
 #if BUILDFLAG(ENTERPRISE_DATA_CONTROLS)
   enterprise_data_protection::IsClipboardCopyAllowedByPolicy(
-      browser_context, url, data_size_in_bytes, std::move(callback));
+      source, metadata, data, std::move(callback));
 #else
   std::u16string replacement_data;
   ClipboardRestrictionService* service =
       ClipboardRestrictionServiceFactory::GetInstance()->GetForBrowserContext(
-          browser_context);
-  if (service->IsUrlAllowedToCopy(url, data_size_in_bytes, &replacement_data)) {
-    std::move(callback).Run(std::nullopt);
+          source.browser_context());
+  if (service->IsUrlAllowedToCopy(*source.data_transfer_endpoint()->GetURL(),
+                                  metadata.size.value_or(0),
+                                  &replacement_data)) {
+    std::move(callback).Run(data, std::nullopt);
   } else {
-    std::move(callback).Run(std::move(replacement_data));
+    std::move(callback).Run(data, std::move(replacement_data));
   }
 #endif  // BUILDFLAG(ENTERPRISE_DATA_CONTROLS)
 }
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index b9f99579..a11cb4b 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -845,9 +845,9 @@
       IsClipboardPasteAllowedCallback callback) override;
 
   void IsClipboardCopyAllowedByPolicy(
-      content::BrowserContext* browser_context,
-      const GURL& url,
-      size_t data_size_in_bytes,
+      const content::ClipboardEndpoint& source,
+      const content::ClipboardMetadata& metadata,
+      const std::u16string& data,
       IsClipboardCopyAllowedCallback callback) override;
 
 #if BUILDFLAG(ENABLE_VR)
diff --git a/chrome/browser/compose/chrome_compose_client.cc b/chrome/browser/compose/chrome_compose_client.cc
index ab53d18f..c3f530b 100644
--- a/chrome/browser/compose/chrome_compose_client.cc
+++ b/chrome/browser/compose/chrome_compose_client.cc
@@ -14,6 +14,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/bind_post_task.h"
 #include "base/task/thread_pool.h"
+#include "base/third_party/icu/icu_utf.h"
 #include "chrome/browser/compose/compose_enabling.h"
 #include "chrome/browser/compose/compose_text_usage_logger.h"
 #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
@@ -68,6 +69,16 @@
   }
 }
 
+std::u16string RemoveLastCharIfInvalid(std::u16string str) {
+  // TODO(b/323902463): Have Autofill send a valid string, i.e. truncated to a
+  // valid grapheme, in FormFieldData.selected_text to ensure greatest
+  // preservation of the original selected text.
+  if (!str.empty() && CBU16_IS_LEAD(str.back())) {
+    str.pop_back();
+  }
+  return str;
+}
+
 }  // namespace
 
 ChromeComposeClient::ChromeComposeClient(content::WebContents* web_contents)
@@ -280,7 +291,13 @@
   active_compose_ids_ = std::make_optional<
       std::pair<autofill::FieldGlobalId, autofill::FormGlobalId>>(
       trigger_field.global_id(), trigger_field.renderer_form_id());
-  std::string selected_text = base::UTF16ToUTF8(trigger_field.selected_text);
+  // The selected text received from Autofill is a UTF-16 string truncated using
+  // substr, which will result in a rendered invalid character in the Compose
+  // dialog if it splits a surrogate pair character. Ensure that any invalid
+  // characters are removed.
+  std::string selected_text =
+      base::UTF16ToUTF8(RemoveLastCharIfInvalid(trigger_field.selected_text));
+
   ComposeSession* current_session;
 
   // We only want to resume if the popup was clicked or the selection is empty.
diff --git a/chrome/browser/compose/chrome_compose_client.h b/chrome/browser/compose/chrome_compose_client.h
index 43602e628..a1d1b54 100644
--- a/chrome/browser/compose/chrome_compose_client.h
+++ b/chrome/browser/compose/chrome_compose_client.h
@@ -10,6 +10,7 @@
 #include <string>
 
 #include "base/containers/flat_map.h"
+#include "base/gtest_prod_util.h"
 #include "base/token.h"
 #include "chrome/browser/compose/compose_enabling.h"
 #include "chrome/browser/compose/compose_session.h"
@@ -151,6 +152,10 @@
 
  private:
   friend class content::WebContentsUserData<ChromeComposeClient>;
+  FRIEND_TEST_ALL_PREFIXES(ChromeComposeClientTest,
+                           TestComposeQualityFeedbackPositive);
+  FRIEND_TEST_ALL_PREFIXES(ChromeComposeClientTest,
+                           TestComposeQualityFeedbackNegative);
 
   raw_ptr<Profile> profile_;
   raw_ptr<PrefService> pref_service_;
diff --git a/chrome/browser/compose/chrome_compose_client_unittest.cc b/chrome/browser/compose/chrome_compose_client_unittest.cc
index 5e8f9213..7e289d1 100644
--- a/chrome/browser/compose/chrome_compose_client_unittest.cc
+++ b/chrome/browser/compose/chrome_compose_client_unittest.cc
@@ -309,6 +309,12 @@
     field_data().selected_text = selection;
   }
 
+  // Emulate selected text truncation performed by Autofill.
+  void SetSelectionWithTruncation(const std::u16string& selection,
+                                  size_t max_length) {
+    field_data().selected_text = selection.substr(0, max_length);
+  }
+
  protected:
   optimization_guide::proto::ComposePageMetadata ComposePageMetadata() {
     optimization_guide::proto::ComposePageMetadata page_metadata;
@@ -1118,6 +1124,21 @@
   client_page_handler()->CloseUI(compose::mojom::CloseReason::kCloseButton);
 }
 
+// Tests that an unpaired high surrogate resulting from truncation by substr is
+// properly removed.
+TEST_F(ChromeComposeClientTest, TestOpenDialogWithTruncatedSelectedText) {
+  std::u16string input(u".🦄🦄🦄");
+  field_data().value = input;
+  SetSelectionWithTruncation(input, 6);
+  ShowDialogAndBindMojo();
+
+  base::test::TestFuture<compose::mojom::OpenMetadataPtr> open_test_future;
+  page_handler()->RequestInitialState(open_test_future.GetCallback());
+
+  compose::mojom::OpenMetadataPtr result = open_test_future.Take();
+  EXPECT_EQ(".🦄🦄", result->initial_input);
+}
+
 // Tests that opening the dialog with user selected text will return that text
 // when the WebUI requests initial state.
 TEST_F(ChromeComposeClientTest, TestOpenDialogWithSelectedText) {
@@ -2176,6 +2197,14 @@
       base::ScopedMockElapsedTimersForTest::kMockElapsedTime.InMilliseconds(),
       quality_result->quality_data<optimization_guide::ComposeFeatureTypeMap>()
           ->request_latency_ms());
+
+  // Check that histogram was sent for Compose State removed from undo stack.
+  histograms().ExpectBucketCount("Compose.Server.Request.Feedback",
+                                 compose::ComposeRequestFeedback::kNoFeedback,
+                                 0);
+  histograms().ExpectBucketCount("Compose.Server.Request.Feedback",
+                                 compose::ComposeRequestFeedback::kRequestError,
+                                 2);
 }
 
 TEST_F(ChromeComposeClientTest, TestComposeQualityLatency) {
@@ -2287,6 +2316,92 @@
                 ->final_status());
 }
 
+TEST_F(ChromeComposeClientTest, TestComposeQualityFeedbackPositive) {
+  base::test::TestFuture<compose::mojom::ComposeResponsePtr> compose_future;
+  BindComposeFutureToOnResponseReceived(compose_future);
+
+  EXPECT_CALL(session(), ExecuteModel(_, _)).Times(1);
+
+  base::test::TestFuture<
+      std::unique_ptr<optimization_guide::ModelQualityLogEntry>>
+      quality_test_future;
+
+  EXPECT_CALL(model_quality_logs_uploader(), UploadModelQualityLogs(_))
+      .WillRepeatedly(testing::Invoke(
+          [&](std::unique_ptr<optimization_guide::ModelQualityLogEntry>
+                  response) {
+            quality_test_future.SetValue(std::move(response));
+          }));
+
+  ShowDialogAndBindMojo();
+  client().GetSessionForActiveComposeField()->SetAllowFeedbackForTesting(true);
+
+  page_handler()->Compose("a user typed this", false);
+  ASSERT_TRUE(compose_future.Take());
+
+  page_handler()->SetUserFeedback(
+      compose::mojom::UserFeedback::kUserFeedbackPositive);
+
+  // Close UI to submit remaining quality logs.
+  client_page_handler()->CloseUI(compose::mojom::CloseReason::kCloseButton);
+
+  // Get quality logs sent for the Compose Request
+  std::unique_ptr<optimization_guide::ModelQualityLogEntry> result =
+      quality_test_future.Take();
+
+  EXPECT_EQ(optimization_guide::proto::UserFeedback::USER_FEEDBACK_THUMBS_UP,
+            result->quality_data<optimization_guide::ComposeFeatureTypeMap>()
+                ->user_feedback());
+
+  // Check that the histogram was sent for request feedback.
+  histograms().ExpectUniqueSample(
+      "Compose.Server.Request.Feedback",
+      compose::ComposeRequestFeedback::kPositiveFeedback, 1);
+}
+
+TEST_F(ChromeComposeClientTest, TestComposeQualityFeedbackNegative) {
+  base::test::TestFuture<compose::mojom::ComposeResponsePtr> compose_future;
+  BindComposeFutureToOnResponseReceived(compose_future);
+
+  EXPECT_CALL(session(), ExecuteModel(_, _)).Times(1);
+
+  base::test::TestFuture<
+      std::unique_ptr<optimization_guide::ModelQualityLogEntry>>
+      quality_test_future;
+
+  EXPECT_CALL(model_quality_logs_uploader(), UploadModelQualityLogs(_))
+      .WillRepeatedly(testing::Invoke(
+          [&](std::unique_ptr<optimization_guide::ModelQualityLogEntry>
+                  response) {
+            quality_test_future.SetValue(std::move(response));
+          }));
+
+  ShowDialogAndBindMojo();
+  client().GetSessionForActiveComposeField()->SetAllowFeedbackForTesting(true);
+
+  page_handler()->Compose("a user typed this", false);
+  ASSERT_TRUE(compose_future.Take());
+
+  page_handler()->SetUserFeedback(
+      compose::mojom::UserFeedback::kUserFeedbackNegative);
+
+  // Close UI to submit remaining quality logs.
+  client_page_handler()->CloseUI(compose::mojom::CloseReason::kCloseButton);
+
+  // Get quality logs sent for the Compose Request
+  std::unique_ptr<optimization_guide::ModelQualityLogEntry> result =
+      quality_test_future.Take();
+
+  EXPECT_EQ(optimization_guide::proto::UserFeedback::USER_FEEDBACK_THUMBS_DOWN,
+            result->quality_data<optimization_guide::ComposeFeatureTypeMap>()
+                ->user_feedback());
+
+  // Check that the histogram was sent for request feedback.
+  histograms().ExpectUniqueSample(
+      "Compose.Server.Request.Feedback",
+      compose::ComposeRequestFeedback::kNegativeFeedback, 1);
+}
+
 TEST_F(ChromeComposeClientTest, TestComposeQualityWasEdited) {
   ShowDialogAndBindMojo();
 
@@ -2339,6 +2454,14 @@
   histograms().ExpectBucketCount(compose::kComposeRequestReason,
                                  compose::ComposeRequestReason::kUpdateRequest,
                                  1);
+
+  EXPECT_EQ(optimization_guide::proto::FinalStatus::STATUS_UNSPECIFIED,
+            result->quality_data<optimization_guide::ComposeFeatureTypeMap>()
+                ->final_status());
+  // Check that the histogram was sent for request feedback.
+  histograms().ExpectUniqueSample("Compose.Server.Request.Feedback",
+                                  compose::ComposeRequestFeedback::kNoFeedback,
+                                  2);
 }
 
 TEST_F(ChromeComposeClientTest, TestRegenerate) {
diff --git a/chrome/browser/compose/compose_session.cc b/chrome/browser/compose/compose_session.cc
index 7e8571e..186f690 100644
--- a/chrome/browser/compose/compose_session.cc
+++ b/chrome/browser/compose/compose_session.cc
@@ -10,6 +10,7 @@
 
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
 #include "base/strings/string_tokenizer.h"
@@ -168,6 +169,44 @@
     mojo_state_ = std::move(mojo_state);
   }
 
+  void UploadModelQualityLogs(
+      raw_ptr<optimization_guide::ModelQualityLogsUploader> logs_uploader) {
+    if (!logs_uploader || !modeling_log_entry_) {
+      return;
+    }
+    LogRequestFeedback();
+    logs_uploader->UploadModelQualityLogs(TakeModelingLogEntry());
+  }
+
+  void LogRequestFeedback() {
+    if (!mojo_state_ || !mojo_state_->response) {
+      // No request or modeling information so nothing to report.
+      return;
+    }
+    if (mojo_state_->response->status != compose::mojom::ComposeStatus::kOk) {
+      // Request Feedback was already reported when error was received.
+      return;
+    }
+
+    compose::EvalLocation eval_location =
+        mojo_state_->response->on_device_evaluation_used
+            ? compose::EvalLocation::kOnDevice
+            : compose::EvalLocation::kServer;
+    compose::ComposeRequestFeedback feedback;
+    switch (mojo_state_->feedback) {
+      case compose::mojom::UserFeedback::kUserFeedbackPositive:
+        feedback = compose::ComposeRequestFeedback::kPositiveFeedback;
+        break;
+      case compose::mojom::UserFeedback::kUserFeedbackNegative:
+        feedback = compose::ComposeRequestFeedback::kNegativeFeedback;
+        break;
+      case compose::mojom::UserFeedback::kUserFeedbackUnspecified:
+        feedback = compose::ComposeRequestFeedback::kNoFeedback;
+        break;
+    }
+    compose::LogComposeRequestFeedback(eval_location, feedback);
+  }
+
  private:
   std::unique_ptr<optimization_guide::ModelQualityLogEntry> modeling_log_entry_;
   compose::mojom::ComposeStatePtr mojo_state_;
@@ -256,41 +295,33 @@
 
   LogComposeSessionCloseUkmMetrics(ukm_source_id_, session_events_);
 
-  // If we have a modeling quality log entry, upload it.
-
-  // If the latest result was an error upload that state
-  if (most_recent_error_log_) {
-    most_recent_error_log_
-        ->quality_data<optimization_guide::ComposeFeatureTypeMap>()
-        ->set_final_status(final_status_);
-  } else if (most_recent_ok_state_->modeling_log_entry()) {
-    most_recent_ok_state_->modeling_log_entry()
-        ->quality_data<optimization_guide::ComposeFeatureTypeMap>()
-        ->set_final_status(final_status_);
-  }
-
   // Quality log would automatically be uploaded on the destruction of
   // a modeling_log_entry. However in order to more easily test the quality
   // uploads we are calling upload directly here.
-  if (model_quality_logs_uploader_) {
-    if (most_recent_error_log_) {
-      model_quality_logs_uploader_->UploadModelQualityLogs(
-          std::move(most_recent_error_log_));
-    }
-    if (most_recent_ok_state_->modeling_log_entry()) {
-      model_quality_logs_uploader_->UploadModelQualityLogs(
-          most_recent_ok_state_->TakeModelingLogEntry());
-    }
+
+  if (!model_quality_logs_uploader_) {
+    // Can not upload any logs so exit early.
+    return;
+  }
+
+  if (most_recent_error_log_) {
+    // First set final status on most_recent_error_log
+    most_recent_error_log_
+        ->quality_data<optimization_guide::ComposeFeatureTypeMap>()
+        ->set_final_status(final_status_);
+    model_quality_logs_uploader_->UploadModelQualityLogs(
+        std::move(most_recent_error_log_));
+  } else if (most_recent_ok_state_->modeling_log_entry()) {
+    // First set final status on most_recent_ok_state_.
+    most_recent_ok_state_->modeling_log_entry()
+        ->quality_data<optimization_guide::ComposeFeatureTypeMap>()
+        ->set_final_status(final_status_);
+    most_recent_ok_state_->UploadModelQualityLogs(model_quality_logs_uploader_);
   }
 
   // Explicitly upload the rest of the undo stack.
   while (!undo_states_.empty()) {
-    if (undo_states_.top()->modeling_log_entry()) {
-      if (model_quality_logs_uploader_) {
-        model_quality_logs_uploader_->UploadModelQualityLogs(
-            undo_states_.top()->TakeModelingLogEntry());
-      }
-    }
+    undo_states_.top()->UploadModelQualityLogs(model_quality_logs_uploader_);
     undo_states_.pop();
   }
 }
@@ -529,6 +560,10 @@
                                   compose::mojom::ComposeStatus error) {
   compose::LogComposeRequestStatus(eval_location, error);
 
+  // Feedback can not be given for a request with an error so report now.
+  compose::LogComposeRequestFeedback(
+      eval_location, compose::ComposeRequestFeedback::kRequestError);
+
   current_state_->has_pending_request = false;
   current_state_->response = compose::mojom::ComposeResponse::New();
   current_state_->response->status = error;
@@ -582,11 +617,8 @@
 
   // upload the most recent modeling quality log entry before overwriting it
   // with state from undo,
-  if (most_recent_ok_state_->modeling_log_entry() &&
-      model_quality_logs_uploader_) {
-    model_quality_logs_uploader_->UploadModelQualityLogs(
-        most_recent_ok_state_->TakeModelingLogEntry());
-  }
+
+  most_recent_ok_state_->UploadModelQualityLogs(model_quality_logs_uploader_);
 
   if (!undo_state->IsMojoValid()) {
     // Gracefully fail if we find an invalid state on the undo stack.
@@ -649,6 +681,11 @@
 void ComposeSession::OpenFeedbackPage(std::string feedback_id) {
   base::Value::Dict feedback_metadata;
   feedback_metadata.Set("log_id", feedback_id);
+
+  if (allow_feedback_for_testing_) {
+    return;
+  }
+
   chrome::ShowFeedbackPage(
       web_contents_->GetLastCommittedURL(),
       Profile::FromBrowserContext(web_contents_->GetBrowserContext()),
@@ -667,13 +704,17 @@
     // feedback to.
     return;
   }
-  OptimizationGuideKeyedService* opt_guide_keyed_service =
-      OptimizationGuideKeyedServiceFactory::GetForProfile(
-          Profile::FromBrowserContext(web_contents_->GetBrowserContext()));
-  if (!opt_guide_keyed_service ||
-      !opt_guide_keyed_service->ShouldFeatureBeCurrentlyAllowedForLogging(
-          optimization_guide::proto::MODEL_EXECUTION_FEATURE_COMPOSE)) {
-    return;
+
+  // TODO(b/314199871): Remove test bypass once this check becomes mock-able.
+  if (!allow_feedback_for_testing_) {
+    OptimizationGuideKeyedService* opt_guide_keyed_service =
+        OptimizationGuideKeyedServiceFactory::GetForProfile(
+            Profile::FromBrowserContext(web_contents_->GetBrowserContext()));
+    if (!opt_guide_keyed_service ||
+        !opt_guide_keyed_service->ShouldFeatureBeCurrentlyAllowedForLogging(
+            optimization_guide::proto::MODEL_EXECUTION_FEATURE_COMPOSE)) {
+      return;
+    }
   }
 
   // Add to most_recent_ok_state_ in case of undos.
@@ -928,3 +969,7 @@
     msbb_initially_off_ = false;
   }
 }
+
+void ComposeSession::SetAllowFeedbackForTesting(bool allowed) {
+  allow_feedback_for_testing_ = allowed;
+}
diff --git a/chrome/browser/compose/compose_session.h b/chrome/browser/compose/compose_session.h
index 10b1c33d..289ea4c 100644
--- a/chrome/browser/compose/compose_session.h
+++ b/chrome/browser/compose/compose_session.h
@@ -10,6 +10,7 @@
 #include <string>
 
 #include "base/check_op.h"
+#include "base/functional/callback_helpers.h"
 #include "base/timer/elapsed_timer.h"
 #include "chrome/browser/content_extraction/inner_text.h"
 #include "chrome/common/compose/compose.mojom.h"
@@ -180,6 +181,8 @@
 
   void SetCloseReason(compose::ComposeSessionCloseReason close_reason);
 
+  void SetAllowFeedbackForTesting(bool allowed);
+
  private:
   void ProcessError(compose::EvalLocation eval_location,
                     compose::mojom::ComposeStatus status);
@@ -310,6 +313,8 @@
 
   base::Token session_id_;
 
+  bool allow_feedback_for_testing_ = false;
+
   base::WeakPtrFactory<ComposeSession> weak_ptr_factory_;
 };
 
diff --git a/chrome/browser/enterprise/data_controls/rules_service.cc b/chrome/browser/enterprise/data_controls/rules_service.cc
index 62901c9..7c309847 100644
--- a/chrome/browser/enterprise/data_controls/rules_service.cc
+++ b/chrome/browser/enterprise/data_controls/rules_service.cc
@@ -53,6 +53,13 @@
   return rules_manager_.GetVerdict(Rule::Restriction::kClipboard, context);
 }
 
+Verdict RulesService::GetCopyRestrictedBySourceVerdict(
+    const GURL& source) const {
+  return rules_manager_.GetVerdict(
+      Rule::Restriction::kClipboard,
+      {.source = {.url = source, .incognito = profile_->IsIncognitoProfile()}});
+}
+
 Verdict RulesService::GetCopyToOSClipboardVerdict(const GURL& source) const {
   return rules_manager_.GetVerdict(
       Rule::Restriction::kClipboard,
diff --git a/chrome/browser/enterprise/data_controls/rules_service.h b/chrome/browser/enterprise/data_controls/rules_service.h
index 3b89196..6ff8910 100644
--- a/chrome/browser/enterprise/data_controls/rules_service.h
+++ b/chrome/browser/enterprise/data_controls/rules_service.h
@@ -29,6 +29,18 @@
   Verdict GetPasteVerdict(const content::ClipboardEndpoint& source,
                           const content::ClipboardEndpoint& destination,
                           const content::ClipboardMetadata& metadata) const;
+
+  // Returns a clipboard verdict only based the source of the copy, without
+  // making any special destination assumptions. This is meant to trigger rules
+  // that only have "sources" conditions, and blocking/warning verdicts returned
+  // by this function should trigger a dialog.
+  Verdict GetCopyRestrictedBySourceVerdict(const GURL& source) const;
+
+  // Returns a clipboard verdict with the provided source attributes, and with
+  // the "os_clipboard" destination. This is meant to trigger rules that make
+  // use of the "os_clipboard" destination attribute. Blocking verdicts returned
+  // by this function should replace the data put in the clipboard, and warning
+  // verdicts should trigger a dialog.
   Verdict GetCopyToOSClipboardVerdict(const GURL& source) const;
 
  protected:
diff --git a/chrome/browser/enterprise/data_controls/rules_service_unittest.cc b/chrome/browser/enterprise/data_controls/rules_service_unittest.cc
index 7a6b81a90..46b80c70 100644
--- a/chrome/browser/enterprise/data_controls/rules_service_unittest.cc
+++ b/chrome/browser/enterprise/data_controls/rules_service_unittest.cc
@@ -131,6 +131,10 @@
                       ->GetForBrowserContext(profile())
                       ->GetCopyToOSClipboardVerdict(
                           /*source*/ google_url()));
+  ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                      ->GetForBrowserContext(profile())
+                      ->GetCopyRestrictedBySourceVerdict(
+                          /*source*/ google_url()));
 }
 
 TEST_F(DataControlsRulesServiceTest, NoRuleSet) {
@@ -147,6 +151,10 @@
                       ->GetForBrowserContext(profile())
                       ->GetCopyToOSClipboardVerdict(
                           /*source*/ google_url()));
+  ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                      ->GetForBrowserContext(profile())
+                      ->GetCopyRestrictedBySourceVerdict(
+                          /*source*/ google_url()));
 }
 
 TEST_F(DataControlsRulesServiceTest, SourceURL) {
@@ -179,6 +187,10 @@
                            ->GetForBrowserContext(profile())
                            ->GetCopyToOSClipboardVerdict(
                                /*source*/ google_url()));
+    ExpectBlockVerdict(RulesServiceFactory::GetInstance()
+                           ->GetForBrowserContext(profile())
+                           ->GetCopyRestrictedBySourceVerdict(
+                               /*source*/ google_url()));
   }
 
   {
@@ -210,6 +222,10 @@
                           ->GetForBrowserContext(profile())
                           ->GetCopyToOSClipboardVerdict(
                               /*source*/ google_url()));
+    ExpectWarnVerdict(RulesServiceFactory::GetInstance()
+                          ->GetForBrowserContext(profile())
+                          ->GetCopyRestrictedBySourceVerdict(
+                              /*source*/ google_url()));
   }
 
   {
@@ -253,6 +269,10 @@
                            ->GetForBrowserContext(profile())
                            ->GetCopyToOSClipboardVerdict(
                                /*source*/ google_url()));
+    ExpectAllowVerdict(RulesServiceFactory::GetInstance()
+                           ->GetForBrowserContext(profile())
+                           ->GetCopyRestrictedBySourceVerdict(
+                               /*source*/ google_url()));
   }
 }
 
@@ -283,6 +303,10 @@
                         ->GetForBrowserContext(profile())
                         ->GetCopyToOSClipboardVerdict(
                             /*source*/ google_url()));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetCopyRestrictedBySourceVerdict(
+                            /*source*/ google_url()));
   }
 
   {
@@ -311,6 +335,10 @@
                         ->GetForBrowserContext(profile())
                         ->GetCopyToOSClipboardVerdict(
                             /*source*/ google_url()));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetCopyRestrictedBySourceVerdict(
+                            /*source*/ google_url()));
   }
 
   {
@@ -349,6 +377,10 @@
                         ->GetForBrowserContext(profile())
                         ->GetCopyToOSClipboardVerdict(
                             /*source*/ google_url()));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetCopyRestrictedBySourceVerdict(
+                            /*source*/ google_url()));
   }
 }
 
@@ -382,6 +414,14 @@
                         ->GetForBrowserContext(profile())
                         ->GetCopyToOSClipboardVerdict(
                             /*source*/ google_url()));
+    ExpectBlockVerdict(RulesServiceFactory::GetInstance()
+                           ->GetForBrowserContext(incognito_profile())
+                           ->GetCopyRestrictedBySourceVerdict(
+                               /*source*/ google_url()));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetCopyRestrictedBySourceVerdict(
+                            /*source*/ google_url()));
   }
 
   {
@@ -413,6 +453,14 @@
                         ->GetForBrowserContext(profile())
                         ->GetCopyToOSClipboardVerdict(
                             /*source*/ google_url()));
+    ExpectWarnVerdict(RulesServiceFactory::GetInstance()
+                          ->GetForBrowserContext(incognito_profile())
+                          ->GetCopyRestrictedBySourceVerdict(
+                              /*source*/ google_url()));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetCopyRestrictedBySourceVerdict(
+                            /*source*/ google_url()));
   }
 
   {
@@ -455,6 +503,14 @@
                         ->GetForBrowserContext(profile())
                         ->GetCopyToOSClipboardVerdict(
                             /*source*/ google_url()));
+    ExpectAllowVerdict(RulesServiceFactory::GetInstance()
+                           ->GetForBrowserContext(incognito_profile())
+                           ->GetCopyRestrictedBySourceVerdict(
+                               /*source*/ google_url()));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetCopyRestrictedBySourceVerdict(
+                            /*source*/ google_url()));
   }
 }
 
@@ -484,6 +540,10 @@
                         ->GetForBrowserContext(profile())
                         ->GetCopyToOSClipboardVerdict(
                             /*source*/ google_url()));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetCopyRestrictedBySourceVerdict(
+                            /*source*/ google_url()));
   }
 
   {
@@ -511,6 +571,10 @@
                         ->GetForBrowserContext(profile())
                         ->GetCopyToOSClipboardVerdict(
                             /*source*/ google_url()));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetCopyRestrictedBySourceVerdict(
+                            /*source*/ google_url()));
   }
 
   {
@@ -549,6 +613,222 @@
                         ->GetForBrowserContext(profile())
                         ->GetCopyToOSClipboardVerdict(
                             /*source*/ google_url()));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetCopyRestrictedBySourceVerdict(
+                            /*source*/ google_url()));
+  }
+}
+
+TEST_F(DataControlsRulesServiceTest, OSClipboardDestination) {
+  {
+    SetDataControls(profile()->GetPrefs(), {R"({
+                      "destinations": {
+                        "os_clipboard": true
+                      },
+                      "restrictions": [
+                        {"class": "CLIPBOARD", "level": "BLOCK"}
+                      ]
+                    })"});
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetPasteVerdict(
+                            /*source*/ empty_endpoint(),
+                            /*destination*/ google_url_endpoint(),
+                            /*metadata*/ {}));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetPasteVerdict(
+                            /*source*/ google_url_endpoint(),
+                            /*destination*/ empty_endpoint(),
+                            /*metadata*/ {}));
+    ExpectBlockVerdict(RulesServiceFactory::GetInstance()
+                           ->GetForBrowserContext(profile())
+                           ->GetCopyToOSClipboardVerdict(
+                               /*source*/ google_url()));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetCopyRestrictedBySourceVerdict(
+                            /*source*/ google_url()));
+  }
+
+  {
+    SetDataControls(profile()->GetPrefs(), {R"({
+                      "destinations": {
+                        "os_clipboard": true
+                      },
+                      "restrictions": [
+                        {"class": "CLIPBOARD", "level": "WARN"}
+                      ]
+                    })"});
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetPasteVerdict(
+                            /*source*/ empty_endpoint(),
+                            /*destination*/ google_url_endpoint(),
+                            /*metadata*/ {}));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetPasteVerdict(
+                            /*source*/ google_url_endpoint(),
+                            /*destination*/ empty_endpoint(),
+                            /*metadata*/ {}));
+    ExpectWarnVerdict(RulesServiceFactory::GetInstance()
+                          ->GetForBrowserContext(profile())
+                          ->GetCopyToOSClipboardVerdict(
+                              /*source*/ google_url()));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetCopyRestrictedBySourceVerdict(
+                            /*source*/ google_url()));
+  }
+
+  {
+    // When multiple rules are triggered, "ALLOW" should have precedence over
+    // any other value.
+    SetDataControls(profile()->GetPrefs(), {
+                                               R"({
+                      "destinations": {
+                        "os_clipboard": true
+                      },
+                      "restrictions": [
+                        {"class": "CLIPBOARD", "level": "ALLOW"}
+                      ]
+                    })",
+                                               R"({
+                      "destinations": {
+                        "os_clipboard": true
+                      },
+                      "restrictions": [
+                        {"class": "CLIPBOARD", "level": "WARN"}
+                      ]
+                    })"});
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetPasteVerdict(
+                            /*source*/ empty_endpoint(),
+                            /*destination*/ google_url_endpoint(),
+                            /*metadata*/ {}));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetPasteVerdict(
+                            /*source*/ google_url_endpoint(),
+                            /*destination*/ empty_endpoint(),
+                            /*metadata*/ {}));
+    ExpectAllowVerdict(RulesServiceFactory::GetInstance()
+                           ->GetForBrowserContext(profile())
+                           ->GetCopyToOSClipboardVerdict(
+                               /*source*/ google_url()));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetCopyRestrictedBySourceVerdict(
+                            /*source*/ google_url()));
+  }
+}
+
+TEST_F(DataControlsRulesServiceTest, NonOSClipboardDestination) {
+  {
+    SetDataControls(profile()->GetPrefs(), {R"({
+                      "destinations": {
+                        "os_clipboard": false
+                      },
+                      "restrictions": [
+                        {"class": "CLIPBOARD", "level": "BLOCK"}
+                      ]
+                    })"});
+    ExpectBlockVerdict(RulesServiceFactory::GetInstance()
+                           ->GetForBrowserContext(profile())
+                           ->GetPasteVerdict(
+                               /*source*/ empty_endpoint(),
+                               /*destination*/ google_url_endpoint(),
+                               /*metadata*/ {}));
+    ExpectBlockVerdict(RulesServiceFactory::GetInstance()
+                           ->GetForBrowserContext(profile())
+                           ->GetPasteVerdict(
+                               /*source*/ google_url_endpoint(),
+                               /*destination*/ empty_endpoint(),
+                               /*metadata*/ {}));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetCopyToOSClipboardVerdict(
+                            /*source*/ google_url()));
+    ExpectBlockVerdict(RulesServiceFactory::GetInstance()
+                           ->GetForBrowserContext(profile())
+                           ->GetCopyRestrictedBySourceVerdict(
+                               /*source*/ google_url()));
+  }
+
+  {
+    SetDataControls(profile()->GetPrefs(), {R"({
+                      "destinations": {
+                        "os_clipboard": false
+                      },
+                      "restrictions": [
+                        {"class": "CLIPBOARD", "level": "WARN"}
+                      ]
+                    })"});
+    ExpectWarnVerdict(RulesServiceFactory::GetInstance()
+                          ->GetForBrowserContext(profile())
+                          ->GetPasteVerdict(
+                              /*source*/ empty_endpoint(),
+                              /*destination*/ google_url_endpoint(),
+                              /*metadata*/ {}));
+    ExpectWarnVerdict(RulesServiceFactory::GetInstance()
+                          ->GetForBrowserContext(profile())
+                          ->GetPasteVerdict(
+                              /*source*/ google_url_endpoint(),
+                              /*destination*/ empty_endpoint(),
+                              /*metadata*/ {}));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetCopyToOSClipboardVerdict(
+                            /*source*/ google_url()));
+    ExpectWarnVerdict(RulesServiceFactory::GetInstance()
+                          ->GetForBrowserContext(profile())
+                          ->GetCopyRestrictedBySourceVerdict(
+                              /*source*/ google_url()));
+  }
+
+  {
+    // When multiple rules are triggered, "ALLOW" should have precedence over
+    // any other value.
+    SetDataControls(profile()->GetPrefs(), {
+                                               R"({
+                      "destinations": {
+                        "os_clipboard": false
+                      },
+                      "restrictions": [
+                        {"class": "CLIPBOARD", "level": "ALLOW"}
+                      ]
+                    })",
+                                               R"({
+                      "destinations": {
+                        "os_clipboard": false
+                      },
+                      "restrictions": [
+                        {"class": "CLIPBOARD", "level": "WARN"}
+                      ]
+                    })"});
+    ExpectAllowVerdict(RulesServiceFactory::GetInstance()
+                           ->GetForBrowserContext(profile())
+                           ->GetPasteVerdict(
+                               /*source*/ empty_endpoint(),
+                               /*destination*/ google_url_endpoint(),
+                               /*metadata*/ {}));
+    ExpectAllowVerdict(RulesServiceFactory::GetInstance()
+                           ->GetForBrowserContext(profile())
+                           ->GetPasteVerdict(
+                               /*source*/ google_url_endpoint(),
+                               /*destination*/ empty_endpoint(),
+                               /*metadata*/ {}));
+    ExpectNoVerdict(RulesServiceFactory::GetInstance()
+                        ->GetForBrowserContext(profile())
+                        ->GetCopyToOSClipboardVerdict(
+                            /*source*/ google_url()));
+    ExpectAllowVerdict(RulesServiceFactory::GetInstance()
+                           ->GetForBrowserContext(profile())
+                           ->GetCopyRestrictedBySourceVerdict(
+                               /*source*/ google_url()));
   }
 }
 
diff --git a/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.cc b/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.cc
index b0380a6..535eae9 100644
--- a/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.cc
+++ b/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.cc
@@ -175,6 +175,14 @@
   auto verdict = data_controls::RulesServiceFactory::GetForBrowserContext(
                      destination.browser_context())
                      ->GetPasteVerdict(source, destination, metadata);
+  if (source.browser_context() &&
+      source.browser_context() != destination.browser_context()) {
+    verdict = data_controls::Verdict::Merge(
+        data_controls::RulesServiceFactory::GetForBrowserContext(
+            source.browser_context())
+            ->GetPasteVerdict(source, destination, metadata),
+        std::move(verdict));
+  }
 
   // TODO(b/302340176): Add support for verdicts other than "block".
   if (verdict.level() == data_controls::Rule::Level::kBlock) {
@@ -252,34 +260,40 @@
 }
 
 void IsClipboardCopyAllowedByPolicy(
-    content::BrowserContext* browser_context,
-    const GURL& url,
-    size_t data_size_in_bytes,
+    const content::ClipboardEndpoint& source,
+    const content::ClipboardMetadata& metadata,
+    const std::u16string& data,
     content::ContentBrowserClient::IsClipboardCopyAllowedCallback callback) {
+  DCHECK(source.web_contents());
+  DCHECK(source.browser_context());
+  DCHECK(source.data_transfer_endpoint());
+  DCHECK(source.data_transfer_endpoint()->IsUrlType());
+  const GURL& url = *source.data_transfer_endpoint()->GetURL();
+
   std::u16string replacement_data;
   ClipboardRestrictionService* service =
       ClipboardRestrictionServiceFactory::GetInstance()->GetForBrowserContext(
-          browser_context);
-  if (!service->IsUrlAllowedToCopy(url, data_size_in_bytes,
+          source.browser_context());
+  if (!service->IsUrlAllowedToCopy(url, metadata.size.value_or(0),
                                    &replacement_data)) {
-    std::move(callback).Run(std::move(replacement_data));
+    std::move(callback).Run(data, std::move(replacement_data));
     return;
   }
 
-  auto verdict =
-      data_controls::RulesServiceFactory::GetForBrowserContext(browser_context)
-          ->GetCopyToOSClipboardVerdict(url);
+  auto verdict = data_controls::RulesServiceFactory::GetForBrowserContext(
+                     source.browser_context())
+                     ->GetCopyToOSClipboardVerdict(url);
 
   // TODO(b/302340176): Add support for verdicts other than "block".
   // TODO(b/303640183): Add reporting logic.
   if (verdict.level() == data_controls::Rule::Level::kBlock) {
     replacement_data = l10n_util::GetStringUTF16(
         IDS_ENTERPRISE_DATA_CONTROLS_COPY_PREVENTION_WARNING_MESSAGE);
-    std::move(callback).Run(std::move(replacement_data));
+    std::move(callback).Run(data, std::move(replacement_data));
     return;
   }
 
-  std::move(callback).Run(std::nullopt);
+  std::move(callback).Run(data, std::nullopt);
 }
 
 }  // namespace enterprise_data_protection
diff --git a/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.h b/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.h
index d81b8bf..4f887d0 100644
--- a/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.h
+++ b/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.h
@@ -38,9 +38,9 @@
 // If the copy is not allowed, `callback` is called with a replacement string
 // that should instead be put into the OS clipboard.
 void IsClipboardCopyAllowedByPolicy(
-    content::BrowserContext* browser_context,
-    const GURL& url,
-    size_t data_size_in_bytes,
+    const content::ClipboardEndpoint& source,
+    const content::ClipboardMetadata& metadata,
+    const std::u16string& data,
     content::ContentBrowserClient::IsClipboardCopyAllowedCallback callback);
 
 }  // namespace enterprise_data_protection
diff --git a/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_browsertests.cc b/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_browsertests.cc
index 01624e5..54555a4 100644
--- a/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_browsertests.cc
+++ b/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_browsertests.cc
@@ -2,16 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/functional/bind.h"
-#include "base/functional/callback_forward.h"
-#include "base/run_loop.h"
-#include "chrome/browser/enterprise/data_controls/test_utils.h"
 #include "chrome/browser/enterprise/data_protection/data_protection_clipboard_utils.h"
 
+#include "base/functional/bind.h"
+#include "base/functional/callback_forward.h"
 #include "base/functional/callback_helpers.h"
+#include "base/run_loop.h"
 #include "base/test/bind.h"
 #include "base/test/test_future.h"
+#include "base/threading/thread_restrictions.h"
+#include "chrome/browser/browser_process.h"
 #include "chrome/browser/enterprise/data_controls/data_controls_dialog.h"
+#include "chrome/browser/enterprise/data_controls/test_utils.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/enterprise/data_controls/features.h"
@@ -41,12 +44,12 @@
     ASSERT_TRUE(dialog);
     ASSERT_EQ(dialog, constructed_dialog_);
 
-    // Some platforms crash if the dialog has been cancelled before fully        
-    // launching modally, so to avoid that issue cancelling the dialog is done   
-    // asynchronously.                                                           
-    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(                 
-        FROM_HERE,                                                               
-        base::BindOnce(&data_controls::DataControlsDialog::CancelDialog,         
+    // Some platforms crash if the dialog has been cancelled before fully
+    // launching modally, so to avoid that issue cancelling the dialog is done
+    // asynchronously.
+    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE,
+        base::BindOnce(&data_controls::DataControlsDialog::CancelDialog,
                        base::Unretained(dialog)));
   }
 
@@ -75,10 +78,12 @@
   raw_ptr<data_controls::DataControlsDialog> constructed_dialog_ = nullptr;
 };
 
-IN_PROC_BROWSER_TEST_F(DataControlsClipboardUtilsBrowserTest, PasteAllowed) {
+IN_PROC_BROWSER_TEST_F(DataControlsClipboardUtilsBrowserTest,
+                       PasteAllowed_NoSource) {
   base::test::TestFuture<absl::optional<content::ClipboardPasteData>> future;
   enterprise_data_protection::PasteIfAllowedByPolicy(
       /*source=*/content::ClipboardEndpoint(absl::nullopt),
+      /*destination=*/
       content::ClipboardEndpoint(
           ui::DataTransferEndpoint(GURL("https://google.com")),
           base::BindLambdaForTesting(
@@ -96,7 +101,33 @@
 }
 
 IN_PROC_BROWSER_TEST_F(DataControlsClipboardUtilsBrowserTest,
-                       PasteBlockedByDataControls) {
+                       PasteAllowed_SameSource) {
+  base::test::TestFuture<absl::optional<content::ClipboardPasteData>> future;
+  enterprise_data_protection::PasteIfAllowedByPolicy(
+      /*source=*/content::ClipboardEndpoint(
+          ui::DataTransferEndpoint(GURL("https://google.com")),
+          base::BindLambdaForTesting(
+              [this]() { return contents()->GetBrowserContext(); }),
+          *contents()->GetPrimaryMainFrame()),
+      /*destination=*/
+      content::ClipboardEndpoint(
+          ui::DataTransferEndpoint(GURL("https://google.com")),
+          base::BindLambdaForTesting(
+              [this]() { return contents()->GetBrowserContext(); }),
+          *contents()->GetPrimaryMainFrame()),
+      /*metadata=*/{.size = 1234},
+      content::ClipboardPasteData("text", "image", {}), future.GetCallback());
+
+  auto paste_data = future.Get();
+  EXPECT_TRUE(paste_data);
+  EXPECT_EQ(paste_data->text, "text");
+  EXPECT_EQ(paste_data->image, "image");
+
+  EXPECT_FALSE(constructed_dialog_);
+}
+
+IN_PROC_BROWSER_TEST_F(DataControlsClipboardUtilsBrowserTest,
+                       PasteBlockedByDataControls_DestinationRule) {
   data_controls::SetDataControls(browser()->profile()->GetPrefs(), {R"({
                     "destinations": {
                       "urls": ["google.com"]
@@ -109,6 +140,7 @@
   base::test::TestFuture<absl::optional<content::ClipboardPasteData>> future;
   enterprise_data_protection::PasteIfAllowedByPolicy(
       /*source=*/content::ClipboardEndpoint(absl::nullopt),
+      /*destination=*/
       content::ClipboardEndpoint(
           ui::DataTransferEndpoint(GURL("https://google.com")),
           base::BindLambdaForTesting(
@@ -123,4 +155,55 @@
   WaitForDialogToClose();
 }
 
+// Ash requires extra boilerplate to run this test, and since copy-pasting
+// between profiles on Ash isn't a meaningful test it is simply omitted from
+// running this.
+#if !BUILDFLAG(IS_CHROMEOS_ASH)
+IN_PROC_BROWSER_TEST_F(DataControlsClipboardUtilsBrowserTest,
+                       PasteBlockedByDataControls_SourceRule) {
+  data_controls::SetDataControls(browser()->profile()->GetPrefs(), {R"({
+                    "destinations": {
+                      "urls": ["google.com"]
+                    },
+                    "restrictions": [
+                      {"class": "CLIPBOARD", "level": "BLOCK"}
+                    ]
+                  })"});
+
+  // By making a new profile for this test, we ensure we can prevent pasting to
+  // it by having the rule set in the source profile.
+  std::unique_ptr<Profile> destination_profile;
+  {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    destination_profile = Profile::CreateProfile(
+        g_browser_process->profile_manager()->user_data_dir().Append(
+            FILE_PATH_LITERAL("DC Test Profile")),
+        /*delegate=*/nullptr, Profile::CreateMode::CREATE_MODE_SYNCHRONOUS);
+  }
+
+  base::test::TestFuture<absl::optional<content::ClipboardPasteData>> future;
+  enterprise_data_protection::PasteIfAllowedByPolicy(
+      /*destination=*/content::ClipboardEndpoint(
+          ui::DataTransferEndpoint(GURL("https://foo.com")),
+          base::BindLambdaForTesting(
+              [this]() { return contents()->GetBrowserContext(); }),
+          *contents()->GetPrimaryMainFrame()),
+      /*destination=*/
+      content::ClipboardEndpoint(
+          ui::DataTransferEndpoint(GURL("https://google.com")),
+          base::BindLambdaForTesting(
+              [&destination_profile]() -> content::BrowserContext* {
+                return destination_profile.get();
+              }),
+          *contents()->GetPrimaryMainFrame()),
+      /*metadata=*/{.size = 1234},
+      content::ClipboardPasteData("text", "image", {}), future.GetCallback());
+
+  auto paste_data = future.Get();
+  EXPECT_FALSE(paste_data);
+
+  WaitForDialogToClose();
+}
+#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
+
 }  // namespace data_protection
diff --git a/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_unittest.cc b/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_unittest.cc
index 2efa46a..c43c072 100644
--- a/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_unittest.cc
+++ b/chrome/browser/enterprise/data_protection/data_protection_clipboard_utils_unittest.cc
@@ -10,6 +10,7 @@
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/enterprise/data_controls/features.h"
+#include "content/public/browser/clipboard_types.h"
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_task_environment.h"
@@ -80,6 +81,16 @@
         *contents()->GetPrimaryMainFrame());
   }
 
+  content::ClipboardEndpoint CopyEndpoint(GURL url) {
+    return content::ClipboardEndpoint(ui::DataTransferEndpoint(std::move(url)),
+                                      base::BindLambdaForTesting([this]() {
+                                        return contents()->GetBrowserContext();
+                                      }),
+                                      *contents()->GetPrimaryMainFrame());
+  }
+
+  content::ClipboardMetadata CopyMetadata() { return {.size = 123}; }
+
  protected:
   content::BrowserTaskEnvironment task_environment_;
   base::test::ScopedFeatureList scoped_features_;
@@ -170,10 +181,14 @@
 }
 
 TEST_F(DataProtectionIsClipboardCopyAllowedByPolicyTest, Default) {
-  base::test::TestFuture<std::optional<std::u16string>> future;
-  IsClipboardCopyAllowedByPolicy(browser_context(), GURL("https://source.com"),
-                                 123, future.GetCallback());
-  auto replacement = future.Get();
+  base::test::TestFuture<const std::u16string&, std::optional<std::u16string>>
+      future;
+  IsClipboardCopyAllowedByPolicy(CopyEndpoint(GURL("https://source.com")),
+                                 CopyMetadata(), u"foo", future.GetCallback());
+  auto data = future.Get<std::u16string>();
+  EXPECT_EQ(data, u"foo");
+
+  auto replacement = future.Get<std::optional<std::u16string>>();
   EXPECT_FALSE(replacement);
 }
 
@@ -188,10 +203,14 @@
                     ]
                   })"});
 
-  base::test::TestFuture<std::optional<std::u16string>> future;
-  IsClipboardCopyAllowedByPolicy(browser_context(), GURL("https://source.com"),
-                                 123, future.GetCallback());
-  auto replacement = future.Get();
+  base::test::TestFuture<const std::u16string&, std::optional<std::u16string>>
+      future;
+  IsClipboardCopyAllowedByPolicy(CopyEndpoint(GURL("https://source.com")),
+                                 CopyMetadata(), u"foo", future.GetCallback());
+  auto data = future.Get<std::u16string>();
+  EXPECT_EQ(data, u"foo");
+
+  auto replacement = future.Get<std::optional<std::u16string>>();
   EXPECT_TRUE(replacement);
   EXPECT_EQ(*replacement,
             u"Pasting this content here is blocked by your administrator.");
@@ -208,10 +227,15 @@
                     ]
                   })"});
 
-  base::test::TestFuture<std::optional<std::u16string>> future;
-  IsClipboardCopyAllowedByPolicy(browser_context(), GURL("https://random.com"),
-                                 123, future.GetCallback());
-  auto replacement = future.Get();
+  base::test::TestFuture<const std::u16string&, std::optional<std::u16string>>
+      future;
+  IsClipboardCopyAllowedByPolicy(CopyEndpoint(GURL("https://random.com")),
+                                 CopyMetadata(), u"foo", future.GetCallback());
+
+  auto data = future.Get<std::u16string>();
+  EXPECT_EQ(data, u"foo");
+
+  auto replacement = future.Get<std::optional<std::u16string>>();
   EXPECT_FALSE(replacement);
 }
 
diff --git a/chrome/browser/extensions/activity_log/activity_database.cc b/chrome/browser/extensions/activity_log/activity_database.cc
index 32fb68b..4a487c4 100644
--- a/chrome/browser/extensions/activity_log/activity_database.cc
+++ b/chrome/browser/extensions/activity_log/activity_database.cc
@@ -40,7 +40,6 @@
 ActivityDatabase::ActivityDatabase(ActivityDatabase::Delegate* delegate)
     : delegate_(delegate),
       db_({
-          .exclusive_locking = true,
           .page_size = 4096,
           .cache_size = 32,
           // TODO(pwnall): Add a meta table and remove this option.
diff --git a/chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_api_unittest.cc b/chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_api_unittest.cc
index 1e82025d..36f43e00 100644
--- a/chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_api_unittest.cc
+++ b/chrome/browser/extensions/api/pdf_viewer_private/pdf_viewer_private_api_unittest.cc
@@ -42,7 +42,7 @@
  protected:
   void SetUp() override {
     ChromeRenderViewHostTestHarness::SetUp();
-    pdf::PdfViewerStreamManager::CreateForWebContents(web_contents());
+    pdf::PdfViewerStreamManager::Create(web_contents());
 
     // For testing purposes, `main_rfh()` represents the extension's
     // embedder's frame host, while `extension_host` represents the
@@ -74,7 +74,7 @@
     content::RenderFrameHost* embedder_host =
         content::NavigationSimulator::NavigateAndCommitFromDocument(
             GURL("https://original_url1"), main_rfh());
-    pdf::PdfViewerStreamManager::CreateForWebContents(web_contents());
+    pdf::PdfViewerStreamManager::Create(web_contents());
 
     auto* manager = pdf_viewer_stream_manager();
     manager->AddStreamContainer(
diff --git a/chrome/browser/extensions/api/streams_private/streams_private_api.cc b/chrome/browser/extensions/api/streams_private/streams_private_api.cc
index 8cea5bf..799ea06 100644
--- a/chrome/browser/extensions/api/streams_private/streams_private_api.cc
+++ b/chrome/browser/extensions/api/streams_private/streams_private_api.cc
@@ -90,7 +90,7 @@
 #if BUILDFLAG(ENABLE_PDF)
   if (base::FeatureList::IsEnabled(chrome_pdf::features::kPdfOopif) &&
       extension_id == extension_misc::kPdfExtensionId) {
-    pdf::PdfViewerStreamManager::CreateForWebContents(web_contents);
+    pdf::PdfViewerStreamManager::Create(web_contents);
     pdf::PdfViewerStreamManager::FromWebContents(web_contents)
         ->AddStreamContainer(frame_tree_node_id, internal_id,
                              std::move(stream_container));
diff --git a/chrome/browser/extensions/cache_wasm_extension_browsertest.cc b/chrome/browser/extensions/cache_wasm_extension_browsertest.cc
new file mode 100644
index 0000000..a4a1b84
--- /dev/null
+++ b/chrome/browser/extensions/cache_wasm_extension_browsertest.cc
@@ -0,0 +1,141 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/base_paths.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/threading/platform_thread.h"
+#include "chrome/browser/extensions/chrome_test_extension_loader.h"
+#include "chrome/browser/extensions/extension_browsertest.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "components/metrics/content/subprocess_metrics_provider.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "third_party/blink/public/common/switches.h"
+
+namespace chrome {
+
+class WasmExtensionCachingBrowserTest
+    : public extensions::ExtensionBrowserTest {
+ public:
+  WasmExtensionCachingBrowserTest() = default;
+  ~WasmExtensionCachingBrowserTest() override = default;
+
+  const base::FilePath& GetExtensionDir() {
+    if (!tmp_dir_.IsValid()) {
+      CHECK(tmp_dir_.CreateUniqueTempDir());
+    }
+    return tmp_dir_.GetPath();
+  }
+
+  // Fetch the |histogram|'s |bucket| in every renderer process until reaching,
+  // but not exceeding, |expected_count|.
+  template <typename T>
+  void CheckHistogramCount(base::StringPiece histogram,
+                           T bucket,
+                           int expected_count) {
+    while (true) {
+      content::FetchHistogramsFromChildProcesses();
+      metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
+
+      int count = histogram_tester_.GetBucketCount(histogram, bucket);
+      CHECK_LE(count, expected_count);
+      if (count == expected_count) {
+        return;
+      }
+
+      base::PlatformThread::Sleep(base::Milliseconds(5));
+    }
+  }
+
+ private:
+  // Flag `allow-natives-syntax` is needed to be able to call runtime functions
+  // (runtime functions have the % prefix).
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    command_line->AppendSwitchASCII(blink::switches::kJavaScriptFlags,
+                                    "--allow-natives-syntax");
+    command_line->AppendSwitchASCII("wasm-caching-threshold", "1");
+    ExtensionBrowserTest::SetUpCommandLine(command_line);
+  }
+
+  base::ScopedTempDir tmp_dir_;
+  base::HistogramTester histogram_tester_;
+};
+
+// The enum values need to match "WasmCodeCaching" in
+// tools/metrics/histograms/metadata/v8/enums.xml.
+enum WasmCodeCaching {
+  kMiss = 0,
+  kHit = 1,
+  kInvalidCacheEntry = 2,
+  kNoCacheHandler = 3,
+
+  kMaxValue = kNoCacheHandler
+};
+
+// The `large.wasm` file is very large: 2.5MB. To avoid increasing the git
+// repository, we prefer borrowing it from web_tests.
+base::FilePath LargeWasmPath() {
+  base::FilePath root_path;
+  CHECK(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &root_path));
+  return root_path.Append(FILE_PATH_LITERAL(
+      "third_party/blink/web_tests/http/tests/wasm/resources/large.wasm"));
+}
+
+// Test that we cache streaming compiled/instantiated Wasm modules in
+// extensions. We have to wait until caching of the module happens and the
+// histogram is populated.
+IN_PROC_BROWSER_TEST_F(WasmExtensionCachingBrowserTest, CacheWasmExtensions) {
+  base::ScopedAllowBlockingForTesting allow_blocking;
+  CHECK(base::CopyFile(LargeWasmPath(),
+                       GetExtensionDir().AppendASCII("large.wasm")));
+
+  base::WriteFile(GetExtensionDir().AppendASCII("service_worker_background.js"),
+                  R"(
+      chrome.tabs.onCreated.addListener(() => {
+        // Run all functions until there are no unoptimized functions left.
+        WebAssembly.instantiateStreaming(fetch("large.wasm")).then(result => {
+          let has_unoptimized = true;
+          while (has_unoptimized) {
+            has_unoptimized = false;
+            for (export_name in result.instance.exports) {
+              const func = result.instance.exports[export_name];
+              func(1, 2, 4);
+              has_unoptimized ||= %IsLiftoffFunction(func);
+            }
+          }
+        });
+      });
+    )");
+
+  base::WriteFile(GetExtensionDir().AppendASCII("manifest.json"), R"({
+    "name": "foo",
+    "description": "foo",
+    "version": "0.1",
+    "manifest_version": 2,
+    "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
+    "background": {
+      "service_worker": "service_worker_background.js"
+    }
+  })");
+
+  // After loading the extension, no WebAssembly have been executed.
+  extensions::ChromeTestExtensionLoader loader(browser()->profile());
+  loader.LoadExtension(GetExtensionDir());
+  CheckHistogramCount("V8.WasmCodeCaching", WasmCodeCaching::kMiss, 0);
+  CheckHistogramCount("V8.WasmCodeCaching", WasmCodeCaching::kHit, 0);
+
+  // Compile and execute webassembly code for the first time. This should be a
+  // cache miss.
+  NewTab(browser());
+  CheckHistogramCount("V8.WasmCodeCaching", WasmCodeCaching::kMiss, 1);
+  CheckHistogramCount("V8.WasmCodeCaching", WasmCodeCaching::kHit, 0);
+
+  // Repeat: This should be a cache hit this time.
+  NewTab(browser());
+  CheckHistogramCount("V8.WasmCodeCaching", WasmCodeCaching::kMiss, 1);
+  CheckHistogramCount("V8.WasmCodeCaching", WasmCodeCaching::kHit, 1);
+}
+
+}  // namespace chrome
diff --git a/chrome/browser/hang_monitor/hang_crash_dump_mac.cc b/chrome/browser/hang_monitor/hang_crash_dump_mac.cc
index f1d6103..03f644e 100644
--- a/chrome/browser/hang_monitor/hang_crash_dump_mac.cc
+++ b/chrome/browser/hang_monitor/hang_crash_dump_mac.cc
@@ -12,7 +12,7 @@
 void CrashDumpHungChildProcess(base::ProcessHandle handle) {
   base::PortProvider* provider =
       content::BrowserChildProcessHost::GetPortProvider();
-  mach_port_t task_port = provider->TaskForPid(handle);
+  mach_port_t task_port = provider->TaskForHandle(handle);
   if (task_port != MACH_PORT_NULL) {
     crash_reporter::DumpProcessWithoutCrashing(task_port);
   }
diff --git a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/ShrinkExpandHubLayoutAnimatorProvider.java b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/ShrinkExpandHubLayoutAnimatorProvider.java
index 132e8a5..280f85127 100644
--- a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/ShrinkExpandHubLayoutAnimatorProvider.java
+++ b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/ShrinkExpandHubLayoutAnimatorProvider.java
@@ -38,16 +38,16 @@
      * supply a bitmap to and a runnable to execute once fulfilled. Weak references are necessary in
      * the event this callback somehow gets stuck in native thumbnail capture code and a reference
      * to it is held for an extended duration. If this happens a fallback animator will run and it
-     * is desirable for the view and runnable to be available for garabage collection.
+     * is desirable for the view and runnable to be available for garbage collection.
      */
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    @VisibleForTesting()
     static class ImageViewWeakRefBitmapCallback implements Callback<Bitmap> {
         private final WeakReference<ImageView> mViewRef;
         private final WeakReference<Runnable> mOnFinishedRunnableRef;
 
         ImageViewWeakRefBitmapCallback(ImageView view, Runnable onFinishedRunnable) {
-            mViewRef = new WeakReference<ImageView>(view);
-            mOnFinishedRunnableRef = new WeakReference<Runnable>(onFinishedRunnable);
+            mViewRef = new WeakReference<>(view);
+            mOnFinishedRunnableRef = new WeakReference<>(onFinishedRunnable);
         }
 
         @Override
@@ -113,7 +113,7 @@
                 : "Invalid shrink expand HubLayoutAnimationType: " + animationType;
         mAnimationType = animationType;
         mHubContainerView = hubContainerView;
-        mAnimatorSupplier = new SyncOneshotSupplierImpl<HubLayoutAnimator>();
+        mAnimatorSupplier = new SyncOneshotSupplierImpl<>();
         mAnimationDataSupplier = animationDataSupplier;
         mDurationMs = durationMs;
         mOnAlphaChange = onAlphaChange;
diff --git a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/ShrinkExpandHubLayoutAnimatorProviderUnitTest.java b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/ShrinkExpandHubLayoutAnimatorProviderUnitTest.java
index bdd6726..e3a2cfde 100644
--- a/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/ShrinkExpandHubLayoutAnimatorProviderUnitTest.java
+++ b/chrome/browser/hub/internal/android/java/src/org/chromium/chrome/browser/hub/ShrinkExpandHubLayoutAnimatorProviderUnitTest.java
@@ -84,7 +84,7 @@
     public void setUp() {
         mActivityScenarioRule.getScenario().onActivity(this::onActivityCreated);
         ShadowLooper.runUiThreadTasks();
-        mAnimationDataSupplier = new SyncOneshotSupplierImpl<ShrinkExpandAnimationData>();
+        mAnimationDataSupplier = new SyncOneshotSupplierImpl<>();
     }
 
     private void onActivityCreated(Activity activity) {
@@ -401,7 +401,7 @@
 
     @Test
     @SmallTest
-    public void testImageViewWeakRefBitmapCallbackGargbageCollection() {
+    public void testImageViewWeakRefBitmapCallbackGarbageCollection() {
         ImageView imageView = new ImageView(mActivity);
         WeakReference<ImageView> imageViewWeakRef = new WeakReference<>(imageView);
         Runnable runnable =
diff --git a/chrome/browser/ip_protection/ip_protection_config_provider.cc b/chrome/browser/ip_protection/ip_protection_config_provider.cc
index 3d8dacbf..1ccbee4 100644
--- a/chrome/browser/ip_protection/ip_protection_config_provider.cc
+++ b/chrome/browser/ip_protection/ip_protection_config_provider.cc
@@ -172,10 +172,10 @@
           /*consumer_name=*/"IpProtectionService", identity_manager_, scopes,
           mode, signin::ConsentLevel::kSignin);
   auto* oauth_token_fetcher_ptr = oauth_token_fetcher.get();
-  oauth_token_fetcher_ptr->Start(base::BindOnce(
-      &IpProtectionConfigProvider::OnRequestOAuthTokenCompleted,
-      weak_ptr_factory_.GetWeakPtr(), std::move(oauth_token_fetcher),
-      std::move(callback)));
+  oauth_token_fetcher_ptr->Start(
+      base::BindOnce(&IpProtectionConfigProvider::OnRequestOAuthTokenCompleted,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     std::move(oauth_token_fetcher), std::move(callback)));
 }
 
 void IpProtectionConfigProvider::OnRequestOAuthTokenCompleted(
@@ -270,9 +270,11 @@
   for (const auto& proxy_chain : response->proxy_chain()) {
     std::vector<net::ProxyServer> proxies;
     bool ok = true;
+    bool overridden = false;
     if (const std::string a_override =
             net::features::kIpPrivacyProxyAHostnameOverride.Get();
         a_override != "") {
+      overridden = true;
       ok = ok && add_server(proxies, a_override);
     } else {
       ok = ok && add_server(proxies, proxy_chain.proxy_a());
@@ -280,6 +282,7 @@
     if (const std::string b_override =
             net::features::kIpPrivacyProxyBHostnameOverride.Get();
         ok && b_override != "") {
+      overridden = true;
       ok = ok && add_server(proxies, b_override);
     } else {
       ok = ok && add_server(proxies, proxy_chain.proxy_b());
@@ -287,11 +290,13 @@
 
     // Create a new ProxyChain if the proxies were all valid.
     if (ok) {
-      // If the `chain_id` is out of range, use the proxy chain anyway, but
-      // with the default `chain_id`. This allows adding new IDs on the server
-      // side without breaking older browsers.
+      // If the `chain_id` is out of range or local features overrode the
+      // chain, use the proxy chain anyway, but with the default `chain_id`.
+      // This allows adding new IDs on the server side without breaking older
+      // browsers.
       int chain_id = proxy_chain.chain_id();
-      if (chain_id < 0 || chain_id > net::ProxyChain::kMaxIpProtectionChainId) {
+      if (overridden || chain_id < 0 ||
+          chain_id > net::ProxyChain::kMaxIpProtectionChainId) {
         chain_id = net::ProxyChain::kDefaultIpProtectionChainId;
       }
       proxy_list.push_back(
diff --git a/chrome/browser/metrics/usage_scenario/chrome_responsiveness_calculator_delegate.cc b/chrome/browser/metrics/usage_scenario/chrome_responsiveness_calculator_delegate.cc
index 88d1506..9fb2b42 100644
--- a/chrome/browser/metrics/usage_scenario/chrome_responsiveness_calculator_delegate.cc
+++ b/chrome/browser/metrics/usage_scenario/chrome_responsiveness_calculator_delegate.cc
@@ -41,6 +41,25 @@
   }
 }
 
+const char* GetSuffixForExtensionCount(size_t extension_count) {
+  if (extension_count >= 16) {
+    return "16";
+  }
+  if (extension_count >= 8) {
+    return "8";
+  }
+  if (extension_count >= 4) {
+    return "4";
+  }
+  if (extension_count >= 2) {
+    return "2";
+  }
+  if (extension_count == 1) {
+    return "1";
+  }
+  return "0";
+}
+
 }  // namespace
 
 // static
@@ -63,8 +82,10 @@
     ~ChromeResponsivenessCalculatorDelegate() = default;
 
 void ChromeResponsivenessCalculatorDelegate::OnMeasurementIntervalEnded() {
-  interval_scenario_params_ =
-      GetLongIntervalScenario(usage_scenario_data_store_->ResetIntervalData());
+  auto interval_data = usage_scenario_data_store_->ResetIntervalData();
+  interval_scenario_params_ = GetLongIntervalScenario(interval_data);
+  extensions_with_content_scripts_in_interval_ =
+      interval_data.num_extensions_with_content_scripts;
 }
 
 void ChromeResponsivenessCalculatorDelegate::OnResponsivenessEmitted(
@@ -83,6 +104,14 @@
                                    num_congested_slices, min, exclusive_max,
                                    buckets);
   }
+  if (extensions_with_content_scripts_in_interval_.has_value()) {
+    base::UmaHistogramCustomCounts(
+        base::StrCat(
+            {"Browser.MainThreadsCongestion.ExtensionContentScripts.",
+             GetSuffixForExtensionCount(
+                 extensions_with_content_scripts_in_interval_.value())}),
+        num_congested_slices, min, exclusive_max, buckets);
+  }
 }
 
 ChromeResponsivenessCalculatorDelegate::ChromeResponsivenessCalculatorDelegate(
diff --git a/chrome/browser/metrics/usage_scenario/chrome_responsiveness_calculator_delegate.h b/chrome/browser/metrics/usage_scenario/chrome_responsiveness_calculator_delegate.h
index b950748..937c38cf 100644
--- a/chrome/browser/metrics/usage_scenario/chrome_responsiveness_calculator_delegate.h
+++ b/chrome/browser/metrics/usage_scenario/chrome_responsiveness_calculator_delegate.h
@@ -45,6 +45,7 @@
   std::unique_ptr<UsageScenarioTracker> usage_scenario_tracker_;
   raw_ptr<UsageScenarioDataStore> usage_scenario_data_store_;
   std::optional<ScenarioParams> interval_scenario_params_;
+  std::optional<size_t> extensions_with_content_scripts_in_interval_;
 };
 
 #endif  // CHROME_BROWSER_METRICS_USAGE_SCENARIO_CHROME_RESPONSIVENESS_CALCULATOR_DELEGATE_H_
diff --git a/chrome/browser/metrics/usage_scenario/tab_usage_scenario_tracker.cc b/chrome/browser/metrics/usage_scenario/tab_usage_scenario_tracker.cc
index d40c4bc..5ada6dd 100644
--- a/chrome/browser/metrics/usage_scenario/tab_usage_scenario_tracker.cc
+++ b/chrome/browser/metrics/usage_scenario/tab_usage_scenario_tracker.cc
@@ -6,8 +6,12 @@
 
 #include "base/containers/contains.h"
 #include "chrome/browser/metrics/usage_scenario/usage_scenario_data_store.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
 #include "content/public/browser/visibility.h"
 #include "content/public/browser/web_contents.h"
+#include "extensions/browser/script_injection_tracker.h"
+#include "extensions/common/extension_id.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "ui/display/screen.h"
 #include "url/origin.h"
@@ -32,6 +36,55 @@
   return screen->GetNumDisplays();
 }
 
+extensions::ExtensionIdSet GetExtensionsThatRanContentScriptsInWebContents(
+    content::WebContents* contents) {
+  content::RenderFrameHost* main_frame = contents->GetPrimaryMainFrame();
+  if (!main_frame) {
+    // WebContents is being destroyed.
+    return {};
+  }
+  // Find the complete set of processes in the WebContents' frame tree first so
+  // that each is only handled once.
+  std::set<content::RenderProcessHost*> processes;
+  processes.insert(main_frame->GetProcess());
+  main_frame->ForEachRenderFrameHost(
+      [&processes](content::RenderFrameHost* frame) {
+        processes.insert(frame->GetProcess());
+      });
+
+  // Find the set of extensions that ran scripts in these processes.
+  //
+  // The result may include extra extensions for the following reasons:
+  //
+  // * ScriptInjectionTracker returns all extensions that have injected scripts
+  //   into a process. Multiple pages on the same domain might share a process
+  //   so we can't be sure which pages the extension affected.
+  // * It also returns extensions that have EVER injected scripts into the
+  //   process, even if the extension is no longer active (eg. after navigating
+  //   to a new page or removing the extension).
+  // * There are renderer-side permission checks that might cause a page to
+  //   refuse to inject a script, which ScriptInjectionTracker isn't aware of so
+  //   assumes the script was injected.
+  //
+  // In the first two points, the extension COULD still be affecting pages
+  // loaded in the process (due to bugs or malicious code, or just because
+  // changes the extension made to the page content had lingering effects).
+  //
+  // It may also miss extensions for the following reasons:
+  //
+  // * ScriptInjectionTracker only includes extensions that inject Javascript.
+  //   It doesn't track injected CSS scripts, declarative web requests, or
+  //   anything else an extension might do that could affect the page content.
+  // * The set of processes for the page includes only those that hosted frames,
+  //   not workers.
+  extensions::ExtensionIdSet extensions;
+  for (const auto* process : processes) {
+    extensions.merge(extensions::ScriptInjectionTracker::
+                         GetExtensionsThatRanContentScriptsInProcess(*process));
+  }
+  return extensions;
+}
+
 }  // namespace
 
 TabUsageScenarioTracker::TabUsageScenarioTracker(
@@ -204,8 +257,9 @@
 
     iter->second = GetNavigationInfoForContents(web_contents);
     if (iter->second.first != ukm::kInvalidSourceId) {
-      usage_scenario_data_store_->OnUkmSourceBecameVisible(iter->second.first,
-                                                           iter->second.second);
+      usage_scenario_data_store_->OnUkmSourceBecameVisible(
+          iter->second.first, iter->second.second,
+          GetExtensionsThatRanContentScriptsInWebContents(web_contents));
     }
   }
 }
@@ -298,7 +352,8 @@
                                     GetNavigationInfoForContents(web_contents));
   if (iter.first->second.first != ukm::kInvalidSourceId) {
     usage_scenario_data_store_->OnUkmSourceBecameVisible(
-        iter.first->second.first, iter.first->second.second);
+        iter.first->second.first, iter.first->second.second,
+        GetExtensionsThatRanContentScriptsInWebContents(web_contents));
   }
 }
 
diff --git a/chrome/browser/metrics/usage_scenario/usage_scenario_data_store.cc b/chrome/browser/metrics/usage_scenario/usage_scenario_data_store.cc
index ee19c4e..4067c2bb 100644
--- a/chrome/browser/metrics/usage_scenario/usage_scenario_data_store.cc
+++ b/chrome/browser/metrics/usage_scenario/usage_scenario_data_store.cc
@@ -233,13 +233,16 @@
 
 void UsageScenarioDataStoreImpl::OnUkmSourceBecameVisible(
     const ukm::SourceId& source,
-    const url::Origin& origin) {
+    const url::Origin& origin,
+    extensions::ExtensionIdSet extensions_with_content_scripts) {
   DCHECK_NE(ukm::kInvalidSourceId, source);
   auto& origin_map_iter = origin_info_map_[origin];
   auto& source_id_iter = origin_map_iter[source];
 
   DCHECK(source_id_iter.visible_timestamp.is_null());
   source_id_iter.visible_timestamp = tick_clock_->NowTicks();
+
+  extensions_with_content_scripts_.merge(extensions_with_content_scripts);
 }
 
 void UsageScenarioDataStoreImpl::OnUkmSourceBecameHidden(
@@ -299,6 +302,10 @@
   interval_data_.time_since_last_user_interaction_with_browser =
       now - last_interaction_with_browser_timestamp_;
 
+  interval_data_.num_extensions_with_content_scripts =
+      extensions_with_content_scripts_.size();
+  extensions_with_content_scripts_.clear();
+
   base::TimeDelta origin_visible_for_longest_time_duration;
   // Finalize the interval data and find the origin that has been visible for
   // the longest period of time.
diff --git a/chrome/browser/metrics/usage_scenario/usage_scenario_data_store.h b/chrome/browser/metrics/usage_scenario/usage_scenario_data_store.h
index 3739d32..7bc0efc 100644
--- a/chrome/browser/metrics/usage_scenario/usage_scenario_data_store.h
+++ b/chrome/browser/metrics/usage_scenario/usage_scenario_data_store.h
@@ -12,6 +12,7 @@
 #include "base/time/tick_clock.h"
 #include "base/time/time.h"
 #include "base/types/pass_key.h"
+#include "extensions/common/extension_id.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "url/origin.h"
 
@@ -95,6 +96,9 @@
 
     // The number of times the system has been put to sleep during the interval.
     uint8_t sleep_events = 0;
+
+    // The number of extensions that ran content scripts during the interval.
+    size_t num_extensions_with_content_scripts = 0;
   };
 
   // Reset the interval data with the current state information and returns the
@@ -150,8 +154,10 @@
   // tab playing video becomes non visible.
   void OnVideoStopsInVisibleTab();
 
-  void OnUkmSourceBecameVisible(const ukm::SourceId& source,
-                                const url::Origin& origin);
+  void OnUkmSourceBecameVisible(
+      const ukm::SourceId& source,
+      const url::Origin& origin,
+      extensions::ExtensionIdSet extensions_with_content_scripts);
   void OnUkmSourceBecameHidden(const ukm::SourceId& source,
                                const url::Origin& origin);
 
@@ -246,6 +252,9 @@
   // Information about the origins that have been visible during the interval.
   OriginInfoMap origin_info_map_;
 
+  // Extensions that ran content scripts on visible origins during the interval.
+  extensions::ExtensionIdSet extensions_with_content_scripts_;
+
   IntervalData interval_data_;
 
   SEQUENCE_CHECKER(sequence_checker_);
diff --git a/chrome/browser/metrics/usage_scenario/usage_scenario_data_store_unittest.cc b/chrome/browser/metrics/usage_scenario/usage_scenario_data_store_unittest.cc
index a0ee431..562f48b 100644
--- a/chrome/browser/metrics/usage_scenario/usage_scenario_data_store_unittest.cc
+++ b/chrome/browser/metrics/usage_scenario/usage_scenario_data_store_unittest.cc
@@ -306,7 +306,7 @@
 
   // Interval with one SourceID visible the entire time.
   const ukm::SourceId kSource1 = 42;
-  data_store()->OnUkmSourceBecameVisible(kSource1, kOrigin);
+  data_store()->OnUkmSourceBecameVisible(kSource1, kOrigin, {});
   task_environment_.FastForwardBy(kShortDelay);
   data = ResetIntervalData();
   EXPECT_EQ(kSource1, data.source_id_for_longest_visible_origin);
@@ -318,7 +318,7 @@
   const ukm::SourceId kSource2 = 43;
   task_environment_.FastForwardBy(2 * kShortDelay);
   data_store()->OnUkmSourceBecameHidden(kSource1, kOrigin);
-  data_store()->OnUkmSourceBecameVisible(kSource2, kOrigin);
+  data_store()->OnUkmSourceBecameVisible(kSource2, kOrigin, {});
   task_environment_.FastForwardBy(kShortDelay);
   data = ResetIntervalData();
   EXPECT_EQ(kSource1, data.source_id_for_longest_visible_origin);
@@ -328,10 +328,10 @@
 
   // Interval with 3 different visible SourceID, |kSource1| and |kSource2| are
   // visible for the same amount of time.
-  data_store()->OnUkmSourceBecameVisible(kSource1, kOrigin);
+  data_store()->OnUkmSourceBecameVisible(kSource1, kOrigin, {});
   const ukm::SourceId kSource3 = 44;
   task_environment_.FastForwardBy(kShortDelay);
-  data_store()->OnUkmSourceBecameVisible(kSource3, kOrigin);
+  data_store()->OnUkmSourceBecameVisible(kSource3, kOrigin, {});
   task_environment_.FastForwardBy(kShortDelay);
   data = ResetIntervalData();
   EXPECT_TRUE(data.source_id_for_longest_visible_origin == kSource1 ||
@@ -363,15 +363,15 @@
   const url::Origin kOrigin = url::Origin::Create(GURL("https://foo.com"));
 
   const ukm::SourceId kSource1 = 42;
-  data_store()->OnUkmSourceBecameVisible(kSource1, kOrigin);
+  data_store()->OnUkmSourceBecameVisible(kSource1, kOrigin, {});
   task_environment_.FastForwardBy(kShortDelay);
   data_store()->OnUkmSourceBecameHidden(kSource1, kOrigin);
   task_environment_.FastForwardBy(kShortDelay);
-  data_store()->OnUkmSourceBecameVisible(kSource1, kOrigin);
+  data_store()->OnUkmSourceBecameVisible(kSource1, kOrigin, {});
   task_environment_.FastForwardBy(kShortDelay);
   data_store()->OnUkmSourceBecameHidden(kSource1, kOrigin);
   task_environment_.FastForwardBy(kShortDelay);
-  data_store()->OnUkmSourceBecameVisible(kSource1, kOrigin);
+  data_store()->OnUkmSourceBecameVisible(kSource1, kOrigin, {});
   auto data = ResetIntervalData();
   EXPECT_EQ(kSource1, data.source_id_for_longest_visible_origin);
   EXPECT_EQ(2 * kShortDelay,
@@ -398,9 +398,9 @@
   // be one associated with |kOrigin1| as the cumulative visibility time for its
   // sourceIDs is the greatest.
 
-  data_store()->OnUkmSourceBecameVisible(kOrigin1SourceId1, kOrigin1);
-  data_store()->OnUkmSourceBecameVisible(kOrigin1SourceId2, kOrigin1);
-  data_store()->OnUkmSourceBecameVisible(kOrigin2SourceId, kOrigin2);
+  data_store()->OnUkmSourceBecameVisible(kOrigin1SourceId1, kOrigin1, {});
+  data_store()->OnUkmSourceBecameVisible(kOrigin1SourceId2, kOrigin1, {});
+  data_store()->OnUkmSourceBecameVisible(kOrigin2SourceId, kOrigin2, {});
   task_environment_.FastForwardBy(kShortDelay);
   auto data = ResetIntervalData();
   EXPECT_TRUE(data.source_id_for_longest_visible_origin == kOrigin1SourceId1 ||
@@ -412,7 +412,7 @@
   // same time, which is greater than the cumulative visibility time for the
   // sourceIDs associated with |kOrigin1|.
   data_store()->OnUkmSourceBecameHidden(kOrigin1SourceId1, kOrigin1);
-  data_store()->OnUkmSourceBecameVisible(kOrigin3SourceId, kOrigin3);
+  data_store()->OnUkmSourceBecameVisible(kOrigin3SourceId, kOrigin3, {});
   task_environment_.FastForwardBy(kShortDelay);
   data_store()->OnUkmSourceBecameHidden(kOrigin1SourceId2, kOrigin1);
   task_environment_.FastForwardBy(kShortDelay);
@@ -427,10 +427,10 @@
   // cumulative time for the source ID associated with |kOrigin1| is also equal
   // to 5 time units.
   data_store()->OnUkmSourceBecameHidden(kOrigin3SourceId, kOrigin3);
-  data_store()->OnUkmSourceBecameVisible(kOrigin1SourceId1, kOrigin1);
+  data_store()->OnUkmSourceBecameVisible(kOrigin1SourceId1, kOrigin1, {});
   task_environment_.FastForwardBy(3 * kShortDelay);
   data_store()->OnUkmSourceBecameHidden(kOrigin1SourceId1, kOrigin1);
-  data_store()->OnUkmSourceBecameVisible(kOrigin1SourceId2, kOrigin1);
+  data_store()->OnUkmSourceBecameVisible(kOrigin1SourceId2, kOrigin1, {});
   task_environment_.FastForwardBy(2 * kShortDelay);
   data = ResetIntervalData();
   EXPECT_TRUE(data.source_id_for_longest_visible_origin == kOrigin2SourceId);
@@ -452,13 +452,13 @@
   const ukm::SourceId kOrigin2SourceId = 44;
   const ukm::SourceId kOrigin3SourceId = 45;
 
-  data_store()->OnUkmSourceBecameVisible(kOrigin1SourceId, kOrigin1);
+  data_store()->OnUkmSourceBecameVisible(kOrigin1SourceId, kOrigin1, {});
   task_environment_.FastForwardBy(kShortDelay);
-  data_store()->OnUkmSourceBecameVisible(kOrigin2SourceId, kOrigin2);
+  data_store()->OnUkmSourceBecameVisible(kOrigin2SourceId, kOrigin2, {});
   task_environment_.FastForwardBy(kShortDelay);
   data_store()->OnUkmSourceBecameHidden(kOrigin1SourceId, kOrigin1);
   data_store()->OnUkmSourceBecameHidden(kOrigin2SourceId, kOrigin2);
-  data_store()->OnUkmSourceBecameVisible(kOrigin3SourceId, kOrigin3);
+  data_store()->OnUkmSourceBecameVisible(kOrigin3SourceId, kOrigin3, {});
   task_environment_.FastForwardBy(kShortDelay);
   auto data = ResetIntervalData();
   EXPECT_TRUE(data.source_id_for_longest_visible_origin == kOrigin1SourceId);
diff --git a/chrome/browser/optimization_guide/model_execution/model_execution_browsertest.cc b/chrome/browser/optimization_guide/model_execution/model_execution_browsertest.cc
index bc78fb7..f62a1df 100644
--- a/chrome/browser/optimization_guide/model_execution/model_execution_browsertest.cc
+++ b/chrome/browser/optimization_guide/model_execution/model_execution_browsertest.cc
@@ -569,12 +569,8 @@
   browser()->profile()->GetPrefs()->SetInteger(
       prefs::kModelExecutionMainToggleSettingState,
       static_cast<int>(optimization_guide::prefs::FeatureOptInState::kEnabled));
-  auto* prefs = browser()->profile()->GetPrefs();
-  EXPECT_EQ(
-      static_cast<int>(optimization_guide::prefs::FeatureOptInState::kEnabled),
-      prefs->GetInteger(prefs::GetSettingEnabledPrefName(
-          optimization_guide::proto::ModelExecutionFeature::
-              MODEL_EXECUTION_FEATURE_TAB_ORGANIZATION)));
+  EXPECT_TRUE(ShouldFeatureBeCurrentlyEnabledForUser(
+      proto::ModelExecutionFeature::MODEL_EXECUTION_FEATURE_TAB_ORGANIZATION));
   EXPECT_FALSE(ShouldFeatureBeCurrentlyEnabledForUser(
       proto::ModelExecutionFeature::MODEL_EXECUTION_FEATURE_COMPOSE));
 }
@@ -591,13 +587,8 @@
       proto::ModelExecutionFeature::MODEL_EXECUTION_FEATURE_COMPOSE));
   EXPECT_TRUE(ShouldFeatureBeCurrentlyEnabledForUser(
       proto::ModelExecutionFeature::MODEL_EXECUTION_FEATURE_TAB_ORGANIZATION));
-
-  auto* prefs = browser()->profile()->GetPrefs();
-  EXPECT_EQ(
-      static_cast<int>(optimization_guide::prefs::FeatureOptInState::kEnabled),
-      prefs->GetInteger(prefs::GetSettingEnabledPrefName(
-          optimization_guide::proto::ModelExecutionFeature::
-              MODEL_EXECUTION_FEATURE_TAB_ORGANIZATION)));
+  EXPECT_TRUE(ShouldFeatureBeCurrentlyEnabledForUser(
+      proto::ModelExecutionFeature::MODEL_EXECUTION_FEATURE_COMPOSE));
 }
 
 #if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_FUCHSIA)
diff --git a/chrome/browser/password_check/android/password_check_manager.cc b/chrome/browser/password_check/android/password_check_manager.cc
index 7c2e56f..6721dc77 100644
--- a/chrome/browser/password_check/android/password_check_manager.cc
+++ b/chrome/browser/password_check/android/password_check_manager.cc
@@ -27,7 +27,7 @@
 using password_manager::PasswordForm;
 using PasswordCheckUIStatus = password_manager::PasswordCheckUIStatus;
 using State = password_manager::BulkLeakCheckService::State;
-using SyncState = password_manager::SyncState;
+using SyncState = password_manager::sync_util::SyncState;
 using CredentialUIEntry = password_manager::CredentialUIEntry;
 using CredentialFacet = password_manager::CredentialFacet;
 using CompromisedCredentialForUI =
@@ -316,7 +316,7 @@
   SyncState sync_state = password_manager::sync_util::GetPasswordSyncState(
       SyncServiceFactory::GetForProfile(profile_));
   switch (sync_state) {
-    case SyncState::kNotSyncing:
+    case SyncState::kNotActive:
       ABSL_FALLTHROUGH_INTENDED;
     case SyncState::kSyncingWithCustomPassphrase:
       ABSL_FALLTHROUGH_INTENDED;
diff --git a/chrome/browser/password_edit_dialog/android/password_edit_dialog_bridge.cc b/chrome/browser/password_edit_dialog/android/password_edit_dialog_bridge.cc
index 50325db1..e0f6623 100644
--- a/chrome/browser/password_edit_dialog/android/password_edit_dialog_bridge.cc
+++ b/chrome/browser/password_edit_dialog/android/password_edit_dialog_bridge.cc
@@ -47,7 +47,7 @@
     const std::vector<std::u16string>& saved_usernames,
     const std::u16string& username,
     const std::u16string& password,
-    const std::string& account_email) {
+    const std::optional<std::string>& account_email) {
   JNIEnv* env = base::android::AttachCurrentThread();
 
   base::android::ScopedJavaLocalRef<jobjectArray> j_saved_usernames =
@@ -57,7 +57,9 @@
   base::android::ScopedJavaLocalRef<jstring> j_password =
       base::android::ConvertUTF16ToJavaString(env, password);
   base::android::ScopedJavaLocalRef<jstring> j_account_email =
-      base::android::ConvertUTF8ToJavaString(env, account_email);
+      account_email.has_value()
+          ? base::android::ConvertUTF8ToJavaString(env, account_email.value())
+          : nullptr;
 
   Java_PasswordEditDialogBridge_showPasswordEditDialog(
       env, java_password_dialog_, j_saved_usernames, j_username, j_password,
diff --git a/chrome/browser/password_edit_dialog/android/password_edit_dialog_bridge.h b/chrome/browser/password_edit_dialog/android/password_edit_dialog_bridge.h
index 5157f04..ea6ce0cb 100644
--- a/chrome/browser/password_edit_dialog/android/password_edit_dialog_bridge.h
+++ b/chrome/browser/password_edit_dialog/android/password_edit_dialog_bridge.h
@@ -61,7 +61,7 @@
       const std::vector<std::u16string>& usernames,
       const std::u16string& username,
       const std::u16string& password,
-      const std::string& account_email) = 0;
+      const std::optional<std::string>& account_email) = 0;
 
   // Dismisses displayed dialog. The owner of PassworDeidtDialogBridge should
   // call this function to correctly dismiss and destroy the dialog. The object
@@ -86,10 +86,11 @@
   PasswordEditDialogBridge& operator=(const PasswordEditDialogBridge&) = delete;
 
   // Calls Java side of the bridge to display password edit modal dialog.
-  void ShowPasswordEditDialog(const std::vector<std::u16string>& usernames,
-                              const std::u16string& username,
-                              const std::u16string& password,
-                              const std::string& account_email) override;
+  void ShowPasswordEditDialog(
+      const std::vector<std::u16string>& usernames,
+      const std::u16string& username,
+      const std::u16string& password,
+      const std::optional<std::string>& account_email) override;
 
   // Dismisses displayed dialog. The owner of PassworDeidtDialogBridge should
   // call this function to correctly dismiss and destroy the dialog. The object
diff --git a/chrome/browser/password_manager/android/password_infobar_utils.cc b/chrome/browser/password_manager/android/password_infobar_utils.cc
index bba8e5c..82ce9eb89 100644
--- a/chrome/browser/password_manager/android/password_infobar_utils.cc
+++ b/chrome/browser/password_manager/android/password_infobar_utils.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/password_manager/android/password_infobar_utils.h"
+#include <optional>
 
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
@@ -14,14 +15,14 @@
 
 namespace password_manager {
 
-AccountInfo GetAccountInfoForPasswordMessages(Profile* profile) {
+std::optional<AccountInfo> GetAccountInfoForPasswordMessages(Profile* profile) {
   DCHECK(profile);
 
   // TODO(crbug.com/1466445): Migrate away from `ConsentLevel::kSync` on
   // Android.
   if (!sync_util::IsSyncFeatureEnabledIncludingPasswords(
           SyncServiceFactory::GetForProfile(profile))) {
-    return AccountInfo();
+    return std::nullopt;
   }
   signin::IdentityManager* identity_manager =
       IdentityManagerFactory::GetForProfile(profile);
diff --git a/chrome/browser/password_manager/android/password_infobar_utils.h b/chrome/browser/password_manager/android/password_infobar_utils.h
index 6b723444..9bda265 100644
--- a/chrome/browser/password_manager/android/password_infobar_utils.h
+++ b/chrome/browser/password_manager/android/password_infobar_utils.h
@@ -15,7 +15,7 @@
 
 namespace password_manager {
 
-AccountInfo GetAccountInfoForPasswordMessages(Profile* profile);
+std::optional<AccountInfo> GetAccountInfoForPasswordMessages(Profile* profile);
 
 std::string GetDisplayableAccountName(content::WebContents* web_contents);
 
diff --git a/chrome/browser/password_manager/android/save_update_password_message_delegate.cc b/chrome/browser/password_manager/android/save_update_password_message_delegate.cc
index 072bb9d..6d1ed70 100644
--- a/chrome/browser/password_manager/android/save_update_password_message_delegate.cc
+++ b/chrome/browser/password_manager/android/save_update_password_message_delegate.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/password_manager/android/save_update_password_message_delegate.h"
+#include <optional>
 #include <utility>
 
 #include "base/android/build_info.h"
@@ -16,6 +17,7 @@
 #include "chrome/browser/android/resource_mapper.h"
 #include "chrome/browser/flags/android/chrome_feature_list.h"
 #include "chrome/browser/password_manager/android/password_infobar_utils.h"
+#include "chrome/browser/password_manager/android/password_manager_android_util.h"
 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/passwords/ui_utils.h"
@@ -27,6 +29,7 @@
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
 #include "components/password_manager/core/browser/password_ui_utils.h"
 #include "components/password_manager/core/common/password_manager_features.h"
+#include "components/prefs/pref_service.h"
 #include "components/url_formatter/elide_url.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_delegate.h"
@@ -36,6 +39,8 @@
 
 namespace {
 
+using password_manager_android_util::UsesSplitStoresAndUPMForLocal;
+
 // Duration of message before timeout; 20 seconds.
 const int kMessageDismissDurationMs = 20000;
 
@@ -188,13 +193,9 @@
     passwords_state_.OnPendingPassword(std::move(form_to_save));
   }
 
-  if (account_info.has_value()) {
-    account_email_ = account_info->CanHaveEmailAddressDisplayed()
-                         ? account_info.value().email
-                         : account_info.value().full_name;
-  } else {
-    account_email_ = std::string();
-  }
+  account_email_ = GetAccountForMessageDescription(
+      account_info, passwords_state_.form_manager()->GetPendingCredentials(),
+      update_password);
 
   CreateMessage(update_password);
   RecordMessageShownMetrics();
@@ -303,12 +304,12 @@
 std::u16string SaveUpdatePasswordMessageDelegate::GetMessageDescription(
     const password_manager::PasswordForm& pending_credentials,
     bool update_password) {
-  if (!account_email_.empty()) {
+  if (account_email_.has_value()) {
     return l10n_util::GetStringFUTF16(
         update_password
             ? IDS_PASSWORD_MANAGER_UPDATE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION
             : IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_IN_MESSAGE_DESCRIPTION,
-        base::UTF8ToUTF16(account_email_));
+        base::UTF8ToUTF16(account_email_.value()));
   }
   return l10n_util::GetStringUTF16(
       update_password
@@ -316,6 +317,27 @@
           : IDS_PASSWORD_MANAGER_SAVE_PASSWORD_SIGNED_OUT_MESSAGE_DESCRIPTION);
 }
 
+std::optional<std::string>
+SaveUpdatePasswordMessageDelegate::GetAccountForMessageDescription(
+    const std::optional<AccountInfo>& account_info,
+    const password_manager::PasswordForm& pending_credentials,
+    bool update_password) {
+  Profile* profile =
+      Profile::FromBrowserContext(web_contents_->GetBrowserContext());
+  if (!account_info.has_value()) {
+    return std::nullopt;
+  }
+  // If password is being updated in the profile storage, don't show the account
+  // even if it's available.
+  if (update_password && UsesSplitStoresAndUPMForLocal(profile->GetPrefs()) &&
+      pending_credentials.IsUsingProfileStore()) {
+    return std::nullopt;
+  }
+  return account_info->CanHaveEmailAddressDisplayed()
+             ? account_info.value().email
+             : account_info.value().full_name;
+}
+
 int SaveUpdatePasswordMessageDelegate::GetPrimaryButtonTextId(
     bool update_password,
     bool use_followup_button_text) {
@@ -388,10 +410,10 @@
 
 void SaveUpdatePasswordMessageDelegate::DisplayEditDialog(
     bool update_password) {
-  const std::u16string& current_username =
-      passwords_state_.form_manager()->GetPendingCredentials().username_value;
-  const std::u16string& current_password =
-      passwords_state_.form_manager()->GetPendingCredentials().password_value;
+  const password_manager::PasswordForm& password_form =
+      passwords_state_.form_manager()->GetPendingCredentials();
+  const std::u16string& current_username = password_form.username_value;
+  const std::u16string& current_password = password_form.password_value;
 
   CreatePasswordEditDialog();
 
diff --git a/chrome/browser/password_manager/android/save_update_password_message_delegate.h b/chrome/browser/password_manager/android/save_update_password_message_delegate.h
index fa3b67a0..bf9240a 100644
--- a/chrome/browser/password_manager/android/save_update_password_message_delegate.h
+++ b/chrome/browser/password_manager/android/save_update_password_message_delegate.h
@@ -121,6 +121,13 @@
       const password_manager::PasswordForm& pending_credentials,
       bool update_password);
 
+  // Gets account name or email that should be displayed in the description
+  // messages. Returns a nullopt if account info should not be displayed.
+  std::optional<std::string> GetAccountForMessageDescription(
+      const std::optional<AccountInfo>& account_info,
+      const password_manager::PasswordForm& pending_credentials,
+      bool update_password);
+
   // Returns string id for the message primary button. Takes into account
   // whether this is save or update password scenario and whether the update
   // message will be followed by a username confirmation dialog.
@@ -169,8 +176,8 @@
 
   raw_ptr<content::WebContents> web_contents_ = nullptr;
 
-  // Can be the empty string, the account email, or the account full name.
-  std::string account_email_;
+  // Can be a nullopt, the account email, or the account full name.
+  std::optional<std::string> account_email_;
   bool update_password_ = false;
 
   // ManagePasswordsState maintains the password form that is being
diff --git a/chrome/browser/password_manager/android/save_update_password_message_delegate_unittest.cc b/chrome/browser/password_manager/android/save_update_password_message_delegate_unittest.cc
index 60a9791..d8aea1c 100644
--- a/chrome/browser/password_manager/android/save_update_password_message_delegate_unittest.cc
+++ b/chrome/browser/password_manager/android/save_update_password_message_delegate_unittest.cc
@@ -35,6 +35,7 @@
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
 #include "components/password_manager/core/browser/stub_password_manager_client.h"
 #include "components/password_manager/core/common/password_manager_features.h"
+#include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/signin/public/identity_manager/account_capabilities_test_mutator.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/public/test/web_contents_tester.h"
@@ -129,7 +130,7 @@
               (const std::vector<std::u16string>& usernames,
                const std::u16string& username,
                const std::u16string& password,
-               const std::string& account_email),
+               const std::optional<std::string>& account_email),
               (override));
   MOCK_METHOD(void, Dismiss, (), (override));
 };
@@ -148,7 +149,9 @@
       const std::vector<raw_ptr<const PasswordForm, VectorExperimental>>*
           best_matches);
   void RecordPasswordSaved();
-  void SetPendingCredentials(std::u16string username, std::u16string password);
+  void SetPendingCredentials(std::u16string username,
+                             std::u16string password,
+                             bool is_account_store = false);
   static PasswordForm CreatePasswordForm(std::u16string username,
                                          std::u16string password);
 
@@ -307,9 +310,13 @@
 
 void SaveUpdatePasswordMessageDelegateTest::SetPendingCredentials(
     std::u16string username,
-    std::u16string password) {
+    std::u16string password,
+    bool is_account_store) {
   pending_credentials_.username_value = std::move(username);
   pending_credentials_.password_value = std::move(password);
+  pending_credentials_.in_store =
+      is_account_store ? password_manager::PasswordForm::Store::kAccountStore
+                       : password_manager::PasswordForm::Store::kProfileStore;
 }
 
 // static
@@ -1312,6 +1319,56 @@
   DismissMessage(messages::DismissReason::UNKNOWN);
 }
 
+// Tests that the description is set correctly when the signed in user updated
+// the password in the local store.
+TEST_F(SaveUpdatePasswordMessageDelegateTest,
+       SignedInDescription_UpdatePasswordInAccountStore) {
+  // Enables using split storages (local and account).
+  profile()->GetPrefs()->SetInteger(
+      password_manager::prefs::kPasswordsUseUPMLocalAndSeparateStores,
+      static_cast<int>(
+          password_manager::prefs::UseUpmLocalAndSeparateStoresState::kOn));
+
+  SetPendingCredentials(kUsername, kPassword, /*is_account_store=*/true);
+  auto form_manager =
+      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
+  const bool is_signed_in = true;
+  const bool is_update = true;
+  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/is_signed_in,
+                 /*update_password=*/is_update);
+
+  EXPECT_EQ(GetExpectedUPMMessageDescription(is_update, is_signed_in,
+                                             kAccountEmail16),
+            GetMessageWrapper()->GetDescription());
+
+  DismissMessage(messages::DismissReason::UNKNOWN);
+}
+
+// Tests that the description is set correctly when the signed in user updated
+// the password in the local store.
+TEST_F(SaveUpdatePasswordMessageDelegateTest,
+       SignedOutDescription_UpdatePasswordInLocalStore) {
+  // Enables using split storages (local and account).
+  profile()->GetPrefs()->SetInteger(
+      password_manager::prefs::kPasswordsUseUPMLocalAndSeparateStores,
+      static_cast<int>(
+          password_manager::prefs::UseUpmLocalAndSeparateStoresState::kOn));
+
+  SetPendingCredentials(kUsername, kPassword, /*is_account_store=*/false);
+  auto form_manager =
+      CreateFormManager(GURL(kDefaultUrl), empty_best_matches());
+  const bool is_update = true;
+  EnqueueMessage(std::move(form_manager), /*user_signed_in=*/true,
+                 /*update_password=*/is_update);
+
+  // Should display signed out message for updating the password in the local
+  // store (even when the user is signed in).
+  EXPECT_EQ(GetExpectedUPMMessageDescription(is_update, false, kAccountEmail16),
+            GetMessageWrapper()->GetDescription());
+
+  DismissMessage(messages::DismissReason::UNKNOWN);
+}
+
 // Tests that the description is set correctly when the signed-in user with a
 // non-displayable email updates a password.
 TEST_F(SaveUpdatePasswordMessageDelegateTest,
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc
index 128dde7..d94e543 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client.cc
@@ -191,12 +191,6 @@
 }
 #endif
 
-const syncer::SyncService* GetSyncServiceForProfile(Profile* profile) {
-  if (SyncServiceFactory::HasSyncService(profile))
-    return SyncServiceFactory::GetForProfile(profile);
-  return nullptr;
-}
-
 }  // namespace
 
 // static
@@ -709,7 +703,10 @@
 }
 
 const syncer::SyncService* ChromePasswordManagerClient::GetSyncService() const {
-  return GetSyncServiceForProfile(profile_);
+  if (SyncServiceFactory::HasSyncService(profile_)) {
+    return SyncServiceFactory::GetForProfile(profile_);
+  }
+  return nullptr;
 }
 
 password_manager::PasswordStoreInterface*
@@ -735,13 +732,6 @@
   return PasswordReuseManagerFactory::GetForProfile(profile_);
 }
 
-password_manager::SyncState ChromePasswordManagerClient::GetPasswordSyncState()
-    const {
-  const syncer::SyncService* sync_service =
-      SyncServiceFactory::GetForProfile(profile_);
-  return password_manager::sync_util::GetPasswordSyncState(sync_service);
-}
-
 bool ChromePasswordManagerClient::WasLastNavigationHTTPError() const {
   DCHECK(web_contents());
 
@@ -1377,12 +1367,9 @@
 #if BUILDFLAG(ENABLE_DICE_SUPPORT)
       credentials_filter_(
           this,
-          base::BindRepeating(&GetSyncServiceForProfile, profile_),
           DiceWebSigninInterceptorFactory::GetForProfile(profile_)),
 #else
-      credentials_filter_(
-          this,
-          base::BindRepeating(&GetSyncServiceForProfile, profile_)),
+      credentials_filter_(this),
 #endif  // BUILDFLAG(ENABLE_DICE_SUPPORT)
 #if BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
       account_storage_auth_helper_(
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.h b/chrome/browser/password_manager/chrome_password_manager_client.h
index 2b07ec50..f12cf40 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.h
+++ b/chrome/browser/password_manager/chrome_password_manager_client.h
@@ -210,7 +210,6 @@
       const override;
   password_manager::PasswordReuseManager* GetPasswordReuseManager()
       const override;
-  password_manager::SyncState GetPasswordSyncState() const override;
   bool WasLastNavigationHTTPError() const override;
 
   net::CertStatus GetMainFrameCertStatus() const override;
diff --git a/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc b/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc
index 59ab8c3..fc518fec 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client_unittest.cc
@@ -458,45 +458,6 @@
   EXPECT_FALSE(logging_active);
 }
 
-TEST_F(ChromePasswordManagerClientTest, GetPasswordSyncState) {
-  sync_service()->GetUserSettings()->SetSelectedTypes(
-      /*sync_everything=*/false,
-      /*types=*/{syncer::UserSelectableType::kPasswords});
-  sync_service()->SetIsUsingExplicitPassphrase(false);
-
-  ChromePasswordManagerClient* client = GetClient();
-
-  // Passwords are syncing and custom passphrase isn't used.
-  EXPECT_EQ(password_manager::SyncState::kSyncingNormalEncryption,
-            client->GetPasswordSyncState());
-
-  // Sync paused due to a persistent auth error.
-  sync_service()->SetPersistentAuthError();
-  EXPECT_EQ(password_manager::SyncState::kNotSyncing,
-            client->GetPasswordSyncState());
-
-  // Again, using a custom passphrase.
-  sync_service()->ClearAuthError();
-  sync_service()->SetIsUsingExplicitPassphrase(true);
-
-  EXPECT_EQ(password_manager::SyncState::kSyncingWithCustomPassphrase,
-            client->GetPasswordSyncState());
-
-  // Report correctly if we aren't syncing passwords.
-  sync_service()->GetUserSettings()->SetSelectedTypes(
-      /*sync_everything=*/false,
-      /*types=*/{syncer::UserSelectableType::kBookmarks});
-
-  EXPECT_EQ(password_manager::SyncState::kNotSyncing,
-            client->GetPasswordSyncState());
-
-  // Again, without a custom passphrase.
-  sync_service()->SetIsUsingExplicitPassphrase(false);
-
-  EXPECT_EQ(password_manager::SyncState::kNotSyncing,
-            client->GetPasswordSyncState());
-}
-
 TEST_F(ChromePasswordManagerClientTest,
        SavingPasswordsTrueDeterminedByService) {
   // Test that saving passwords depends on querying the settings service.
diff --git a/chrome/browser/password_manager/multi_profile_credentials_filter.cc b/chrome/browser/password_manager/multi_profile_credentials_filter.cc
index 2f17817..55f56fac 100644
--- a/chrome/browser/password_manager/multi_profile_credentials_filter.cc
+++ b/chrome/browser/password_manager/multi_profile_credentials_filter.cc
@@ -14,11 +14,8 @@
 
 MultiProfileCredentialsFilter::MultiProfileCredentialsFilter(
     password_manager::PasswordManagerClient* client,
-    SyncServiceFactoryFunction sync_service_factory_function,
     DiceWebSigninInterceptor* dice_web_signin_interceptor)
-    : password_manager::SyncCredentialsFilter(
-          client,
-          std::move(sync_service_factory_function)),
+    : password_manager::SyncCredentialsFilter(client),
       dice_web_signin_interceptor_(dice_web_signin_interceptor) {}
 
 bool MultiProfileCredentialsFilter::ShouldSave(
diff --git a/chrome/browser/password_manager/multi_profile_credentials_filter.h b/chrome/browser/password_manager/multi_profile_credentials_filter.h
index f36664d..e197734 100644
--- a/chrome/browser/password_manager/multi_profile_credentials_filter.h
+++ b/chrome/browser/password_manager/multi_profile_credentials_filter.h
@@ -19,7 +19,6 @@
   // interception.
   MultiProfileCredentialsFilter(
       password_manager::PasswordManagerClient* client,
-      SyncServiceFactoryFunction sync_service_factory_function,
       DiceWebSigninInterceptor* dice_web_signin_interceptor);
 
   MultiProfileCredentialsFilter(const MultiProfileCredentialsFilter&) = delete;
diff --git a/chrome/browser/password_manager/multi_profile_credentials_filter_unittest.cc b/chrome/browser/password_manager/multi_profile_credentials_filter_unittest.cc
index 09e936d0..3e28c81 100644
--- a/chrome/browser/password_manager/multi_profile_credentials_filter_unittest.cc
+++ b/chrome/browser/password_manager/multi_profile_credentials_filter_unittest.cc
@@ -63,12 +63,21 @@
     return identity_manager_;
   }
 
+  const syncer::SyncService* GetSyncService() const override {
+    return sync_service_;
+  }
+
   void set_identity_manager(signin::IdentityManager* manager) {
     identity_manager_ = manager;
   }
 
+  void set_sync_service(const syncer::SyncService* sync_service) {
+    sync_service_ = sync_service;
+  }
+
  private:
   raw_ptr<signin::IdentityManager> identity_manager_ = nullptr;
+  raw_ptr<const syncer::SyncService> sync_service_ = nullptr;
 };
 
 }  // namespace
@@ -76,14 +85,7 @@
 class MultiProfileCredentialsFilterTest : public BrowserWithTestWindowTest {
  public:
   MultiProfileCredentialsFilterTest()
-      : sync_filter_(&test_password_manager_client_, GetSyncServiceCallback()) {
-  }
-
-  password_manager::SyncCredentialsFilter::SyncServiceFactoryFunction
-  GetSyncServiceCallback() {
-    return base::BindRepeating(&MultiProfileCredentialsFilterTest::sync_service,
-                               base::Unretained(this));
-  }
+      : sync_filter_(&test_password_manager_client_) {}
 
   signin::IdentityTestEnvironment* identity_test_env() {
     return identity_test_env_profile_adaptor_->identity_test_env();
@@ -131,6 +133,7 @@
 
     test_password_manager_client_.set_identity_manager(
         identity_test_env()->identity_manager());
+    test_password_manager_client_.set_sync_service(&sync_service_);
 
     // If features::kEnablePasswordsAccountStorage is enabled, then the browser
     // never asks to save the primary account's password. So fake-signin an
@@ -160,13 +163,11 @@
   }
 
  protected:
-  const syncer::SyncService* sync_service() { return &sync_service_; }
-
   network::TestURLLoaderFactory test_url_loader_factory_;
+  syncer::TestSyncService sync_service_;
   TestPasswordManagerClient test_password_manager_client_;
   std::unique_ptr<IdentityTestEnvironmentProfileAdaptor>
       identity_test_env_profile_adaptor_;
-  syncer::TestSyncService sync_service_;
   password_manager::SyncCredentialsFilter sync_filter_;
   std::unique_ptr<DiceWebSigninInterceptor> dice_web_signin_interceptor_;
 };
@@ -181,7 +182,7 @@
 
   ASSERT_FALSE(sync_filter_.ShouldSave(form));
   MultiProfileCredentialsFilter multi_profile_filter(
-      password_manager_client(), GetSyncServiceCallback(),
+      password_manager_client(),
       /*dice_web_signin_interceptor=*/nullptr);
   EXPECT_FALSE(multi_profile_filter.ShouldSave(form));
 }
@@ -193,7 +194,7 @@
           "user@example.org");
   ASSERT_TRUE(sync_filter_.ShouldSave(form));
   MultiProfileCredentialsFilter multi_profile_filter(
-      password_manager_client(), GetSyncServiceCallback(),
+      password_manager_client(),
       /*dice_web_signin_interceptor=*/nullptr);
   EXPECT_TRUE(multi_profile_filter.ShouldSave(form));
 }
@@ -206,8 +207,7 @@
   ASSERT_TRUE(sync_filter_.ShouldSave(form));
 
   MultiProfileCredentialsFilter multi_profile_filter(
-      password_manager_client(), GetSyncServiceCallback(),
-      dice_web_signin_interceptor());
+      password_manager_client(), dice_web_signin_interceptor());
   EXPECT_TRUE(multi_profile_filter.ShouldSave(form));
 }
 
@@ -223,8 +223,7 @@
   ASSERT_TRUE(sync_filter_.ShouldSave(form));
 
   MultiProfileCredentialsFilter multi_profile_filter(
-      password_manager_client(), GetSyncServiceCallback(),
-      dice_web_signin_interceptor());
+      password_manager_client(), dice_web_signin_interceptor());
   EXPECT_FALSE(multi_profile_filter.ShouldSave(form));
 }
 
@@ -240,8 +239,7 @@
   ASSERT_TRUE(sync_filter_.ShouldSave(form));
 
   MultiProfileCredentialsFilter multi_profile_filter(
-      password_manager_client(), GetSyncServiceCallback(),
-      dice_web_signin_interceptor());
+      password_manager_client(), dice_web_signin_interceptor());
   EXPECT_TRUE(multi_profile_filter.ShouldSave(form));
 }
 
@@ -263,8 +261,7 @@
   ASSERT_TRUE(dice_web_signin_interceptor_->is_interception_in_progress());
 
   MultiProfileCredentialsFilter multi_profile_filter(
-      password_manager_client(), GetSyncServiceCallback(),
-      dice_web_signin_interceptor());
+      password_manager_client(), dice_web_signin_interceptor());
   EXPECT_FALSE(multi_profile_filter.ShouldSave(form));
 }
 
@@ -284,8 +281,7 @@
             SigninInterceptionHeuristicOutcome::kInterceptProfileSwitch);
 
   MultiProfileCredentialsFilter multi_profile_filter(
-      password_manager_client(), GetSyncServiceCallback(),
-      dice_web_signin_interceptor());
+      password_manager_client(), dice_web_signin_interceptor());
   EXPECT_FALSE(multi_profile_filter.ShouldSave(form));
 }
 
@@ -305,8 +301,7 @@
       /*is_new_account=*/true, /*is_sync_signin=*/false, kFormEmail));
 
   MultiProfileCredentialsFilter multi_profile_filter(
-      password_manager_client(), GetSyncServiceCallback(),
-      dice_web_signin_interceptor());
+      password_manager_client(), dice_web_signin_interceptor());
   EXPECT_FALSE(multi_profile_filter.ShouldSave(form));
 }
 
@@ -332,7 +327,6 @@
   // Not interception, credentials should be saved.
   ASSERT_FALSE(dice_web_signin_interceptor_->is_interception_in_progress());
   MultiProfileCredentialsFilter multi_profile_filter(
-      password_manager_client(), GetSyncServiceCallback(),
-      dice_web_signin_interceptor());
+      password_manager_client(), dice_web_signin_interceptor());
   EXPECT_TRUE(multi_profile_filter.ShouldSave(form));
 }
diff --git a/chrome/browser/pdf/pdf_viewer_stream_manager.cc b/chrome/browser/pdf/pdf_viewer_stream_manager.cc
index 5d80384..5b14b6ac 100644
--- a/chrome/browser/pdf/pdf_viewer_stream_manager.cc
+++ b/chrome/browser/pdf/pdf_viewer_stream_manager.cc
@@ -12,6 +12,7 @@
 
 #include "base/containers/contains.h"
 #include "base/functional/bind.h"
+#include "base/memory/ptr_util.h"
 #include "base/memory/weak_ptr.h"
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/navigation_handle.h"
@@ -103,6 +104,19 @@
 PdfViewerStreamManager::~PdfViewerStreamManager() = default;
 
 // static
+void PdfViewerStreamManager::Create(content::WebContents* contents) {
+  if (FromWebContents(contents)) {
+    return;
+  }
+
+  // TODO(crbug.com/1445746): Use a factory to create
+  // `TestPdfViewerStreamManager` instances for testing.
+  // Using `new` to access a non-public constructor.
+  contents->SetUserData(UserDataKey(),
+                        base::WrapUnique(new PdfViewerStreamManager(contents)));
+}
+
+// static
 PdfViewerStreamManager* PdfViewerStreamManager::FromRenderFrameHost(
     content::RenderFrameHost* render_frame_host) {
   return FromWebContents(
diff --git a/chrome/browser/pdf/pdf_viewer_stream_manager.h b/chrome/browser/pdf/pdf_viewer_stream_manager.h
index 77a1f932..6bd9dbe 100644
--- a/chrome/browser/pdf/pdf_viewer_stream_manager.h
+++ b/chrome/browser/pdf/pdf_viewer_stream_manager.h
@@ -51,6 +51,8 @@
 // `extensions::StreamContainer` objects are stored from
 // `PluginResponseInterceptorURLLoaderThrottle::WillProcessResponse()` until
 // the PDF viewer is no longer in use.
+//
+// Use `PdfViewerStreamManager::Create()` to create an instance.
 // Use `PdfViewerStreamManager::FromWebContents()` to get an instance.
 class PdfViewerStreamManager
     : public content::WebContentsObserver,
@@ -72,6 +74,13 @@
     content::GlobalRenderFrameHostId global_id;
   };
 
+  // Creates a `PdfViewerStreamManager` for `contents`, if one doesn't already
+  // exist.
+  static void Create(content::WebContents* contents);
+
+  // Use `Create()` to create an instance instead.
+  static void CreateForWebContents(content::WebContents*) = delete;
+
   PdfViewerStreamManager(const PdfViewerStreamManager&) = delete;
   PdfViewerStreamManager& operator=(const PdfViewerStreamManager&) = delete;
   ~PdfViewerStreamManager() override;
@@ -189,6 +198,7 @@
     bool plugin_can_save_ = false;
   };
 
+  // Use `Create()` to create an instance instead.
   explicit PdfViewerStreamManager(content::WebContents* contents);
 
   // Returns the stream info claimed by `embedder_host`, or nullptr if there's
diff --git a/chrome/browser/pdf/pdf_viewer_stream_manager_unittest.cc b/chrome/browser/pdf/pdf_viewer_stream_manager_unittest.cc
index 72e45a1..b5f084c 100644
--- a/chrome/browser/pdf/pdf_viewer_stream_manager_unittest.cc
+++ b/chrome/browser/pdf/pdf_viewer_stream_manager_unittest.cc
@@ -57,7 +57,7 @@
     // Create `PdfViewerStreamManager` if it doesn't exist already. If `host` is
     // the primary main frame, then the previous `PdfViewerStreamManager` may
     // have been deleted as part of the above navigation.
-    PdfViewerStreamManager::CreateForWebContents(
+    PdfViewerStreamManager::Create(
         ChromeRenderViewHostTestHarness::web_contents());
     return new_host;
   }
diff --git a/chrome/browser/predictors/predictor_database.cc b/chrome/browser/predictors/predictor_database.cc
index add88989..a9adc778 100644
--- a/chrome/browser/predictors/predictor_database.cc
+++ b/chrome/browser/predictors/predictor_database.cc
@@ -100,8 +100,6 @@
 
 void PredictorDatabaseInternal::Initialize() {
   DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
-  // TODO(tburkard): figure out if we need this.
-  //  db_->set_exclusive_locking();
   if (autocomplete_table_->IsCancelled() ||
       resource_prefetch_tables_->IsCancelled()) {
     return;
diff --git a/chrome/browser/printing/print_preview_dialog_controller.cc b/chrome/browser/printing/print_preview_dialog_controller.cc
index a462e52..54efe456 100644
--- a/chrome/browser/printing/print_preview_dialog_controller.cc
+++ b/chrome/browser/printing/print_preview_dialog_controller.cc
@@ -89,6 +89,7 @@
   GURL GetDialogContentURL() const override;
   void GetDialogSize(gfx::Size* size) const override;
   std::string GetDialogArgs() const override;
+  void OnDialogClosingFromKeyEvent() override;
   void OnDialogClosed(const std::string& json_retval) override;
   void OnCloseContents(WebContents* source, bool* out_close_dialog) override;
   bool ShouldShowDialogTitle() const override;
@@ -158,6 +159,10 @@
   return std::string();
 }
 
+void PrintPreviewDialogDelegate::OnDialogClosingFromKeyEvent() {
+  OnDialogClosed(std::string());
+}
+
 void PrintPreviewDialogDelegate::OnDialogClosed(
     const std::string& /* json_retval */) {
   if (on_dialog_closed_called_ || !initiator())
diff --git a/chrome/browser/resources/ash/settings/os_about_page/os_about_page.html b/chrome/browser/resources/ash/settings/os_about_page/os_about_page.html
index 5b82fca..3850fe8 100644
--- a/chrome/browser/resources/ash/settings/os_about_page/os_about_page.html
+++ b/chrome/browser/resources/ash/settings/os_about_page/os_about_page.html
@@ -162,7 +162,7 @@
               </eol-offer-section>
             </template>
             <localized-link
-                id="endOfLifeMessageContainer" hidden="[[!hasEndOfLife_]]"
+                id="endOfLifeMessage" hidden="[[!hasEndOfLife_]]"
                 localized-string="$i18n{endOfLifeMessage}">
             </localized-link>
             <div class="secondary" hidden="[[!hasDeferredUpdate_]]">
@@ -172,12 +172,12 @@
           </div>
           <div class="separator" hidden="[[!showButtonContainer_]]"></div>
           <span id="buttonContainer" hidden="[[!showButtonContainer_]]">
-            <cr-button id="relaunch" hidden$="[[!showRelaunch_]]"
+            <cr-button id="relaunchButton" hidden$="[[!showRelaunch_]]"
                       on-click="onRelaunchClick_">
                 [[getRelaunchButtonText_(
                                 currentUpdateStatusEvent_)]]
             </cr-button>
-            <cr-button id="checkForUpdates" hidden="[[!showCheckUpdates_]]"
+            <cr-button id="checkForUpdatesButton" hidden="[[!showCheckUpdates_]]"
                 on-click="onCheckUpdatesClick_"
                 deep-link-focus-id$="[[Setting.kCheckForOsUpdate]]">
               $i18n{aboutCheckForUpdates}
@@ -324,8 +324,8 @@
             </div>
           </if>
         </div>
-        <div class="settings-box padded block" id="regulatoryInfo"
-          hidden$="[[!shouldShowRegulatoryOrSafetyInfo_(regulatoryInfo_)]]">
+        <div id="regulatoryInfo" class="settings-box padded block"
+            hidden$="[[!shouldShowRegulatoryOrSafetyInfo_(regulatoryInfo_)]]">
           <if expr="_google_chrome">
             <div class="secondary" hidden$="[[!shouldShowSafetyInfo_()]]">
               <a target="_blank" href="$i18n{aboutProductSafetyURL}">
diff --git a/chrome/browser/resources/ash/settings/os_about_page/os_about_page.ts b/chrome/browser/resources/ash/settings/os_about_page/os_about_page.ts
index bc22873..5ca9ad1a 100644
--- a/chrome/browser/resources/ash/settings/os_about_page/os_about_page.ts
+++ b/chrome/browser/resources/ash/settings/os_about_page/os_about_page.ts
@@ -27,6 +27,7 @@
 import '../crostini_page/crostini_settings_card.js';
 
 import {LifetimeBrowserProxyImpl} from '/shared/settings/lifetime_browser_proxy.js';
+import {CrButtonElement} from 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
 import {I18nMixin} from 'chrome://resources/ash/common/cr_elements/i18n_mixin.js';
 import {WebUiListenerMixin} from 'chrome://resources/ash/common/cr_elements/web_ui_listener_mixin.js';
 import {assert} from 'chrome://resources/js/assert.js';
@@ -51,17 +52,21 @@
   }
 }
 
-interface OsAboutPageElement {
+export interface OsAboutPageElement {
   $: {
-    updateStatusMessageInner: HTMLDivElement,
+    buttonContainer: HTMLElement,
+    checkForUpdatesButton: CrButtonElement,
     productLogo: HTMLImageElement,
+    regulatoryInfo: HTMLElement,
+    relaunchButton: CrButtonElement,
+    updateStatusMessageInner: HTMLDivElement,
   };
 }
 
 const OsAboutPageBase = DeepLinkingMixin(
     RouteOriginMixin(I18nMixin(WebUiListenerMixin(PolymerElement))));
 
-class OsAboutPageElement extends OsAboutPageBase {
+export class OsAboutPageElement extends OsAboutPageBase {
   static get is() {
     return 'os-about-page' as const;
   }
diff --git a/chrome/browser/resources/ash/settings/os_bluetooth_page/os_bluetooth_page.ts b/chrome/browser/resources/ash/settings/os_bluetooth_page/os_bluetooth_page.ts
index e7f03ee4..c376c30 100644
--- a/chrome/browser/resources/ash/settings/os_bluetooth_page/os_bluetooth_page.ts
+++ b/chrome/browser/resources/ash/settings/os_bluetooth_page/os_bluetooth_page.ts
@@ -81,8 +81,8 @@
         OsBluetoothDevicesSubpageBrowserProxyImpl.getInstance();
   }
 
-  override ready(): void {
-    super.ready();
+  override connectedCallback(): void {
+    super.connectedCallback();
     getBluetoothConfig().observeSystemProperties(
         this.systemPropertiesObserverReceiver_.$.bindNewPipeAndPassRemote());
   }
diff --git a/chrome/browser/resources/ash/settings/os_settings.ts b/chrome/browser/resources/ash/settings/os_settings.ts
index e69a325..2e80b04 100644
--- a/chrome/browser/resources/ash/settings/os_settings.ts
+++ b/chrome/browser/resources/ash/settings/os_settings.ts
@@ -203,6 +203,7 @@
 export {AboutPageBrowserProxy, AboutPageBrowserProxyImpl, BrowserChannel, ChannelInfo, EndOfLifeInfo, RegulatoryInfo, TpmFirmwareUpdateStatusChangedEvent, UpdateStatus, VersionInfo} from './os_about_page/about_page_browser_proxy.js';
 export {DeviceNameBrowserProxy, DeviceNameBrowserProxyImpl, DeviceNameMetadata} from './os_about_page/device_name_browser_proxy.js';
 export {DeviceNameState, SetDeviceNameResult} from './os_about_page/device_name_util.js';
+export {OsAboutPageElement} from './os_about_page/os_about_page.js';
 export {AndroidAppsBrowserProxy, AndroidAppsBrowserProxyImpl} from './os_apps_page/android_apps_browser_proxy.js';
 export {AppManagementFileHandlingItemElement} from './os_apps_page/app_management_page/file_handling_item.js';
 export {PluginVmBrowserProxy, PluginVmBrowserProxyImpl} from './os_apps_page/app_management_page/plugin_vm_page/plugin_vm_browser_proxy.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/accessibility_common/BUILD.gn
index 9cef5eb..8786dce4 100644
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/BUILD.gn
@@ -47,8 +47,6 @@
 ]
 
 js_modules = [
-  "dictation/parse/parse_strategy.js",
-  "dictation/parse/pumpkin/pumpkin_constants.js",
   "dictation/parse/pumpkin_parse_strategy.js",
   "dictation/parse/sandboxed_pumpkin_tagger.js",
   "dictation/parse/simple_parse_strategy.js",
@@ -81,6 +79,8 @@
   "dictation/macros/smart_select_between_macro.ts",
   "dictation/macros/stop_listening_macro.ts",
   "dictation/parse/input_text_strategy.ts",
+  "dictation/parse/parse_strategy.ts",
+  "dictation/parse/pumpkin/pumpkin_constants.ts",
   "facegaze/facegaze.ts",
   "facegaze/gesture_detector.ts",
   "facegaze/mouse_controller.ts",
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/parse_strategy.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/parse_strategy.js
deleted file mode 100644
index c7039d8e..0000000
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/parse_strategy.js
+++ /dev/null
@@ -1,50 +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.
-
-/**
- * @fileoverview Defines a base class that represents a strategy for parsing
- * text and converting it into a macro.
- */
-
-import {InputController} from '../input_controller.js';
-import {Macro} from '../macros/macro.js';
-
-/**
- * Represents a strategy for parsing speech input and converting it into a
- * Macro.
- */
-export class ParseStrategy {
-  /** @param {!InputController} inputController */
-  constructor(inputController) {
-    /** @private {!InputController} */
-    this.inputController_ = inputController;
-    /** @protected {boolean} */
-    this.enabled = false;
-  }
-
-  /** @return {!InputController} */
-  getInputController() {
-    return this.inputController_;
-  }
-
-  /** @return {boolean} */
-  isEnabled() {
-    return this.enabled;
-  }
-
-  /** @param {boolean} enabled */
-  setEnabled(enabled) {
-    this.enabled = enabled;
-  }
-
-  /** Refreshes this strategy when the locale changes. */
-  refresh() {}
-
-  /**
-   * Accepts text, parses it, and returns a Macro.
-   * @param {string} text
-   * @return {!Promise<?Macro>}
-   */
-  async parse(text) {}
-}
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/parse_strategy.ts b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/parse_strategy.ts
new file mode 100644
index 0000000..0c89d1a9
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/parse_strategy.ts
@@ -0,0 +1,43 @@
+// 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.
+
+/**
+ * @fileoverview Defines a base class that represents a strategy for parsing
+ * text and converting it into a macro.
+ */
+
+import {InputController} from '../input_controller.js';
+import {Macro} from '../macros/macro.js';
+
+/**
+ * Represents a strategy for parsing speech input and converting it into a
+ * Macro.
+ */
+export class ParseStrategy {
+  private inputController_: InputController;
+  protected enabled = false;
+  constructor(inputController: InputController) {
+    this.inputController_ = inputController;
+  }
+
+  getInputController(): InputController {
+    return this.inputController_;
+  }
+
+  isEnabled(): boolean {
+    return this.enabled;
+  }
+
+  setEnabled(enabled: boolean): void {
+    this.enabled = enabled;
+  }
+
+  /** Refreshes this strategy when the locale changes. */
+  refresh(): void {}
+
+  /** Accepts text, parses it, and returns a Macro. */
+  async parse(text: string): Promise<Macro|null> {
+    throw new Error(`The parse() function must be implemented by each subclass. Trying to parse text: ${text}`);
+  }
+}
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/pumpkin_constants.js b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/pumpkin_constants.js
deleted file mode 100644
index b5dee15..0000000
--- a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/pumpkin_constants.js
+++ /dev/null
@@ -1,163 +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 Defines constants used for Pumpkin.
- */
-
-/**
- * The sandbox doesn't have access to extension APIs, so we need to keep a copy
- * of the PumpkinData typedef. Copied from
- * third_party/closure_compiler/externs/accessibility_private.js
- * TODO(crbug.com/1258190): Consider creating a python script that would pull
- * this definition in at build time.
- * @typedef {{
- *   js_pumpkin_tagger_bin_js: ArrayBuffer,
- *   tagger_wasm_main_js: ArrayBuffer,
- *   tagger_wasm_main_wasm: ArrayBuffer,
- *   en_us_action_config_binarypb: ArrayBuffer,
- *   en_us_pumpkin_config_binarypb: ArrayBuffer,
- *   fr_fr_action_config_binarypb: ArrayBuffer,
- *   fr_fr_pumpkin_config_binarypb: ArrayBuffer,
- *   it_it_action_config_binarypb: ArrayBuffer,
- *   it_it_pumpkin_config_binarypb: ArrayBuffer,
- *   de_de_action_config_binarypb: ArrayBuffer,
- *   de_de_pumpkin_config_binarypb: ArrayBuffer,
- *   es_es_action_config_binarypb: ArrayBuffer,
- *   es_es_pumpkin_config_binarypb: ArrayBuffer
- * }}
- */
-export let PumpkinData;
-
-/**
- * The types of commands that can come from SandboxedPumpkinTagger.
- * @enum {string}
- */
-export const FromPumpkinTaggerCommand = {
-  READY: 'ready',
-  FULLY_INITIALIZED: 'fullyInitialized',
-  TAG_RESULTS: 'tagResults',
-  REFRESHED: 'refreshed',
-};
-
-/**
- * The types of commands that can be sent to SandboxedPumpkinTagger.
- * @enum {string}
- */
-export const ToPumpkinTaggerCommand = {
-  LOAD: 'load',
-  TAG: 'tagAndGetNBestHypotheses',
-  REFRESH: 'refresh',
-};
-
-/**
- * Defines the message data received from SandboxedPumpkinTagger.
- * @typedef {{
- *  results: (!Object|null|undefined),
- *  type: !FromPumpkinTaggerCommand,
- * }}
- */
-export let FromPumpkinTagger;
-
-/**
- * Defines the message data sent to SandboxedPumpkinTagger.
- * @typedef {{
- *  locale: (!PumpkinLocale|undefined),
- *  numResults: (number|undefined),
- *  pumpkinData: (!PumpkinData|null|undefined),
- *  text: (string|undefined),
- *  type: !ToPumpkinTaggerCommand,
- * }}
- */
-export let ToPumpkinTagger;
-
-/**
- * Supported Pumpkin locales.
- * @enum {string}
- */
-export const PumpkinLocale = {
-  EN_US: 'en_us',
-  FR_FR: 'fr_fr',
-  IT_IT: 'it_it',
-  DE_DE: 'de_de',
-  ES_ES: 'es_es',
-};
-
-/**
- * Map from BCP-47 locale code (see dictation.cc) to directory name in
- * dictation/parse/pumpkin/ for supported Pumpkin locales.
- * TODO(crbug.com/1264544): Determine if all en* languages can be mapped to
- * en_us. Possible locales are listed in dictation.cc,
- * kWebSpeechSupportedLocales.
- * @const {!Object<string, PumpkinLocale>}
- */
-export const SUPPORTED_LOCALES = {
-  // English.
-  'en-US': PumpkinLocale.EN_US,
-  'en-AU': PumpkinLocale.EN_US,
-  'en-CA': PumpkinLocale.EN_US,
-  'en-GB': PumpkinLocale.EN_US,
-  'en-GH': PumpkinLocale.EN_US,
-  'en-HK': PumpkinLocale.EN_US,
-  'en-IN': PumpkinLocale.EN_US,
-  'en-KE': PumpkinLocale.EN_US,
-  'en-NG': PumpkinLocale.EN_US,
-  'en-NZ': PumpkinLocale.EN_US,
-  'en-PH': PumpkinLocale.EN_US,
-  'en-PK': PumpkinLocale.EN_US,
-  'en-SG': PumpkinLocale.EN_US,
-  'en-TZ': PumpkinLocale.EN_US,
-  'en-ZA': PumpkinLocale.EN_US,
-  // French.
-  'fr-BE': PumpkinLocale.FR_FR,
-  'fr-CA': PumpkinLocale.FR_FR,
-  'fr-CH': PumpkinLocale.FR_FR,
-  'fr-FR': PumpkinLocale.FR_FR,
-  // Italian.
-  'it-CH': PumpkinLocale.IT_IT,
-  'it-IT': PumpkinLocale.IT_IT,
-  // German.
-  'de-AT': PumpkinLocale.DE_DE,
-  'de-CH': PumpkinLocale.DE_DE,
-  'de-DE': PumpkinLocale.DE_DE,
-  // Spanish.
-  'es-AR': PumpkinLocale.ES_ES,
-  'es-BO': PumpkinLocale.ES_ES,
-  'es-CL': PumpkinLocale.ES_ES,
-  'es-CO': PumpkinLocale.ES_ES,
-  'es-CR': PumpkinLocale.ES_ES,
-  'es-DO': PumpkinLocale.ES_ES,
-  'es-EC': PumpkinLocale.ES_ES,
-  'es-ES': PumpkinLocale.ES_ES,
-  'es-GT': PumpkinLocale.ES_ES,
-  'es-HN': PumpkinLocale.ES_ES,
-  'es-MX': PumpkinLocale.ES_ES,
-  'es-NI': PumpkinLocale.ES_ES,
-  'es-PA': PumpkinLocale.ES_ES,
-  'es-PE': PumpkinLocale.ES_ES,
-  'es-PR': PumpkinLocale.ES_ES,
-  'es-PY': PumpkinLocale.ES_ES,
-  'es-SV': PumpkinLocale.ES_ES,
-  'es-US': PumpkinLocale.ES_ES,
-  'es-UY': PumpkinLocale.ES_ES,
-  'es-VE': PumpkinLocale.ES_ES,
-};
-
-/**
- * PumpkinTagger Hypothesis argument names. These should match the variable
- * argument placeholders in voiceaccess.patterns_template and the static strings
- * defined in voiceaccess/utils/PumpkinUtils.java in google3.
- * @enum {string}
- */
-export const HypothesisArgumentName = {
-  SEM_TAG: 'SEM_TAG',
-  NUM_ARG: 'NUM_ARG',
-  OPEN_ENDED_TEXT: 'OPEN_ENDED_TEXT',
-  BEGIN_PHRASE: 'BEGIN_PHRASE',
-  END_PHRASE: 'END_PHRASE',
-};
-
-/** @const {string} */
-export const SANDBOXED_PUMPKIN_TAGGER_JS_FILE =
-    'dictation/parse/sandboxed_pumpkin_tagger.js';
diff --git a/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/pumpkin_constants.ts b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/pumpkin_constants.ts
new file mode 100644
index 0000000..7341200
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/accessibility_common/dictation/parse/pumpkin/pumpkin_constants.ts
@@ -0,0 +1,144 @@
+// 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 Defines constants used for Pumpkin.
+ */
+
+/**
+ * The sandbox doesn't have access to extension APIs, so we need to keep a copy
+ * of the PumpkinData typedef. Copied from
+ * third_party/closure_compiler/externs/accessibility_private.js
+ * TODO(crbug.com/1258190): Consider creating a python script that would pull
+ * this definition in at build time.
+ */
+export interface PumpkinData {
+  js_pumpkin_tagger_bin_js: ArrayBuffer;
+  tagger_wasm_main_js: ArrayBuffer;
+  tagger_wasm_main_wasm: ArrayBuffer;
+  en_us_action_config_binarypb: ArrayBuffer;
+  en_us_pumpkin_config_binarypb: ArrayBuffer;
+  fr_fr_action_config_binarypb: ArrayBuffer;
+  fr_fr_pumpkin_config_binarypb: ArrayBuffer;
+  it_it_action_config_binarypb: ArrayBuffer;
+  it_it_pumpkin_config_binarypb: ArrayBuffer;
+  de_de_action_config_binarypb: ArrayBuffer;
+  de_de_pumpkin_config_binarypb: ArrayBuffer;
+  es_es_action_config_binarypb: ArrayBuffer;
+  es_es_pumpkin_config_binarypb: ArrayBuffer;
+}
+
+/** The types of commands that can come from SandboxedPumpkinTagger. */
+export enum FromPumpkinTaggerCommand {
+  READY = 'ready',
+  FULLY_INITIALIZED = 'fullyInitialized',
+  TAG_RESULTS = 'tagResults',
+  REFRESHED = 'refreshed',
+}
+
+/** The types of commands that can be sent to SandboxedPumpkinTagger. */
+export enum ToPumpkinTaggerCommand {
+  LOAD = 'load',
+  TAG = 'tagAndGetNBestHypotheses',
+  REFRESH ='refresh',
+}
+
+/** Defines the message data received from SandboxedPumpkinTagger. */
+export interface FromPumpkinTagger {
+  results?: Object|null;
+  type: FromPumpkinTaggerCommand;
+}
+
+/** Defines the message data sent to SandboxedPumpkinTagger. */
+export interface ToPumpkinTagger {
+  locale?: PumpkinLocale;
+  numResults?: number;
+  pumpkinData?: PumpkinData|null;
+  text?: string;
+  type: ToPumpkinTaggerCommand;
+}
+
+/** Supported Pumpkin locales. */
+export enum PumpkinLocale {
+  EN_US = 'en_us',
+  FR_FR = 'fr_fr',
+  IT_IT = 'it_it',
+  DE_DE = 'de_de',
+  ES_ES = 'es_es',
+}
+
+/**
+ * Map from BCP-47 locale code (see dictation.cc) to directory name in
+ * dictation/parse/pumpkin/ for supported Pumpkin locales.
+ * TODO(crbug.com/1264544): Determine if all en* languages can be mapped to
+ * en_us. Possible locales are listed in dictation.cc,
+ * kWebSpeechSupportedLocales.
+ */
+export const SUPPORTED_LOCALES = {
+  // English.
+  'en-US': PumpkinLocale.EN_US,
+  'en-AU': PumpkinLocale.EN_US,
+  'en-CA': PumpkinLocale.EN_US,
+  'en-GB': PumpkinLocale.EN_US,
+  'en-GH': PumpkinLocale.EN_US,
+  'en-HK': PumpkinLocale.EN_US,
+  'en-IN': PumpkinLocale.EN_US,
+  'en-KE': PumpkinLocale.EN_US,
+  'en-NG': PumpkinLocale.EN_US,
+  'en-NZ': PumpkinLocale.EN_US,
+  'en-PH': PumpkinLocale.EN_US,
+  'en-PK': PumpkinLocale.EN_US,
+  'en-SG': PumpkinLocale.EN_US,
+  'en-TZ': PumpkinLocale.EN_US,
+  'en-ZA': PumpkinLocale.EN_US,
+  // French.
+  'fr-BE': PumpkinLocale.FR_FR,
+  'fr-CA': PumpkinLocale.FR_FR,
+  'fr-CH': PumpkinLocale.FR_FR,
+  'fr-FR': PumpkinLocale.FR_FR,
+  // Italian.
+  'it-CH': PumpkinLocale.IT_IT,
+  'it-IT': PumpkinLocale.IT_IT,
+  // German.
+  'de-AT': PumpkinLocale.DE_DE,
+  'de-CH': PumpkinLocale.DE_DE,
+  'de-DE': PumpkinLocale.DE_DE,
+  // Spanish.
+  'es-AR': PumpkinLocale.ES_ES,
+  'es-BO': PumpkinLocale.ES_ES,
+  'es-CL': PumpkinLocale.ES_ES,
+  'es-CO': PumpkinLocale.ES_ES,
+  'es-CR': PumpkinLocale.ES_ES,
+  'es-DO': PumpkinLocale.ES_ES,
+  'es-EC': PumpkinLocale.ES_ES,
+  'es-ES': PumpkinLocale.ES_ES,
+  'es-GT': PumpkinLocale.ES_ES,
+  'es-HN': PumpkinLocale.ES_ES,
+  'es-MX': PumpkinLocale.ES_ES,
+  'es-NI': PumpkinLocale.ES_ES,
+  'es-PA': PumpkinLocale.ES_ES,
+  'es-PE': PumpkinLocale.ES_ES,
+  'es-PR': PumpkinLocale.ES_ES,
+  'es-PY': PumpkinLocale.ES_ES,
+  'es-SV': PumpkinLocale.ES_ES,
+  'es-US': PumpkinLocale.ES_ES,
+  'es-UY': PumpkinLocale.ES_ES,
+  'es-VE': PumpkinLocale.ES_ES,
+};
+
+/**
+ * PumpkinTagger Hypothesis argument names. These should match the variable
+ * argument placeholders in voiceaccess.patterns_template and the static strings
+ * defined in voiceaccess/utils/PumpkinUtils.java in google3.
+ */
+export enum HypothesisArgumentName {
+  SEM_TAG = 'SEM_TAG',
+  NUM_ARG = 'NUM_ARG',
+  OPEN_ENDED_TEXT = 'OPEN_ENDED_TEXT',
+  BEGIN_PHRASE = 'BEGIN_PHRASE',
+  END_PHRASE = 'END_PHRASE',
+}
+
+export const SANDBOXED_PUMPKIN_TAGGER_JS_FILE =
+    'dictation/parse/sandboxed_pumpkin_tagger.js';
diff --git a/chrome/browser/resources/gaia_auth_host/saml_handler.js b/chrome/browser/resources/gaia_auth_host/saml_handler.js
index 036ac41..e6fcfd2b 100644
--- a/chrome/browser/resources/gaia_auth_host/saml_handler.js
+++ b/chrome/browser/resources/gaia_auth_host/saml_handler.js
@@ -56,6 +56,9 @@
   /** @const */
   const SAML_API_Error = 'ChromeOS.SAML.APIError';
 
+  /** @const */
+  const SAML_INCORRECT_ATTESTATION = 'ChromeOS.SAML.IncorrectAttestation';
+
   /**
    * The script to inject into webview and its sub frames.
    * @type {string}
@@ -174,6 +177,25 @@
       };
 
       /**
+       * This enum is tied directly to a UMA enum defined in
+       * //tools/metrics/histograms/metadata/chromeos/enums.xml, and should
+       * always reflect it (do not change one without changing the other). These
+       * values are persisted to logs. Entries should not be renumbered and
+       * numeric values should never be reused.
+       * @enum {number}
+       */
+      SamlHandler.IncorrectAttestationStage = {
+        // onBeforeRequest_(details) method.
+        ON_BEFORE_REQUEST: 0,
+        // onBeforeSendHeaders_(details) method.
+        ON_BEFORE_SEND_HEADERS: 1,
+        // continueDelayedRedirect_(url, challengeResponse) method.
+        CONTINUE_DELAYED_REDIRECT: 2,
+        // Enum Max value.
+        MAX: 3,
+      };
+
+      /**
        * The webview that serves IdP pages.
        * @private {!WebView}
        */
@@ -658,8 +680,10 @@
     continueDelayedRedirect_(url, challengeResponse) {
       if (this.deviceAttestationStage_ !==
           SamlHandler.DeviceAttestationStage.ORIGINAL_REDIRECT_CANCELED) {
-        console.error(
+        console.warn(
             'SamlHandler.continueDelayedRedirect_: incorrect attestation stage');
+        this.recordInIncorrectAttestationHistogram_(
+            SamlHandler.IncorrectAttestationStage.CONTINUE_DELAYED_REDIRECT);
         return;
       }
 
@@ -718,8 +742,9 @@
 
       // Reset state in case of unexpected requests during device attestation.
       this.deviceAttestationStage_ = SamlHandler.DeviceAttestationStage.NONE;
-      console.error(
-          'SamlHandler.onBeforeRequest_: incorrect attestation stage');
+      console.warn('SamlHandler.onBeforeRequest_: incorrect attestation stage');
+      this.recordInIncorrectAttestationHistogram_(
+          SamlHandler.IncorrectAttestationStage.ON_BEFORE_REQUEST);
       return {};
     }
 
@@ -778,8 +803,10 @@
 
       // Reset state in case of unexpected navigation during device attestation.
       this.deviceAttestationStage_ = SamlHandler.DeviceAttestationStage.NONE;
-      console.error(
+      console.warn(
           'SamlHandler.onBeforeSendHeaders_: incorrect attestation stage');
+      this.recordInIncorrectAttestationHistogram_(
+          SamlHandler.IncorrectAttestationStage.ON_BEFORE_SEND_HEADERS);
       return {};
     }
 
@@ -877,6 +904,18 @@
     }
 
     /**
+     * Invoked to record value in ChromeOS.SAML.IncorrectAttestation metric.
+     * @private
+     */
+    recordInIncorrectAttestationHistogram_(value) {
+      chrome.send('metricsHandler:recordInHistogram', [
+        SAML_INCORRECT_ATTESTATION,
+        value,
+        SamlHandler.IncorrectAttestationStage.MAX,
+      ]);
+    }
+
+    /**
      * Invoked to record that password wasn't confirmed in
      * ChromeOS.SAML.APIError metric.
      */
diff --git a/chrome/browser/resources/settings/privacy_sandbox/privacy_sandbox_manage_topics_subpage.html b/chrome/browser/resources/settings/privacy_sandbox/privacy_sandbox_manage_topics_subpage.html
index f4c94e0..3800f767 100644
--- a/chrome/browser/resources/settings/privacy_sandbox/privacy_sandbox_manage_topics_subpage.html
+++ b/chrome/browser/resources/settings/privacy_sandbox/privacy_sandbox_manage_topics_subpage.html
@@ -12,7 +12,11 @@
     width: 100%;
   }
 
-  .topic-toggle {
+  .topic-toggle-row:hover {
+    background-color: var(--cr-hover-background-color);
+  }
+
+  .topic-toggle-row {
     align-items: center;
     display: flex;
     width: 100%
@@ -32,7 +36,7 @@
   $i18nRaw{manageTopicsPageDescription}
 </div>
 <template is="dom-repeat" items="[[firstLevelTopicsList_]]">
-  <div class="topic-toggle">
+  <div class="topic-toggle-row" on-click="onToggleRowClick_" actionable>
     <div class="outer-row">
       <span class="icon">
         <iron-icon slot="icon"
diff --git a/chrome/browser/resources/settings/privacy_sandbox/privacy_sandbox_manage_topics_subpage.ts b/chrome/browser/resources/settings/privacy_sandbox/privacy_sandbox_manage_topics_subpage.ts
index 919d0e9..8b33ff4 100644
--- a/chrome/browser/resources/settings/privacy_sandbox/privacy_sandbox_manage_topics_subpage.ts
+++ b/chrome/browser/resources/settings/privacy_sandbox/privacy_sandbox_manage_topics_subpage.ts
@@ -149,7 +149,20 @@
     });
   }
 
+  // When the user clicks anywhere on the toggle row, we click the toggle itself
+  // here to trigger its on-change event.
+  private onToggleRowClick_(e: DomRepeatEvent<PrivacySandboxInterest>) {
+    e.stopPropagation();
+    assert(e.model.item?.topic);
+    const toggleId = `#toggle-${e.model.item.topic.topicId}`;
+    const toggleBeingChanged =
+        this.shadowRoot!.querySelector<CrToggleElement>(toggleId);
+    assert(toggleBeingChanged);
+    toggleBeingChanged!.click();
+  }
+
   private async onToggleChange_(e: DomRepeatEvent<PrivacySandboxInterest>) {
+    e.stopPropagation();
     this.topicBeingToggled_ = e.model.item;
     assert(this.topicBeingToggled_);
     assert(this.topicBeingToggled_.topic);
@@ -157,8 +170,9 @@
     const toggleBeingChanged =
         this.shadowRoot!.querySelector<CrToggleElement>(toggleId);
     assert(toggleBeingChanged);
-    // If the toggle is checked, then the First Level Topic needs to be
-    // updated to be unblocked.
+    // At this point, the toggle checked state has already changed. If the
+    // toggle is now checked, then the First Level Topic needs to be updated to
+    // be unblocked.
     if (toggleBeingChanged.checked) {
       this.updateTopicState_({blocked: false});
       return;
diff --git a/chrome/browser/ssl/https_first_mode_settings_tracker.cc b/chrome/browser/ssl/https_first_mode_settings_tracker.cc
index 8d3afee..bb28477c 100644
--- a/chrome/browser/ssl/https_first_mode_settings_tracker.cc
+++ b/chrome/browser/ssl/https_first_mode_settings_tracker.cc
@@ -324,6 +324,7 @@
   }
   // The prefs must be set in this order, as setting kHttpsOnlyModeEnabled
   // will cause kHttpsOnlyModeAutoEnabled to be reset to false.
+  keep_http_allowlist_on_next_pref_change_ = true;
   profile_->GetPrefs()->SetBoolean(prefs::kHttpsOnlyModeEnabled, true);
   profile_->GetPrefs()->SetBoolean(prefs::kHttpsOnlyModeAutoEnabled, true);
 }
@@ -348,14 +349,17 @@
         GetSyntheticFieldTrialGroupName(setting));
   }
 
-  // Reset the allowlist when the pref changes. A user going from HTTPS-Upgrades
-  // to HTTPS-First Mode shouldn't inherit the set of allowlisted sites (or
-  // vice versa).
-  StatefulSSLHostStateDelegate* state =
-      static_cast<StatefulSSLHostStateDelegate*>(
-          profile_->GetSSLHostStateDelegate());
-  state->ClearHttpsOnlyModeAllowlist();
-  state->ClearHttpsEnforcelist();
+  // Reset the HTTP allowlist and HTTPS enforcelist when the pref changes.
+  // A user going from HTTPS-Upgrades to HTTPS-First Mode shouldn't inherit the
+  // set of allowlisted sites (or vice versa).
+  if (!keep_http_allowlist_on_next_pref_change_) {
+    StatefulSSLHostStateDelegate* state =
+        static_cast<StatefulSSLHostStateDelegate*>(
+            profile_->GetSSLHostStateDelegate());
+    state->ClearHttpsOnlyModeAllowlist();
+    state->ClearHttpsEnforcelist();
+  }
+  keep_http_allowlist_on_next_pref_change_ = false;
 
   // Since the user modified the UI pref, explicitly disable any automatic
   // HTTPS-First Mode heuristic.
diff --git a/chrome/browser/ssl/https_first_mode_settings_tracker.h b/chrome/browser/ssl/https_first_mode_settings_tracker.h
index 50286c1..d8b7e46 100644
--- a/chrome/browser/ssl/https_first_mode_settings_tracker.h
+++ b/chrome/browser/ssl/https_first_mode_settings_tracker.h
@@ -128,6 +128,14 @@
       const std::vector<site_engagement::mojom::SiteEngagementDetails>&
           details);
 
+  // If true, will not clear the HTTP allowlist when the HFM pref changes next
+  // time. Will be set to false again upon pref change.
+  // The HFM pref can be changed by the UI setting or the Typically Secure User
+  // heuristic. We only need to clear the allowlist if the UI setting is. The
+  // pref observer has no way of knowing how the pref was changed, so we use
+  // this bool to tell it to clear or keep the allowlist.
+  bool keep_http_allowlist_on_next_pref_change_ = false;
+
   raw_ptr<Profile> profile_;
   PrefChangeRegistrar pref_change_registrar_;
   raw_ptr<base::Clock> clock_;
diff --git a/chrome/browser/ssl/https_first_mode_settings_tracker_unittest.cc b/chrome/browser/ssl/https_first_mode_settings_tracker_unittest.cc
index 718696c5..7b92141 100644
--- a/chrome/browser/ssl/https_first_mode_settings_tracker_unittest.cc
+++ b/chrome/browser/ssl/https_first_mode_settings_tracker_unittest.cc
@@ -892,6 +892,97 @@
                                1);
 }
 
+// Checks that manually changing the HFM pref in the UI clears the HTTP
+// allowlist. A variant of this test
+// (TypicallySecureUserTest.PrefUpdatedByHeuristic_ShouldNotClearAllowlist)
+// checks that a heuristic auto-enabling HFM does NOT clear the allowlist.
+TEST_F(HttpsFirstModeSettingsTrackerTest, PrefUpdated_ShouldClearAllowlist) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndDisableFeature(features::kHttpsFirstModeIncognito);
+
+  // Instantiate the service so that it can track pref changes.
+  HttpsFirstModeService* service =
+      HttpsFirstModeServiceFactory::GetForProfile(profile());
+  ASSERT_TRUE(service);
+
+  EXPECT_FALSE(profile()->GetPrefs()->GetBoolean(prefs::kHttpsOnlyModeEnabled));
+
+  // Allowlist a host for for http.
+  StatefulSSLHostStateDelegate* state =
+      StatefulSSLHostStateDelegateFactory::GetForProfile(profile());
+  ASSERT_TRUE(state);
+  content::StoragePartition* storage_partition =
+      profile()->GetDefaultStoragePartition();
+  state->AllowHttpForHost("http-allowed.com", storage_partition);
+  EXPECT_TRUE(
+      state->IsHttpAllowedForHost("http-allowed.com", storage_partition));
+
+  // Change the UI setting. This should clear the http allowlist.
+  profile()->GetPrefs()->SetBoolean(prefs::kHttpsOnlyModeEnabled, true);
+  EXPECT_FALSE(
+      state->IsHttpAllowedForHost("http-allowed.com", storage_partition));
+}
+
+TEST_F(TypicallySecureUserTest,
+       PrefUpdatedByHeuristic_ShouldNotClearAllowlist) {
+  StatefulSSLHostStateDelegate* state =
+      StatefulSSLHostStateDelegateFactory::GetForProfile(profile());
+  ASSERT_TRUE(state);
+  content::StoragePartition* storage_partition =
+      profile()->GetDefaultStoragePartition();
+  EXPECT_FALSE(profile()->GetPrefs()->GetBoolean(prefs::kHttpsOnlyModeEnabled));
+  EXPECT_FALSE(
+      profile()->GetPrefs()->HasPrefPath(prefs::kHttpsOnlyModeAutoEnabled));
+
+  state->AllowHttpForHost("http-allowed.com", storage_partition);
+  EXPECT_TRUE(
+      state->IsHttpAllowedForHost("http-allowed.com", storage_partition));
+
+  // From here on, do a bunch of navigations and advance the clock so that
+  // Typically Secure heuristic eventually auto-enables HFM.
+
+  base::Time now = clock()->Now();
+  clock()->SetNow(now + base::Days(3));
+  // Record a fallback event to start the Typically Secure observation.
+  RecordFallbackEventAndMaybeEnableHttpsFirstMode();
+  EXPECT_EQ(1u, hfm_service()->GetFallbackEntryCountForTesting());
+
+  // Move forward and record another event.
+  clock()->SetNow(now + base::Days(8));
+  RecordFallbackEventAndMaybeEnableHttpsFirstMode();
+  EXPECT_EQ(2u, hfm_service()->GetFallbackEntryCountForTesting());
+
+  // We have observed for long enough, and we don't have too many fallback
+  // events (2). However, last fallback event was too recent. Move forward
+  // again.
+  clock()->SetNow(now + base::Days(9) + base::Hours(1));
+  hfm_service()->CheckUserIsTypicallySecureAndMaybeEnableHttpsFirstMode();
+  EXPECT_EQ(2u, hfm_service()->GetFallbackEntryCountForTesting());
+
+  // Last fallback event is now a day old, but we don't have enough recent
+  // navigations. Don't enable yet.
+  EXPECT_FALSE(
+      hfm_service()->IsInterstitialEnabledByTypicallySecureUserHeuristic());
+  EXPECT_FALSE(
+      profile()->GetPrefs()->HasPrefPath(prefs::kHttpsOnlyModeEnabled));
+  EXPECT_FALSE(
+      profile()->GetPrefs()->HasPrefPath(prefs::kHttpsOnlyModeAutoEnabled));
+
+  // Finally, do lots of navigations. Should auto-enable HFM now.
+  IncrementRecentNavigationCount(100u);
+  hfm_service()->CheckUserIsTypicallySecureAndMaybeEnableHttpsFirstMode();
+  EXPECT_TRUE(
+      hfm_service()->IsInterstitialEnabledByTypicallySecureUserHeuristic());
+  EXPECT_TRUE(profile()->GetPrefs()->HasPrefPath(prefs::kHttpsOnlyModeEnabled));
+  EXPECT_TRUE(
+      profile()->GetPrefs()->HasPrefPath(prefs::kHttpsOnlyModeAutoEnabled));
+
+  // Typically Secure heuristic auto-enabling HFM should not clear the
+  // allowlist.
+  EXPECT_TRUE(
+      state->IsHttpAllowedForHost("http-allowed.com", storage_partition));
+}
+
 // Tests the pref update observer callback, with the HttpsFirstModeIncognito
 // feature flag enabled (which changes the setting to be a tri-state that
 // controls two boolean preferences).
diff --git a/chrome/browser/ssl/https_upgrades_browsertest.cc b/chrome/browser/ssl/https_upgrades_browsertest.cc
index 4188cae..104bfd1 100644
--- a/chrome/browser/ssl/https_upgrades_browsertest.cc
+++ b/chrome/browser/ssl/https_upgrades_browsertest.cc
@@ -247,8 +247,9 @@
     mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK);
     host_resolver()->AddRule("*", "127.0.0.1");
 
-    // Set up "bad-https.com" and "nonunique-hostname-bad-https" as hostnames
-    // with an SSL error. HTTPS upgrades to this host will fail.
+    // Set up "bad-https.com", "bad-https2.com" and
+    // "nonunique-hostname-bad-https" as hostnames with an SSL error. HTTPS
+    // upgrades to these hosts will fail.
     scoped_refptr<net::X509Certificate> cert(https_server_.GetCertificate());
     net::CertVerifyResult verify_result;
     verify_result.is_issued_by_known_root = false;
@@ -261,6 +262,9 @@
         cert, "www.bad-https.com", verify_result,
         net::ERR_CERT_COMMON_NAME_INVALID);
     mock_cert_verifier_.mock_cert_verifier()->AddResultForCertAndHost(
+        cert, "bad-https2.com", verify_result,
+        net::ERR_CERT_COMMON_NAME_INVALID);
+    mock_cert_verifier_.mock_cert_verifier()->AddResultForCertAndHost(
         cert, "nonunique-hostname-bad-https", verify_result,
         net::ERR_CERT_COMMON_NAME_INVALID);
 
@@ -1261,8 +1265,10 @@
   hfm_service->CheckUserIsTypicallySecureAndMaybeEnableHttpsFirstMode();
   size_t initial_navigation_count = hfm_service->GetRecentNavigationCount();
 
-  GURL http_url = http_server()->GetURL("bad-https.com", "/simple.html");
-  GURL https_url = https_server()->GetURL("bad-https.com", "/simple.html");
+  // Use a different hostname than the PRE_ test so that we don't hit the
+  // allowlist.
+  GURL http_url = http_server()->GetURL("bad-https2.com", "/simple.html");
+  GURL https_url = https_server()->GetURL("bad-https2.com", "/simple.html");
   NavigateAndWaitForFallback(contents, http_url);
   EXPECT_EQ(http_url, contents->GetLastCommittedURL());
   EXPECT_EQ(initial_navigation_count + 1u,
diff --git a/chrome/browser/sync/test/integration/password_manager_sync_test.cc b/chrome/browser/sync/test/integration/password_manager_sync_test.cc
index 4ac47dbe..739104b 100644
--- a/chrome/browser/sync/test/integration/password_manager_sync_test.cc
+++ b/chrome/browser/sync/test/integration/password_manager_sync_test.cc
@@ -1147,7 +1147,7 @@
             SyncTest::kDefaultUserEmail);
   EXPECT_EQ(
       password_manager::sync_util::GetPasswordSyncState(GetSyncService(0)),
-      password_manager::SyncState::kSyncingNormalEncryption);
+      password_manager::sync_util::SyncState::kSyncingNormalEncryption);
 
   // Enter a persistent auth error state.
   GetClient(0)->EnterSyncPausedStateForPrimaryAccount();
@@ -1159,7 +1159,7 @@
           GetSyncService(0)));
   EXPECT_EQ(
       password_manager::sync_util::GetPasswordSyncState(GetSyncService(0)),
-      password_manager::SyncState::kNotSyncing);
+      password_manager::sync_util::SyncState::kNotActive);
 
   // In the current implementation, the APIs below treat sync as enabled/active
   // even while paused.
diff --git a/chrome/browser/tabpersistence/android/java/src/org/chromium/chrome/browser/tabpersistence/FlatBufferTabStateSerializer.java b/chrome/browser/tabpersistence/android/java/src/org/chromium/chrome/browser/tabpersistence/FlatBufferTabStateSerializer.java
index 11b230e1a..02d4f5f0 100644
--- a/chrome/browser/tabpersistence/android/java/src/org/chromium/chrome/browser/tabpersistence/FlatBufferTabStateSerializer.java
+++ b/chrome/browser/tabpersistence/android/java/src/org/chromium/chrome/browser/tabpersistence/FlatBufferTabStateSerializer.java
@@ -4,11 +4,13 @@
 
 package org.chromium.chrome.browser.tabpersistence;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.google.flatbuffers.FlatBufferBuilder;
 
+import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tab.TabState;
 import org.chromium.chrome.browser.tab.TabUserAgent;
@@ -17,6 +19,8 @@
 import org.chromium.chrome.browser.tab.flatbuffer.TabStateFlatBufferV1;
 import org.chromium.chrome.browser.tab.flatbuffer.UserAgentType;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.nio.ByteBuffer;
 
 /** {@link TabStateSerializer} backed by a FlatBuffer */
@@ -29,6 +33,27 @@
         mIsEncrypted = isEncrypted;
     }
 
+    @IntDef({
+        TabStateFlatBufferDeserializeResult.SUCCESS,
+        TabStateFlatBufferDeserializeResult.FAILURE_UNKNOWN_REASON,
+        TabStateFlatBufferDeserializeResult.FAILURE_INDEX_OUT_OF_BOUNDS_EXCEPTION,
+        TabStateFlatBufferDeserializeResult.NUM_ENTRIES,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    public @interface TabStateFlatBufferDeserializeResult {
+        /** FlatBuffer was successfully deserialized to TabState. */
+        int SUCCESS = 0;
+
+        /** FlatBuffer deserialization failed because of an unknown reason. */
+        int FAILURE_UNKNOWN_REASON = 1;
+
+        /** FlatBuffer deserialization failed because of an index out of bounds exception. */
+        int FAILURE_INDEX_OUT_OF_BOUNDS_EXCEPTION = 2;
+
+        int NUM_ENTRIES = 3;
+    }
+
     @Override
     public ByteBuffer serialize(TabState state) {
         FlatBufferBuilder fbb = new FlatBufferBuilder();
@@ -67,37 +92,51 @@
 
     @Override
     public TabState deserialize(ByteBuffer bytes) {
-        TabStateFlatBufferV1 tabStateFlatBuffer =
-                TabStateFlatBufferV1.getRootAsTabStateFlatBufferV1(bytes);
+        try {
+            TabStateFlatBufferV1 tabStateFlatBuffer =
+                    TabStateFlatBufferV1.getRootAsTabStateFlatBufferV1(bytes);
 
-        TabState state = new TabState();
-        state.parentId = tabStateFlatBuffer.parentId();
-        state.rootId = tabStateFlatBuffer.rootId();
-        state.openerAppId =
-                NULL_OPENER_APP_ID.equals(tabStateFlatBuffer.openerAppId())
-                        ? null
-                        : tabStateFlatBuffer.openerAppId();
-        state.timestampMillis = tabStateFlatBuffer.timestampMillis();
-        state.lastNavigationCommittedTimestampMillis =
-                tabStateFlatBuffer.lastNavigationCommittedTimestampMillis();
-        state.userAgent = getTabUserAgentTypeFromFlatBuffer(tabStateFlatBuffer.userAgent());
-        state.tabLaunchTypeAtCreation =
-                getLaunchTypeFromFlatBuffer(tabStateFlatBuffer.launchTypeAtCreation());
-        state.themeColor = tabStateFlatBuffer.themeColor();
-        ByteBuffer webContentsStateBuffer =
-                tabStateFlatBuffer.webContentsStateBytesAsByteBuffer() == null
-                        ? ByteBuffer.allocateDirect(0)
-                        : tabStateFlatBuffer.webContentsStateBytesAsByteBuffer().slice();
-        if (mIsEncrypted) {
-            state.contentsState =
-                    new WebContentsState(
-                            ByteBuffer.allocateDirect(webContentsStateBuffer.remaining()));
-            state.contentsState.buffer().put(webContentsStateBuffer);
-        } else {
-            state.contentsState = new WebContentsState(webContentsStateBuffer);
+            TabState state = new TabState();
+            state.parentId = tabStateFlatBuffer.parentId();
+            state.rootId = tabStateFlatBuffer.rootId();
+            state.openerAppId =
+                    NULL_OPENER_APP_ID.equals(tabStateFlatBuffer.openerAppId())
+                            ? null
+                            : tabStateFlatBuffer.openerAppId();
+            state.timestampMillis = tabStateFlatBuffer.timestampMillis();
+            state.lastNavigationCommittedTimestampMillis =
+                    tabStateFlatBuffer.lastNavigationCommittedTimestampMillis();
+            state.userAgent = getTabUserAgentTypeFromFlatBuffer(tabStateFlatBuffer.userAgent());
+            state.tabLaunchTypeAtCreation =
+                    getLaunchTypeFromFlatBuffer(tabStateFlatBuffer.launchTypeAtCreation());
+            state.themeColor = tabStateFlatBuffer.themeColor();
+            ByteBuffer webContentsStateBuffer =
+                    tabStateFlatBuffer.webContentsStateBytesAsByteBuffer() == null
+                            ? ByteBuffer.allocateDirect(0)
+                            : tabStateFlatBuffer.webContentsStateBytesAsByteBuffer().slice();
+            if (mIsEncrypted) {
+                state.contentsState =
+                        new WebContentsState(
+                                ByteBuffer.allocateDirect(webContentsStateBuffer.remaining()));
+                state.contentsState.buffer().put(webContentsStateBuffer);
+            } else {
+                state.contentsState = new WebContentsState(webContentsStateBuffer);
+            }
+            state.contentsState.setVersion(WebContentsState.CONTENTS_STATE_CURRENT_VERSION);
+            return state;
+        } catch (IndexOutOfBoundsException e) {
+            RecordHistogram.recordEnumeratedHistogram(
+                    "Tabs.TabState.FlatBufferDeserializeResult",
+                    TabStateFlatBufferDeserializeResult.FAILURE_INDEX_OUT_OF_BOUNDS_EXCEPTION,
+                    TabStateFlatBufferDeserializeResult.NUM_ENTRIES);
+        } catch (Exception e) {
+            RecordHistogram.recordEnumeratedHistogram(
+                    "Tabs.TabState.FlatBufferDeserializeResult",
+                    TabStateFlatBufferDeserializeResult.FAILURE_UNKNOWN_REASON,
+                    TabStateFlatBufferDeserializeResult.NUM_ENTRIES);
+            assert false : e.getMessage();
         }
-        state.contentsState.setVersion(WebContentsState.CONTENTS_STATE_CURRENT_VERSION);
-        return state;
+        return null;
     }
 
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
diff --git a/chrome/browser/tabpersistence/android/java/src/org/chromium/chrome/browser/tabpersistence/TabStateFileManagerUnitTest.java b/chrome/browser/tabpersistence/android/java/src/org/chromium/chrome/browser/tabpersistence/TabStateFileManagerUnitTest.java
index 7d330d9..fc85789 100644
--- a/chrome/browser/tabpersistence/android/java/src/org/chromium/chrome/browser/tabpersistence/TabStateFileManagerUnitTest.java
+++ b/chrome/browser/tabpersistence/android/java/src/org/chromium/chrome/browser/tabpersistence/TabStateFileManagerUnitTest.java
@@ -17,18 +17,21 @@
 
 import org.chromium.base.StreamUtil;
 import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.HistogramWatcher;
 import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tab.TabState;
 import org.chromium.chrome.browser.tab.TabUserAgent;
 import org.chromium.chrome.browser.tab.WebContentsState;
 import org.chromium.chrome.browser.tab.flatbuffer.TabLaunchTypeAtCreation;
 import org.chromium.chrome.browser.tab.flatbuffer.UserAgentType;
+import org.chromium.chrome.browser.tabpersistence.FlatBufferTabStateSerializer.TabStateFlatBufferDeserializeResult;
 
 import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
 
 /** Unit tests for {@link TabStateFileManager}. */
@@ -57,6 +60,25 @@
     }
 
     @Test
+    public void testInvalidBuffer() {
+        byte[] bytes = new byte[5000];
+        for (int i = 0; i < bytes.length; i++) {
+            bytes[i] = (byte) i;
+        }
+
+        var builder =
+                HistogramWatcher.newBuilder()
+                        .expectIntRecord(
+                                "Tabs.TabState.FlatBufferDeserializeResult",
+                                TabStateFlatBufferDeserializeResult
+                                        .FAILURE_INDEX_OUT_OF_BOUNDS_EXCEPTION);
+        HistogramWatcher histograms = builder.build();
+        FlatBufferTabStateSerializer serializer = new FlatBufferTabStateSerializer(false);
+        Assert.assertNull(serializer.deserialize(ByteBuffer.wrap(bytes)));
+        histograms.assertExpected();
+    }
+
+    @Test
     public void testFlatBufferValuesUnchanged() {
         // FlatBuffer enum values should not be changed as they are persisted across restarts.
         // Changing them would cause backward compatibility issues
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
index 73968abd..9eca9b4c 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarCoordinator.java
@@ -37,6 +37,7 @@
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteControllerProvider;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteDelegate;
+import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteDelegate.AutocompleteLoadCallback;
 import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionsDropdownScrollListener;
 import org.chromium.chrome.browser.omnibox.suggestions.basic.BasicSuggestionProcessor.BookmarkState;
 import org.chromium.chrome.browser.omnibox.suggestions.history_clusters.HistoryClustersProcessor.OpenHistoryClustersDelegate;
@@ -514,6 +515,16 @@
     }
 
     @Override
+    public void loadUrl(
+            String url,
+            int transition,
+            long inputStart,
+            boolean openInNewTab,
+            @Nullable AutocompleteLoadCallback callback) {
+        mLocationBarMediator.loadUrl(url, transition, inputStart, openInNewTab, callback);
+    }
+
+    @Override
     public void loadUrlWithPostData(
             String url, int transition, long inputStart, String postDataType, byte[] postData) {
         mLocationBarMediator.loadUrlWithPostData(
@@ -521,6 +532,24 @@
     }
 
     @Override
+    public void loadUrlWithPostData(
+            String url,
+            int transition,
+            long inputStart,
+            String postDataType,
+            byte[] postData,
+            @Nullable AutocompleteLoadCallback callback) {
+        mLocationBarMediator.loadUrlWithPostData(
+                url,
+                transition,
+                inputStart,
+                postDataType,
+                postData,
+                /* openInNewTab= */ false,
+                callback);
+    }
+
+    @Override
     public boolean didFocusUrlFromFakebox() {
         return mLocationBarMediator.didFocusUrlFromFakebox();
     }
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
index dc9460d..31f2c13 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediator.java
@@ -51,12 +51,15 @@
 import org.chromium.chrome.browser.omnibox.status.StatusCoordinator;
 import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator;
+import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteDelegate.AutocompleteLoadCallback;
 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler;
 import org.chromium.chrome.browser.prefetch.settings.PreloadPagesSettingsBridge;
 import org.chromium.chrome.browser.prefetch.settings.PreloadPagesState;
 import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManager;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.Tab.LoadUrlResult;
 import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.theme.ThemeUtils;
@@ -513,7 +516,16 @@
     }
 
     /* package */ void loadUrl(String url, int transition, long inputStart, boolean openInNewTab) {
-        loadUrlWithPostData(url, transition, inputStart, null, null, openInNewTab);
+        loadUrl(url, transition, inputStart, openInNewTab, null);
+    }
+
+    /* package */ void loadUrl(
+            String url,
+            int transition,
+            long inputStart,
+            boolean openInNewTab,
+            @Nullable AutocompleteLoadCallback callback) {
+        loadUrlWithPostData(url, transition, inputStart, null, null, openInNewTab, callback);
     }
 
     /* package */ void loadUrlWithPostData(
@@ -523,6 +535,18 @@
             String postDataType,
             byte[] postData,
             boolean openInNewTab) {
+        loadUrlWithPostData(
+                url, transition, inputStart, postDataType, postData, openInNewTab, null);
+    }
+
+    /* package */ void loadUrlWithPostData(
+            String url,
+            int transition,
+            long inputStart,
+            String postDataType,
+            byte[] postData,
+            boolean openInNewTab,
+            @Nullable AutocompleteLoadCallback callback) {
         assert mLocationBarDataProvider != null;
         Tab currentTab = mLocationBarDataProvider.getTab();
 
@@ -551,6 +575,18 @@
                 // refresh the page as it does when you click and press enter on any other site.
                 if (url.isEmpty()) url = currentTab.getUrl().getSpec();
             }
+
+            if (callback != null) {
+                currentTab.addObserver(
+                        new EmptyTabObserver() {
+                            @Override
+                            public void onLoadUrl(
+                                    Tab tab, LoadUrlParams params, LoadUrlResult loadUrlResult) {
+                                callback.onLoadUrl(params, loadUrlResult);
+                                tab.removeObserver(this);
+                            }
+                        });
+            }
         }
 
         // Loads the |url| in a new tab or the current ContentView and gives focus to the
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java
index 2f6b7f4..3353a09 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/LocationBarMediatorTest.java
@@ -71,6 +71,7 @@
 import org.chromium.chrome.browser.omnibox.geo.GeolocationHeader;
 import org.chromium.chrome.browser.omnibox.status.StatusCoordinator;
 import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteCoordinator;
+import org.chromium.chrome.browser.omnibox.suggestions.AutocompleteDelegate.AutocompleteLoadCallback;
 import org.chromium.chrome.browser.omnibox.test.R;
 import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler;
 import org.chromium.chrome.browser.prefetch.settings.PreloadPagesSettingsBridge;
@@ -80,7 +81,9 @@
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tab.Tab.LoadUrlResult;
 import org.chromium.chrome.browser.tab.TabLaunchType;
+import org.chromium.chrome.browser.tab.TabObserver;
 import org.chromium.chrome.browser.tabmodel.TabModelSelector;
 import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
 import org.chromium.chrome.browser.util.ChromeAccessibilityUtil;
@@ -91,6 +94,8 @@
 import org.chromium.components.search_engines.TemplateUrlService;
 import org.chromium.components.signin.identitymanager.IdentityManager;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.common.ResourceRequestBody;
+import org.chromium.content_public.common.ResourceRequestBodyJni;
 import org.chromium.ui.base.PageTransition;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.url.GURL;
@@ -184,6 +189,9 @@
     @Mock private ObjectAnimator mUrlAnimator;
     @Mock private View mRootView;
     @Mock private SearchEngineUtils mSearchEngineUtils;
+    @Mock private AutocompleteLoadCallback mAutocompleteLoadCallback;
+    @Mock private LoadUrlParams mLoadUrlParams;
+    @Mock private LoadUrlResult mLoadUrlResult;
 
     @Mock private LensController mLensController;
     @Mock private IdentityServicesProvider mIdentityServicesProvider;
@@ -192,9 +200,11 @@
     @Mock private PreloadPagesSettingsBridge.Natives mPreloadPagesSettingsJni;
     @Mock private LocationBarMediator.OmniboxUma mOmniboxUma;
     @Mock private OmniboxSuggestionsDropdownEmbedderImpl mEmbedderImpl;
+    @Mock private ResourceRequestBody.Natives mResourceRequestBodyJni;
 
     @Captor private ArgumentCaptor<Runnable> mRunnableCaptor;
     @Captor private ArgumentCaptor<LoadUrlParams> mLoadUrlParamsCaptor;
+    @Captor private ArgumentCaptor<TabObserver> mTabObserverCaptor;
 
     private Context mContext;
     private ObservableSupplierImpl<Profile> mProfileSupplier = new ObservableSupplierImpl<>();
@@ -220,6 +230,7 @@
         mJniMocker.mock(UrlUtilitiesJni.TEST_HOOKS, mUrlUtilitiesJniMock);
         mJniMocker.mock(OmniboxPrerenderJni.TEST_HOOKS, mPrerenderJni);
         mJniMocker.mock(PreloadPagesSettingsBridgeJni.TEST_HOOKS, mPreloadPagesSettingsJni);
+        mJniMocker.mock(ResourceRequestBodyJni.TEST_HOOKS, mResourceRequestBodyJni);
         doReturn(mProfile).when(mTab).getProfile();
         doReturn(mIdentityManager).when(mIdentityServicesProvider).getIdentityManager(mProfile);
         IdentityServicesProvider.setInstanceForTests(mIdentityServicesProvider);
@@ -374,6 +385,44 @@
     }
 
     @Test
+    public void testLoadUrlWithAutocompleteLoadCallback() {
+        mMediator.onFinishNativeInitialization();
+
+        doReturn(mTab).when(mLocationBarDataProvider).getTab();
+        mMediator.loadUrl(TEST_URL, PageTransition.TYPED, 0, false, mAutocompleteLoadCallback);
+
+        verify(mTab).loadUrl(mLoadUrlParamsCaptor.capture());
+        assertEquals(TEST_URL, mLoadUrlParamsCaptor.getValue().getUrl());
+        assertEquals(
+                PageTransition.TYPED | PageTransition.FROM_ADDRESS_BAR,
+                mLoadUrlParamsCaptor.getValue().getTransitionType());
+
+        verify(mTab).addObserver(mTabObserverCaptor.capture());
+        mTabObserverCaptor.getValue().onLoadUrl(mTab, mLoadUrlParams, mLoadUrlResult);
+        verify(mTab).removeObserver(mTabObserverCaptor.getValue());
+        verify(mAutocompleteLoadCallback).onLoadUrl(mLoadUrlParams, mLoadUrlResult);
+    }
+
+    @Test
+    public void testLoadUrlWithPostData() {
+        mMediator.onFinishNativeInitialization();
+        String text = "text";
+        byte[] data = new byte[] {0, 1, 2, 3, 4};
+
+        doReturn(mTab).when(mLocationBarDataProvider).getTab();
+        doReturn(data).when(mResourceRequestBodyJni).createResourceRequestBodyFromBytes(any());
+        mMediator.loadUrlWithPostData(TEST_URL, PageTransition.TYPED, 0, text, data, false);
+
+        verify(mTab).loadUrl(mLoadUrlParamsCaptor.capture());
+        assertEquals(TEST_URL, mLoadUrlParamsCaptor.getValue().getUrl());
+        assertEquals(
+                PageTransition.TYPED | PageTransition.FROM_ADDRESS_BAR,
+                mLoadUrlParamsCaptor.getValue().getTransitionType());
+        assertTrue(mLoadUrlParamsCaptor.getValue().getVerbatimHeaders().contains(text));
+        assertEquals(data, mLoadUrlParamsCaptor.getValue().getPostData().getEncodedNativeForm());
+    }
+
+    @Test
     public void testLoadUrl_NativeNotInitialized() {
         if (BuildConfig.ENABLE_ASSERTS) {
             try {
diff --git a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteDelegate.java b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteDelegate.java
index adc9e64..565961f 100644
--- a/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteDelegate.java
+++ b/chrome/browser/ui/android/omnibox/java/src/org/chromium/chrome/browser/omnibox/suggestions/AutocompleteDelegate.java
@@ -4,10 +4,20 @@
 
 package org.chromium.chrome.browser.omnibox.suggestions;
 
+import androidx.annotation.Nullable;
+
+import org.chromium.chrome.browser.tab.Tab.LoadUrlResult;
+import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.ui.base.PageTransition;
 
 /** Provides the additional functionality to trigger and interact with autocomplete suggestions. */
 public interface AutocompleteDelegate extends UrlBarDelegate {
+
+    /** Called when loadUrl is done on a {@link Tab}. */
+    interface AutocompleteLoadCallback {
+        void onLoadUrl(LoadUrlParams params, LoadUrlResult loadUrlResult);
+    }
+
     /** Notified that the URL text has changed. */
     void onUrlTextChanged();
 
@@ -48,6 +58,23 @@
     void loadUrl(String url, @PageTransition int transition, long inputStart, boolean openInNewTab);
 
     /**
+     * Requests that the given URL be loaded in the current tab or in a new tab.
+     *
+     * @param url The URL to be loaded.
+     * @param transition The transition type associated with the url load.
+     * @param inputStart The time the input started for the load request.
+     * @param openInNewTab Whether the URL will be loaded in a new tab. If {@code true}, the URL
+     *     will be loaded in a new tab. If {@code false}, The URL will be loaded in the current tab.
+     * @param callback A callback that will be called when loadUrl is done on a {@link Tab}.
+     */
+    void loadUrl(
+            String url,
+            @PageTransition int transition,
+            long inputStart,
+            boolean openInNewTab,
+            @Nullable AutocompleteLoadCallback callback);
+
+    /**
      * Requests that the given URL be loaded in the current tab.
      *
      * @param url The URL to be loaded.
@@ -65,6 +92,25 @@
             byte[] postData);
 
     /**
+     * Requests that the given URL be loaded in the current tab.
+     *
+     * @param url The URL to be loaded.
+     * @param transition The transition type associated with the url load.
+     * @param inputStart The time the input started for the load request.
+     * @param postDataType postData type.
+     * @param postData Post-data to include in the tab URL's request body, ex. bitmap when image
+     *     search.
+     * @param callback A callback that will be called when loadUrl is done on a {@link Tab}.
+     */
+    void loadUrlWithPostData(
+            String url,
+            @PageTransition int transition,
+            long inputStart,
+            String postDataType,
+            byte[] postData,
+            @Nullable AutocompleteLoadCallback callback);
+
+    /**
      * @return Whether the omnibox was focused via the NTP fakebox.
      */
     boolean didFocusUrlFromFakebox();
diff --git a/chrome/browser/ui/android/webid/account_selection_view_android.cc b/chrome/browser/ui/android/webid/account_selection_view_android.cc
index 0834e4b..7b5e958d 100644
--- a/chrome/browser/ui/android/webid/account_selection_view_android.cc
+++ b/chrome/browser/ui/android/webid/account_selection_view_android.cc
@@ -328,6 +328,10 @@
   delegate_->OnMoreDetails();
 }
 
+void AccountSelectionViewAndroid::OnAccountsDisplayed(JNIEnv* env) {
+  delegate_->OnAccountsDisplayed();
+}
+
 bool AccountSelectionViewAndroid::MaybeCreateJavaObject() {
   if (delegate_->GetNativeView() == nullptr ||
       delegate_->GetNativeView()->GetWindowAndroid() == nullptr) {
diff --git a/chrome/browser/ui/android/webid/account_selection_view_android.h b/chrome/browser/ui/android/webid/account_selection_view_android.h
index a4de00e..c1a5238 100644
--- a/chrome/browser/ui/android/webid/account_selection_view_android.h
+++ b/chrome/browser/ui/android/webid/account_selection_view_android.h
@@ -60,6 +60,7 @@
                     const base::android::JavaParamRef<jobject>& idp_config_url,
                     const base::android::JavaParamRef<jobject>& idp_login_url);
   void OnMoreDetails(JNIEnv* env);
+  void OnAccountsDisplayed(JNIEnv* env);
 
  private:
   // Returns either true if the java counterpart of this bridge is initialized
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionBridge.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionBridge.java
index c4e98c4..3f95f43e 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionBridge.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionBridge.java
@@ -231,6 +231,13 @@
     }
 
     @Override
+    public void onAccountsDisplayed() {
+        if (mNativeView != 0) {
+            AccountSelectionBridgeJni.get().onAccountsDisplayed(mNativeView);
+        }
+    }
+
+    @Override
     public void onModalDialogClosed() {
         mAccountSelectionComponent.onModalDialogClosed();
     }
@@ -252,5 +259,7 @@
                 long nativeAccountSelectionViewAndroid, GURL idpConfigUrl, GURL idpLoginUrl);
 
         void onMoreDetails(long nativeAccountSelectionViewAndroid);
+
+        void onAccountsDisplayed(long nativeAccountSelectionViewAndroid);
     }
 }
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionControllerTest.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionControllerTest.java
index 25fd4fff..22e679a 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionControllerTest.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionControllerTest.java
@@ -612,6 +612,7 @@
         pressBack();
         verify(mMockDelegate).onDismissed(IdentityRequestDialogDismissReason.OTHER);
         verify(mMockDelegate).onAccountSelected(TEST_CONFIG_URL, ANA);
+        verify(mMockDelegate).onAccountsDisplayed();
         verifyNoMoreInteractions(mMockDelegate);
         assertTrue(mMediator.wasDismissed());
         // The delayed task should not call delegate after user dismissing.
@@ -670,6 +671,7 @@
 
             assertEquals(1, mSheetAccountItems.size());
             assertEquals(HeaderType.VERIFY, mModel.get(ItemProperties.HEADER).get(TYPE));
+            verify(mMockDelegate).onAccountsDisplayed();
         }
     }
 
@@ -692,6 +694,7 @@
             assertEquals(1, mSheetAccountItems.size());
             assertEquals(
                     HeaderType.VERIFY_AUTO_REAUTHN, mModel.get(ItemProperties.HEADER).get(TYPE));
+            verify(mMockDelegate).onAccountsDisplayed();
         }
     }
 
@@ -710,6 +713,7 @@
             assertEquals(0, mSheetAccountItems.size());
             assertEquals(
                     HeaderType.SIGN_IN_TO_IDP_STATIC, mModel.get(ItemProperties.HEADER).get(TYPE));
+            verify(mMockDelegate, never()).onAccountsDisplayed();
             // For failure dialog, we expect header + IDP sign in text + continue btn
             assertEquals(3, countAllItems());
             assertTrue(containsItemOfType(mModel, ItemProperties.IDP_SIGNIN));
@@ -748,6 +752,7 @@
                     TOKEN_ERROR_EMPTY_URL);
             assertEquals(0, mSheetAccountItems.size());
             assertEquals(HeaderType.SIGN_IN_ERROR, mModel.get(ItemProperties.HEADER).get(TYPE));
+            verify(mMockDelegate, never()).onAccountsDisplayed();
 
             // For error dialog, we expect header + error text + got it button
             assertEquals(3, countAllItems());
@@ -801,6 +806,7 @@
                     TOKEN_ERROR);
             assertEquals(0, mSheetAccountItems.size());
             assertEquals(HeaderType.SIGN_IN_ERROR, mModel.get(ItemProperties.HEADER).get(TYPE));
+            verify(mMockDelegate, never()).onAccountsDisplayed();
 
             // For error dialog, we expect header + error text + got it button
             assertEquals(3, countAllItems());
@@ -923,6 +929,24 @@
         assertFalse(mMediator.wasDismissed());
     }
 
+    @Test
+    public void testWebContentsHidden() {
+        when(mTab.isHidden()).thenReturn(true);
+        when(mMockBottomSheetController.requestShowContent(any(), anyBoolean())).thenReturn(true);
+        mMediator.showAccounts(
+                TEST_ETLD_PLUS_ONE,
+                TEST_ETLD_PLUS_ONE_1,
+                TEST_ETLD_PLUS_ONE_2,
+                Arrays.asList(ANA),
+                IDP_METADATA,
+                CLIENT_ID_METADATA,
+                /* isAutoReauthn= */ false,
+                /* rpContext= */ "signin");
+        verify(mMockBottomSheetController, never()).requestShowContent(any(), anyBoolean());
+        mMediator.getTabObserver().onInteractabilityChanged(mTab, true);
+        verify(mMockBottomSheetController, times(1)).requestShowContent(mBottomSheetContent, true);
+    }
+
     private void pressBack() {
         if (mBottomSheetContent.handleBackPress()) return;
 
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionMediator.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionMediator.java
index 316da7a..c104022a 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionMediator.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionMediator.java
@@ -581,7 +581,10 @@
                         : null);
 
         mBottomSheetContent.computeAndUpdateAccountListHeight();
-        showContent();
+        // When a user opens a page that invokes the FedCM API in a new tab, the tab will be hidden
+        // and we should not show the bottom sheet to avoid confusion.
+        mTab.addObserver(mTabObserver);
+        if (!mTab.isHidden()) showContent();
     }
 
     private void updateHeader() {
@@ -607,10 +610,15 @@
             if (mRegisteredObservers) return;
 
             mRegisteredObservers = true;
+            if (mHeaderType == HeaderType.SIGN_IN
+                    || mHeaderType == HeaderType.VERIFY
+                    || mHeaderType == HeaderType.VERIFY_AUTO_REAUTHN) {
+                mDelegate.onAccountsDisplayed();
+            }
             mBottomSheetController.addObserver(mBottomSheetObserver);
             KeyboardVisibilityDelegate.getInstance()
                     .addKeyboardVisibilityListener(mKeyboardVisibilityListener);
-            mTab.addObserver(mTabObserver);
+            if (!mTab.hasObserver(mTabObserver)) mTab.addObserver(mTabObserver);
         } else {
             onDismissed(IdentityRequestDialogDismissReason.OTHER);
         }
diff --git a/chrome/browser/ui/android/webid/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionComponent.java b/chrome/browser/ui/android/webid/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionComponent.java
index b0b44ebd..5cbb750 100644
--- a/chrome/browser/ui/android/webid/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionComponent.java
+++ b/chrome/browser/ui/android/webid/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionComponent.java
@@ -45,6 +45,9 @@
 
         /** Called on the opener when a modal dialog that it opened has been closed. */
         void onModalDialogClosed();
+
+        /** Called when the accounts UI is displayed. */
+        void onAccountsDisplayed();
     }
 
     /**
diff --git a/chrome/browser/ui/passwords/bubble_controllers/manage_passwords_bubble_controller.cc b/chrome/browser/ui/passwords/bubble_controllers/manage_passwords_bubble_controller.cc
index 18b334a2..f5c08dc 100644
--- a/chrome/browser/ui/passwords/bubble_controllers/manage_passwords_bubble_controller.cc
+++ b/chrome/browser/ui/passwords/bubble_controllers/manage_passwords_bubble_controller.cc
@@ -111,7 +111,7 @@
       &favicon_tracker_);
 }
 
-password_manager::SyncState
+password_manager::sync_util::SyncState
 ManagePasswordsBubbleController::GetPasswordSyncState() {
   const syncer::SyncService* sync_service =
       SyncServiceFactory::GetForProfile(GetProfile());
diff --git a/chrome/browser/ui/passwords/bubble_controllers/manage_passwords_bubble_controller.h b/chrome/browser/ui/passwords/bubble_controllers/manage_passwords_bubble_controller.h
index a30b3eb0..11401a9b 100644
--- a/chrome/browser/ui/passwords/bubble_controllers/manage_passwords_bubble_controller.h
+++ b/chrome/browser/ui/passwords/bubble_controllers/manage_passwords_bubble_controller.h
@@ -25,7 +25,10 @@
 
 namespace password_manager {
 class PasswordStoreInterface;
+
+namespace sync_util {
 enum class SyncState;
+}  // namespace sync_util
 }  // namespace password_manager
 
 // This controller provides data and actions for the ManagePasswordsView.
@@ -47,7 +50,7 @@
   void RequestFavicon(
       base::OnceCallback<void(const gfx::Image&)> favicon_ready_callback);
 
-  password_manager::SyncState GetPasswordSyncState();
+  password_manager::sync_util::SyncState GetPasswordSyncState();
 
   // Returns the email of current primary account. Returns empty string if no
   // account is signed in.
diff --git a/chrome/browser/ui/passwords/bubble_controllers/manage_passwords_bubble_controller_unittest.cc b/chrome/browser/ui/passwords/bubble_controllers/manage_passwords_bubble_controller_unittest.cc
index 7d3b4ad6..7f8b3b5 100644
--- a/chrome/browser/ui/passwords/bubble_controllers/manage_passwords_bubble_controller_unittest.cc
+++ b/chrome/browser/ui/passwords/bubble_controllers/manage_passwords_bubble_controller_unittest.cc
@@ -21,6 +21,7 @@
 #include "components/password_manager/core/browser/password_manager_client.h"
 #include "components/password_manager/core/browser/password_manager_test_utils.h"
 #include "components/password_manager/core/browser/password_store/mock_password_store_interface.h"
+#include "components/password_manager/core/browser/password_sync_util.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/signin/public/identity_manager/identity_test_utils.h"
 #include "components/sync/test/test_sync_service.h"
@@ -199,22 +200,23 @@
       /*types=*/syncer::UserSelectableTypeSet());
 
   EXPECT_EQ(controller()->GetPasswordSyncState(),
-            password_manager::SyncState::kNotSyncing);
+            password_manager::sync_util::SyncState::kNotActive);
 
   sync_service()->GetUserSettings()->SetSelectedTypes(
       /*sync_everything=*/false,
       /*types=*/{syncer::UserSelectableType::kPasswords});
-  EXPECT_EQ(
-      controller()->GetPasswordSyncState(),
-      password_manager::SyncState::kAccountPasswordsActiveNormalEncryption);
+  EXPECT_EQ(controller()->GetPasswordSyncState(),
+            password_manager::sync_util::SyncState::
+                kAccountPasswordsActiveNormalEncryption);
 
   sync_service()->SetHasSyncConsent(true);
   EXPECT_EQ(controller()->GetPasswordSyncState(),
-            password_manager::SyncState::kSyncingNormalEncryption);
+            password_manager::sync_util::SyncState::kSyncingNormalEncryption);
 
   sync_service()->SetIsUsingExplicitPassphrase(true);
-  EXPECT_EQ(controller()->GetPasswordSyncState(),
-            password_manager::SyncState::kSyncingWithCustomPassphrase);
+  EXPECT_EQ(
+      controller()->GetPasswordSyncState(),
+      password_manager::sync_util::SyncState::kSyncingWithCustomPassphrase);
 }
 
 TEST_F(ManagePasswordsBubbleControllerTest, ShouldGetPrimaryAccountEmail) {
diff --git a/chrome/browser/ui/passwords/bubble_controllers/save_update_bubble_controller.cc b/chrome/browser/ui/passwords/bubble_controllers/save_update_bubble_controller.cc
index 8eccf9e..453c09b 100644
--- a/chrome/browser/ui/passwords/bubble_controllers/save_update_bubble_controller.cc
+++ b/chrome/browser/ui/passwords/bubble_controllers/save_update_bubble_controller.cc
@@ -86,11 +86,12 @@
   CHECK(profile);
   const syncer::SyncService* sync_service =
       SyncServiceFactory::GetForProfile(profile);
-  password_manager::SyncState sync_state =
+  password_manager::sync_util::SyncState sync_state =
       password_manager::sync_util::GetPasswordSyncState(sync_service);
-  return sync_state == password_manager::SyncState::kSyncingNormalEncryption ||
-         sync_state ==
-             password_manager::SyncState::kSyncingWithCustomPassphrase;
+  return sync_state ==
+             password_manager::sync_util::SyncState::kSyncingNormalEncryption ||
+         sync_state == password_manager::sync_util::SyncState::
+                           kSyncingWithCustomPassphrase;
 }
 
 }  // namespace
diff --git a/chrome/browser/ui/views/constrained_web_dialog_delegate_views.cc b/chrome/browser/ui/views/constrained_web_dialog_delegate_views.cc
index dc0268df..1758dd17 100644
--- a/chrome/browser/ui/views/constrained_web_dialog_delegate_views.cc
+++ b/chrome/browser/ui/views/constrained_web_dialog_delegate_views.cc
@@ -453,6 +453,7 @@
     const ui::Accelerator& accelerator) {
   // Pressing ESC closes the dialog.
   DCHECK_EQ(ui::VKEY_ESCAPE, accelerator.key_code());
+  GetWebDialogDelegate()->OnDialogClosingFromKeyEvent();
   GetWidget()->CloseWithReason(views::Widget::ClosedReason::kEscKeyPressed);
   return true;
 }
diff --git a/chrome/browser/ui/views/controls/hover_button.cc b/chrome/browser/ui/views/controls/hover_button.cc
index 7d42a39..da6a071 100644
--- a/chrome/browser/ui/views/controls/hover_button.cc
+++ b/chrome/browser/ui/views/controls/hover_button.cc
@@ -137,9 +137,10 @@
 
   // Set the layout manager to ignore the ink_drop_container to ensure the ink
   // drop tracks the bounds of its parent.
+  ink_drop_container()->SetProperty(views::kViewIgnoredByLayoutKey, true);
+
   SetLayoutManager(std::make_unique<views::FlexLayout>())
-      ->SetCrossAxisAlignment(views::LayoutAlignment::kCenter)
-      .SetChildViewIgnoredByLayout(ink_drop_container(), true);
+      ->SetCrossAxisAlignment(views::LayoutAlignment::kCenter);
 
   // The vertical space that must exist on the top and the bottom of the item
   // to ensure the proper spacing is maintained between items when stacking
diff --git a/chrome/browser/ui/views/controls/rich_hover_button.cc b/chrome/browser/ui/views/controls/rich_hover_button.cc
index 669a618..64b3248 100644
--- a/chrome/browser/ui/views/controls/rich_hover_button.cc
+++ b/chrome/browser/ui/views/controls/rich_hover_button.cc
@@ -96,9 +96,9 @@
                    text_context, views::style::STYLE_PRIMARY));
 
   // TODO(pkasting): This class should subclass Button, not HoverButton.
-  table_layout->SetChildViewIgnoredByLayout(image_container_view(), true);
-  table_layout->SetChildViewIgnoredByLayout(label(), true);
-  table_layout->SetChildViewIgnoredByLayout(ink_drop_container(), true);
+  image_container_view()->SetProperty(views::kViewIgnoredByLayoutKey, true);
+  label()->SetProperty(views::kViewIgnoredByLayoutKey, true);
+  ink_drop_container()->SetProperty(views::kViewIgnoredByLayoutKey, true);
 
   AddChildView(CreateIconView(main_image_icon));
   auto title_label = std::make_unique<views::Label>();
diff --git a/chrome/browser/ui/views/crypto_module_password_dialog_view.cc b/chrome/browser/ui/views/crypto_module_password_dialog_view.cc
index 93ed6d0..174c5367 100644
--- a/chrome/browser/ui/views/crypto_module_password_dialog_view.cc
+++ b/chrome/browser/ui/views/crypto_module_password_dialog_view.cc
@@ -29,17 +29,17 @@
   SetButtonLabel(
       ui::DIALOG_BUTTON_OK,
       l10n_util::GetStringUTF16(IDS_CRYPTO_MODULE_AUTH_DIALOG_OK_BUTTON_LABEL));
-  SetAcceptCallback(base::BindOnce(
-      [](CryptoModulePasswordDialogView* dialog) {
-        std::move(dialog->callback_)
-            .Run(base::UTF16ToUTF8(dialog->password_entry_->GetText()));
-      },
-      base::Unretained(this)));
-  SetCancelCallback(base::BindOnce(
-      [](CryptoModulePasswordDialogView* dialog) {
-        std::move(dialog->callback_).Run(std::string());
-      },
-      base::Unretained(this)));
+  constexpr bool kAccepted = true;
+  constexpr bool kCancelled = false;
+  SetAcceptCallback(
+      base::BindOnce(&CryptoModulePasswordDialogView::DialogAcceptedOrCancelled,
+                     base::Unretained(this), kAccepted));
+  SetCancelCallback(
+      base::BindOnce(&CryptoModulePasswordDialogView::DialogAcceptedOrCancelled,
+                     base::Unretained(this), kCancelled));
+  SetCloseCallback(
+      base::BindOnce(&CryptoModulePasswordDialogView::DialogAcceptedOrCancelled,
+                     base::Unretained(this), kCancelled));
   SetModalType(ui::MODAL_TYPE_WINDOW);
   set_margins(ChromeLayoutProvider::Get()->GetDialogInsetsForContentType(
       views::DialogContentType::kText, views::DialogContentType::kControl));
@@ -129,6 +129,14 @@
   password_container->SetFlexForView(password_entry_, 1);
 }
 
+void CryptoModulePasswordDialogView::DialogAcceptedOrCancelled(bool accepted) {
+  CHECK(callback_);
+
+  std::string result =
+      accepted ? base::UTF16ToUTF8(password_entry_->GetText()) : std::string();
+  std::move(callback_).Run(result);
+}
+
 BEGIN_METADATA(CryptoModulePasswordDialogView)
 END_METADATA
 
diff --git a/chrome/browser/ui/views/crypto_module_password_dialog_view.h b/chrome/browser/ui/views/crypto_module_password_dialog_view.h
index 075125a..d541577 100644
--- a/chrome/browser/ui/views/crypto_module_password_dialog_view.h
+++ b/chrome/browser/ui/views/crypto_module_password_dialog_view.h
@@ -50,6 +50,8 @@
   bool HandleKeyEvent(views::Textfield* sender,
                       const ui::KeyEvent& keystroke) override;
 
+  void DialogAcceptedOrCancelled(bool accepted);
+
   // Initialize views and layout.
   void Init(const std::string& server,
             const std::string& slot_name,
diff --git a/chrome/browser/ui/views/crypto_module_password_dialog_view_unittest.cc b/chrome/browser/ui/views/crypto_module_password_dialog_view_unittest.cc
index 7e2db12..35c16b4 100644
--- a/chrome/browser/ui/views/crypto_module_password_dialog_view_unittest.cc
+++ b/chrome/browser/ui/views/crypto_module_password_dialog_view_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <string>
 
+#include "base/scoped_observation.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
 #include "chrome/browser/ui/crypto_module_password_dialog.h"
@@ -13,6 +14,8 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/views/controls/textfield/textfield.h"
 #include "ui/views/test/views_test_base.h"
+#include "ui/views/widget/widget_observer.h"
+#include "ui/views/window/dialog_client_view.h"
 
 using CryptoModulePasswordDialogViewTest = ChromeViewsTestBase;
 
@@ -51,3 +54,44 @@
   EXPECT_TRUE(callback_run);
   EXPECT_EQ("", password);
 }
+
+class WidgetCloseWaiter : public views::WidgetObserver {
+ public:
+  explicit WidgetCloseWaiter(views::Widget* widget) {
+    observation_.Observe(widget);
+  }
+  ~WidgetCloseWaiter() override {}
+
+  void Wait() { loop_.Run(); }
+
+  void OnWidgetDestroying(views::Widget* widget) override {
+    CHECK_EQ(widget, observation_.GetSource());
+    observation_.Reset();
+    loop_.Quit();
+  }
+
+ private:
+  base::ScopedObservation<views::Widget, views::WidgetObserver> observation_{
+      this};
+  base::RunLoop loop_;
+};
+
+TEST_F(CryptoModulePasswordDialogViewTest, EscapeRunsCallback) {
+  bool callback_run = false;
+  auto dialog = CreateCryptoDialog(base::BindLambdaForTesting(
+      [&](const std::string&) { callback_run = true; }));
+  auto* weak_dialog = dialog.get();
+
+  views::Widget* dialog_widget =
+      dialog->CreateDialogWidget(std::move(dialog), GetContext(), nullptr);
+
+  views::DialogClientView* dcv = weak_dialog->GetDialogClientView();
+  ASSERT_TRUE(dcv);
+
+  WidgetCloseWaiter waiter(dialog_widget);
+
+  dcv->AcceleratorPressed(ui::Accelerator(ui::VKEY_ESCAPE, 0));
+
+  waiter.Wait();
+  EXPECT_TRUE(callback_run);
+}
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
index 3efce5d..fc5eed4 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
@@ -371,34 +371,32 @@
   const int icon_label_spacing = ChromeLayoutProvider::Get()->GetDistanceMetric(
       views::DISTANCE_RELATED_LABEL_HORIZONTAL);
 
-  auto* layout = SetLayoutManager(std::make_unique<views::TableLayout>());
-  // Download Icon
-  layout->AddColumn(views::LayoutAlignment::kCenter,
-                    views::LayoutAlignment::kStart,
-                    views::TableLayout::kFixedSize,
-                    views::TableLayout::ColumnSize::kUsePreferred, 0, 0);
-  // Download name label (primary_label_)
-  layout->AddPaddingColumn(views::TableLayout::kFixedSize, icon_label_spacing)
+  SetLayoutManager(std::make_unique<views::TableLayout>())
+      // Download Icon
+      ->AddColumn(views::LayoutAlignment::kCenter,
+                  views::LayoutAlignment::kStart,
+                  views::TableLayout::kFixedSize,
+                  views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
+      // Download name label (primary_label_)
+      .AddPaddingColumn(views::TableLayout::kFixedSize, icon_label_spacing)
       .AddColumn(views::LayoutAlignment::kStart,
                  features::IsChromeRefresh2023()
                      ? views::LayoutAlignment::kCenter
                      : views::LayoutAlignment::kStart,
-                 1.0f, views::TableLayout::ColumnSize::kUsePreferred, 0, 0);
-  // Download Buttons: Cancel, Discard, Scan, Open Now, only one may be active
-  layout->AddColumn(views::LayoutAlignment::kCenter,
-                    views::LayoutAlignment::kStart,
-                    views::TableLayout::kFixedSize,
-                    views::TableLayout::ColumnSize::kUsePreferred, 0, 0);
+                 1.0f, views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
+      // Download Buttons: Cancel, Discard, Scan, Open Now, only one may be
+      // active
+      .AddColumn(views::LayoutAlignment::kCenter,
+                 views::LayoutAlignment::kStart, views::TableLayout::kFixedSize,
+                 views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
+      // Subpage icon
+      .AddColumn(views::LayoutAlignment::kCenter,
+                 views::LayoutAlignment::kStart, views::TableLayout::kFixedSize,
+                 views::TableLayout::ColumnSize::kUsePreferred, 0, 0)
+      // Three rows, one for name, one for status, and one for the progress bar.
+      .AddRows(3, 1.0f);
 
-  // Subpage icon
-  layout->AddColumn(views::LayoutAlignment::kCenter,
-                    views::LayoutAlignment::kStart,
-                    views::TableLayout::kFixedSize,
-                    views::TableLayout::ColumnSize::kUsePreferred, 0, 0);
-  // Three rows, one for name, one for status, and one for the progress bar.
-  layout->AddRows(3, 1.0f);
-
-  layout->SetChildViewIgnoredByLayout(inkdrop_container_, true);
+  inkdrop_container_->SetProperty(views::kViewIgnoredByLayoutKey, true);
 
   transparent_button_ =
       AddChildView(std::make_unique<DownloadBubbleTransparentButton>(
@@ -407,7 +405,7 @@
           this));
   transparent_button_->set_context_menu_controller(this);
   transparent_button_->SetTriggerableEventFlags(ui::EF_LEFT_MOUSE_BUTTON);
-  layout->SetChildViewIgnoredByLayout(transparent_button_, true);
+  transparent_button_->SetProperty(views::kViewIgnoredByLayoutKey, true);
 
   icon_ = AddChildView(std::make_unique<views::ImageView>());
   icon_->SetCanProcessEventsWithinSubtree(false);
@@ -465,7 +463,7 @@
   open_when_complete_action_ =
       AddQuickAction(DownloadCommands::OPEN_WHEN_COMPLETE);
   quick_action_holder_->SetVisible(false);
-  layout->SetChildViewIgnoredByLayout(quick_action_holder_, true);
+  quick_action_holder_->SetProperty(views::kViewIgnoredByLayoutKey, true);
   quick_action_holder_->SetBackground(
       views::CreateThemedSolidBackground(ui::kColorDialogBackground));
 
diff --git a/chrome/browser/ui/views/download/bubble/download_dialog_view.cc b/chrome/browser/ui/views/download/bubble/download_dialog_view.cc
index c5d6eda5..9a2b1a2f 100644
--- a/chrome/browser/ui/views/download/bubble/download_dialog_view.cc
+++ b/chrome/browser/ui/views/download/bubble/download_dialog_view.cc
@@ -75,9 +75,7 @@
     // Override the table layout from RichHoverButton, in order to control the
     // spacing/padding. Code below is copied from rich_hover_button.cc but with
     // padding columns rearranged.
-    views::TableLayout* table_layout =
-        SetLayoutManager(std::make_unique<views::TableLayout>());
-    table_layout
+    SetLayoutManager(std::make_unique<views::TableLayout>())
         // Column for |main_image_icon|.
         ->AddColumn(views::LayoutAlignment::kCenter,
                     views::LayoutAlignment::kCenter,
@@ -110,9 +108,9 @@
                      views::style::STYLE_PRIMARY));
 
     // TODO(pkasting): This class should subclass Button, not HoverButton.
-    table_layout->SetChildViewIgnoredByLayout(image_container_view(), true);
-    table_layout->SetChildViewIgnoredByLayout(label(), true);
-    table_layout->SetChildViewIgnoredByLayout(ink_drop_container(), true);
+    image_container_view()->SetProperty(views::kViewIgnoredByLayoutKey, true);
+    label()->SetProperty(views::kViewIgnoredByLayoutKey, true);
+    ink_drop_container()->SetProperty(views::kViewIgnoredByLayoutKey, true);
 
     DeprecatedLayoutImmediately();
   }
diff --git a/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc b/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc
index 5b6af8846..bc01fc83a 100644
--- a/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc
+++ b/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc
@@ -63,11 +63,6 @@
 // Time delay before the install button is enabled after initial display.
 int g_install_delay_in_ms = 500;
 
-// The name of the histogram that records decision made by user on the cloud
-// extension request dialog.
-constexpr char kCloudExtensionRequestMetricsName[] =
-    "Enterprise.CloudExtensionRequestDialogAction";
-
 // These values are logged to UMA. Entries should not be renumbered and numeric
 // values should never be reused. Please keep in sync with "BooleanSent" in
 // src/tools/metrics/histograms/enums.xml.
@@ -529,7 +524,6 @@
   // being uninstalled).
   extension_registry_observation_.Reset();
 
-  UpdateEnterpriseCloudExtensionRequestDialogActionHistogram(false);
   prompt_->OnDialogCanceled();
   std::move(done_callback_)
       .Run(ExtensionInstallPrompt::DoneCallbackPayload(
@@ -549,7 +543,6 @@
       ExtensionInstallPrompt::PromptType::EXTENSION_REQUEST_PROMPT;
   DCHECK(expect_justification == !!justification_view_);
 
-  UpdateEnterpriseCloudExtensionRequestDialogActionHistogram(true);
   prompt_->OnDialogAccepted();
 
   // Permissions are withheld at installation when the prompt specifies it and
@@ -728,20 +721,6 @@
   DialogModelChanged();
 }
 
-void ExtensionInstallDialogView::
-    UpdateEnterpriseCloudExtensionRequestDialogActionHistogram(
-        bool accepted) const {
-  if (prompt_->type() == ExtensionInstallPrompt::EXTENSION_REQUEST_PROMPT) {
-    if (accepted) {
-      base::UmaHistogramEnumeration(kCloudExtensionRequestMetricsName,
-                                    CloudExtensionRequestMetricEvent::kSent);
-    } else {
-      base::UmaHistogramEnumeration(kCloudExtensionRequestMetricsName,
-                                    CloudExtensionRequestMetricEvent::kNotSent);
-    }
-  }
-}
-
 BEGIN_METADATA(ExtensionInstallDialogView, views::BubbleDialogDelegateView)
 END_METADATA
 
diff --git a/chrome/browser/ui/views/extensions/extension_install_dialog_view.h b/chrome/browser/ui/views/extensions/extension_install_dialog_view.h
index b187df9..4884512 100644
--- a/chrome/browser/ui/views/extensions/extension_install_dialog_view.h
+++ b/chrome/browser/ui/views/extensions/extension_install_dialog_view.h
@@ -92,11 +92,6 @@
   // Enables the install button and updates the dialog buttons.
   void EnableInstallButton();
 
-  // Updates the histogram that holds cloud extension request accepted/aborted
-  // decision made by user on the specific prompt dialog.
-  void UpdateEnterpriseCloudExtensionRequestDialogActionHistogram(
-      bool accepted) const;
-
   raw_ptr<Profile> profile_;
   std::unique_ptr<ExtensionInstallPromptShowParams> show_params_;
   ExtensionInstallPrompt::DoneCallback done_callback_;
diff --git a/chrome/browser/ui/views/extensions/extension_install_dialog_view_browsertest.cc b/chrome/browser/ui/views/extensions/extension_install_dialog_view_browsertest.cc
index 7d228451..03176390 100644
--- a/chrome/browser/ui/views/extensions/extension_install_dialog_view_browsertest.cc
+++ b/chrome/browser/ui/views/extensions/extension_install_dialog_view_browsertest.cc
@@ -61,9 +61,6 @@
 
 namespace {
 
-constexpr char kCloudExtensionRequestMetricsName[] =
-    "Enterprise.CloudExtensionRequestDialogAction";
-
 void CloseAndWait(views::Widget* widget) {
   views::test::WidgetDestroyedWaiter waiter(widget);
   widget->Close();
@@ -778,10 +775,6 @@
     delegate_view->AcceptDialog();
     EXPECT_EQ(ExtensionInstallPrompt::Result::ACCEPTED, helper.result());
     EXPECT_EQ(std::string(), helper.justification());
-    histogram_tester.ExpectTotalCount(kCloudExtensionRequestMetricsName, 1);
-    histogram_tester.ExpectBucketCount(kCloudExtensionRequestMetricsName,
-                                       /*sent*/ 1,
-                                       /*expected_count*/ 1);
   }
   {
     // User presses cancel.
@@ -792,10 +785,6 @@
     delegate_view->CancelDialog();
     EXPECT_EQ(ExtensionInstallPrompt::Result::USER_CANCELED, helper.result());
     EXPECT_EQ(std::string(), helper.justification());
-    histogram_tester.ExpectTotalCount(kCloudExtensionRequestMetricsName, 2);
-    histogram_tester.ExpectBucketCount(kCloudExtensionRequestMetricsName,
-                                       /*not_sent*/ 0,
-                                       /*expected_count*/ 1);
   }
   {
     // Dialog is closed without the user explicitly choosing to proceed or
@@ -811,10 +800,6 @@
     // TODO(devlin): Should this be ABORTED?
     EXPECT_EQ(ExtensionInstallPrompt::Result::USER_CANCELED, helper.result());
     EXPECT_EQ(std::string(), helper.justification());
-    histogram_tester.ExpectTotalCount(kCloudExtensionRequestMetricsName, 3);
-    histogram_tester.ExpectBucketCount(kCloudExtensionRequestMetricsName,
-                                       /*not_sent*/ 0,
-                                       /*expected_count*/ 2);
   }
 }
 
diff --git a/chrome/browser/ui/views/frame/tab_strip_region_view.cc b/chrome/browser/ui/views/frame/tab_strip_region_view.cc
index 09d9d2c1..83a844b00 100644
--- a/chrome/browser/ui/views/frame/tab_strip_region_view.cc
+++ b/chrome/browser/ui/views/frame/tab_strip_region_view.cc
@@ -90,8 +90,8 @@
       this, views::kCascadingBackgroundColor,
       kColorTabBackgroundInactiveFrameInactive);
 
-  layout_manager_ = SetLayoutManager(std::make_unique<views::FlexLayout>());
-  layout_manager_->SetOrientation(views::LayoutOrientation::kHorizontal);
+  SetLayoutManager(std::make_unique<views::FlexLayout>())
+      ->SetOrientation(views::LayoutOrientation::kHorizontal);
 
   tab_strip_ = tab_strip.get();
   const Browser* browser = tab_strip_->GetBrowser();
@@ -113,7 +113,7 @@
 
     // Inset between the tabsearch and tabstrip should be reduced to account for
     // extra spacing.
-    layout_manager_->SetChildViewIgnoredByLayout(tab_search_container_, true);
+    tab_search_container_->SetProperty(views::kViewIgnoredByLayoutKey, true);
   }
 
   if (base::FeatureList::IsEnabled(features::kScrollableTabStrip)) {
@@ -499,7 +499,7 @@
       new_tab_button_->layer()->SetFillsBoundsOpaquely(false);
       // Inset between the tabstrip and new tab button should be reduced to
       // account for extra spacing.
-      layout_manager_->SetChildViewIgnoredByLayout(new_tab_button_, true);
+      new_tab_button_->SetProperty(views::kViewIgnoredByLayoutKey, true);
 
       tab_strip_right_margin = new_tab_button_->GetPreferredSize().width() +
                                GetLayoutConstant(TAB_STRIP_PADDING);
@@ -509,7 +509,7 @@
   std::optional<int> tab_strip_left_margin;
   if (tab_search_container_ && render_tab_search_before_tab_strip_) {
     // The `tab_search_container_` is being laid out manually.
-    CHECK(layout_manager_->IsChildViewIgnoredByLayout(tab_search_container_));
+    tab_search_container_->GetProperty(views::kViewIgnoredByLayoutKey);
 
     // Add a margin to the tab_strip_container_ to leave the correct amount of
     // space for the `tab_search_container_`.
diff --git a/chrome/browser/ui/views/frame/tab_strip_region_view.h b/chrome/browser/ui/views/frame/tab_strip_region_view.h
index e91047a..6951cbfc 100644
--- a/chrome/browser/ui/views/frame/tab_strip_region_view.h
+++ b/chrome/browser/ui/views/frame/tab_strip_region_view.h
@@ -13,7 +13,6 @@
 #include "ui/views/accessible_pane_view.h"
 
 namespace views {
-class FlexLayout;
 class Button;
 }
 
@@ -85,7 +84,6 @@
   static void ReportCaptionHitTestInReservedGrabHandleSpace(
       bool in_reserved_grab_handle_space);
 
-  views::FlexLayout* layout_manager_for_testing() { return layout_manager_; }
   views::View* GetTabStripContainerForTesting() { return tab_strip_container_; }
 
  private:
@@ -99,7 +97,6 @@
   // `render_tab_search_before_tab_strip_` is true.
   void UpdateTabStripMargin();
 
-  raw_ptr<views::FlexLayout, DanglingUntriaged> layout_manager_ = nullptr;
   raw_ptr<views::View, AcrossTasksDanglingUntriaged> tab_strip_container_ =
       nullptr;
   raw_ptr<views::View, DanglingUntriaged> reserved_grab_handle_space_ = nullptr;
diff --git a/chrome/browser/ui/views/passwords/manage_passwords_view.cc b/chrome/browser/ui/views/passwords/manage_passwords_view.cc
index 5d99802..7ce24bf 100644
--- a/chrome/browser/ui/views/passwords/manage_passwords_view.cc
+++ b/chrome/browser/ui/views/passwords/manage_passwords_view.cc
@@ -22,6 +22,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "components/password_manager/core/browser/password_manager_client.h"
 #include "components/password_manager/core/browser/password_manager_metrics_util.h"
+#include "components/password_manager/core/browser/password_sync_util.h"
 #include "components/password_manager/core/common/password_manager_constants.h"
 #include "components/vector_icons/vector_icons.h"
 #include "ui/base/interaction/element_identifier.h"
@@ -197,15 +198,15 @@
       base::Unretained(this));
 
   switch (controller_.GetPasswordSyncState()) {
-    case password_manager::SyncState::kNotSyncing:
+    case password_manager::sync_util::SyncState::kNotActive:
       return CreateGooglePasswordManagerLabel(
           /*text_message_id=*/
           IDS_PASSWORD_BUBBLES_FOOTER_SAVING_ON_DEVICE,
           /*link_message_id=*/
           IDS_PASSWORD_BUBBLES_PASSWORD_MANAGER_LINK_TEXT_SAVING_ON_DEVICE,
           open_password_manager_closure, views::style::CONTEXT_BUBBLE_FOOTER);
-    case password_manager::SyncState::kSyncingNormalEncryption:
-    case password_manager::SyncState::kSyncingWithCustomPassphrase:
+    case password_manager::sync_util::SyncState::kSyncingNormalEncryption:
+    case password_manager::sync_util::SyncState::kSyncingWithCustomPassphrase:
       return CreateGooglePasswordManagerLabel(
           /*text_message_id=*/
           IDS_PASSWORD_BUBBLES_FOOTER_SYNCED_TO_ACCOUNT,
@@ -213,7 +214,8 @@
           IDS_PASSWORD_BUBBLES_PASSWORD_MANAGER_LINK_TEXT_SYNCED_TO_ACCOUNT,
           controller_.GetPrimaryAccountEmail(), open_password_manager_closure,
           views::style::CONTEXT_BUBBLE_FOOTER);
-    case password_manager::SyncState::kAccountPasswordsActiveNormalEncryption:
+    case password_manager::sync_util::SyncState::
+        kAccountPasswordsActiveNormalEncryption:
       // Account store users have a special footer in the management bubble
       // since they might have a mix of synced and non-synced passwords.
       return CreateGooglePasswordManagerLabel(
@@ -222,7 +224,7 @@
           /*link_message_id=*/
           IDS_PASSWORD_BUBBLES_PASSWORD_MANAGER_LINK_TEXT_SYNCED_TO_ACCOUNT,
           open_password_manager_closure, views::style::CONTEXT_BUBBLE_FOOTER);
-    case password_manager::SyncState::
+    case password_manager::sync_util::SyncState::
         kAccountPasswordsActiveWithCustomPassphrase:
       // Unreachable on desktop platforms.
       NOTREACHED_NORETURN();
diff --git a/chrome/browser/ui/views/payments/payment_request_row_view.cc b/chrome/browser/ui/views/payments/payment_request_row_view.cc
index 2805281..e1bdab47 100644
--- a/chrome/browser/ui/views/payments/payment_request_row_view.cc
+++ b/chrome/browser/ui/views/payments/payment_request_row_view.cc
@@ -162,11 +162,9 @@
   if (GetClickable())
     SetHighlighted(true);
   View::OnFocus();
-  views::FocusRing* focus_ring = views::FocusRing::Get(this);
-  views::TableLayout* layout =
-      static_cast<views::TableLayout*>(GetLayoutManager());
-  if (focus_ring && layout)
-    layout->SetChildViewIgnoredByLayout(focus_ring, true);
+  if (views::FocusRing* focus_ring = views::FocusRing::Get(this)) {
+    focus_ring->SetProperty(views::kViewIgnoredByLayoutKey, true);
+  }
 }
 
 void PaymentRequestRowView::OnBlur() {
diff --git a/chrome/browser/ui/views/side_panel/lens/lens_unified_side_panel_view.cc b/chrome/browser/ui/views/side_panel/lens/lens_unified_side_panel_view.cc
index 3cd84fd71..3723b4ff 100644
--- a/chrome/browser/ui/views/side_panel/lens/lens_unified_side_panel_view.cc
+++ b/chrome/browser/ui/views/side_panel/lens/lens_unified_side_panel_view.cc
@@ -286,7 +286,8 @@
   if (lens::features::GetEnableContextMenuInLensSidePanel()) {
     // Use |OpenURL| so that we create a new tab rather than trying to open
     // this link in the side panel.
-    return browser_view_->browser()->OpenURL(params);
+    browser_view_->browser()->OpenURL(params);
+    return nullptr;
   } else {
     return content::WebContentsDelegate::OpenURLFromTab(source, params);
   }
@@ -310,12 +311,6 @@
     bool renderer_initiated) {
   content::OpenURLParams params(url, referrer, disposition, transition,
                                 renderer_initiated);
-  if (lens::features::GetEnableContextMenuInLensSidePanel() &&
-      started_from_context_menu) {
-    // Link clicks initiated from the context menu will be handled by
-    // |OpenURLFromTab|.
-    return;
-  }
 
   // If the navigation is initiated by the renderer process, we must set an
   // initiator origin.
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.cc b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
index 0d94365..4f4208ca 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller.cc
@@ -2831,7 +2831,9 @@
           ->browser();
   SavedTabGroupKeyedService* const saved_tab_group_service =
       SavedTabGroupServiceFactory::GetForProfile(browser->profile());
-  if (!saved_tab_group_service->model()->Contains(group_.value())) {
+
+  if (!saved_tab_group_service ||
+      !saved_tab_group_service->model()->Contains(group_.value())) {
     return;
   }
 
@@ -2854,6 +2856,10 @@
   SavedTabGroupKeyedService* const saved_tab_group_service =
       SavedTabGroupServiceFactory::GetForProfile(browser->profile());
 
+  if (!saved_tab_group_service) {
+    return;
+  }
+
   saved_tab_group_service->ResumeTrackingLocalTabGroup(
       paused_saved_group_id_.value(), group_.value());
   paused_saved_group_id_ = std::nullopt;
diff --git a/chrome/browser/ui/views/webauthn/webauthn_hover_button.cc b/chrome/browser/ui/views/webauthn/webauthn_hover_button.cc
index 7054810..c7e1bc2 100644
--- a/chrome/browser/ui/views/webauthn/webauthn_hover_button.cc
+++ b/chrome/browser/ui/views/webauthn/webauthn_hover_button.cc
@@ -61,7 +61,7 @@
   // used but must exist to keep things happy. This view should be refactored to
   // descend from views::Button directly.
   for (views::View* child : children()) {
-    layout->SetChildViewIgnoredByLayout(child, true);
+    child->SetProperty(views::kViewIgnoredByLayoutKey, true);
   }
 
   const int icon_padding = layout_provider->GetDistanceMetric(
diff --git a/chrome/browser/ui/views/webid/account_selection_view_base.h b/chrome/browser/ui/views/webid/account_selection_view_base.h
index 1903a1d3..a40bed7a 100644
--- a/chrome/browser/ui/views/webid/account_selection_view_base.h
+++ b/chrome/browser/ui/views/webid/account_selection_view_base.h
@@ -162,6 +162,9 @@
     // dialog.
     virtual void OnMoreDetails(const ui::Event& event) = 0;
 
+    // Called when the accounts UI is displayed.
+    virtual void OnAccountsDisplayed() = 0;
+
     // Called when IdentityProvider.close() is called from the renderer.
     virtual void CloseModalDialog() = 0;
   };
diff --git a/chrome/browser/ui/views/webid/fake_delegate.h b/chrome/browser/ui/views/webid/fake_delegate.h
index 4ce6897..4b74c38 100644
--- a/chrome/browser/ui/views/webid/fake_delegate.h
+++ b/chrome/browser/ui/views/webid/fake_delegate.h
@@ -24,6 +24,7 @@
   void OnLoginToIdP(const GURL& idp_config_url,
                     const GURL& idp_login_url) override {}
   void OnMoreDetails() override {}
+  void OnAccountsDisplayed() override {}
 
   // AccountSelectionView::Delegate
   gfx::NativeView GetNativeView() override;
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 094530d..1883e93 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
@@ -87,6 +87,10 @@
     return;
   }
 
+  accounts_displayed_callback_ =
+      base::BindOnce(&FedCmAccountSelectionView::OnAccountsDisplayed,
+                     weak_ptr_factory_.GetWeakPtr());
+
   idp_display_data_list_.clear();
 
   size_t accounts_size = 0u;
@@ -182,6 +186,9 @@
     if (is_web_contents_visible_) {
       input_protector_->VisibilityChanged(true);
       GetDialogWidget()->Show();
+      if (accounts_displayed_callback_) {
+        std::move(accounts_displayed_callback_).Run();
+      }
     }
   }
   // Else:
@@ -198,6 +205,10 @@
   }
 }
 
+void FedCmAccountSelectionView::OnAccountsDisplayed() {
+  delegate_->OnAccountsDisplayed();
+}
+
 void FedCmAccountSelectionView::ShowFailureDialog(
     const std::string& top_frame_etld_plus_one,
     const std::optional<std::string>& iframe_etld_plus_one,
@@ -368,6 +379,9 @@
 
   if (is_web_contents_visible_) {
     GetDialogWidget()->Show();
+    if (accounts_displayed_callback_) {
+      std::move(accounts_displayed_callback_).Run();
+    }
     GetDialogWidget()->widget_delegate()->SetCanActivate(true);
     // This will protect against potentially unintentional inputs that happen
     // right after the dialog becomes visible again.
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 91b39fd..334d95e 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
@@ -73,6 +73,8 @@
                        blink::mojom::RpMode rp_mode,
                        const content::IdentityProviderMetadata& idp_metadata,
                        const std::optional<TokenError>& error) override;
+  void OnAccountsDisplayed() override;
+
   void ShowUrl(LinkType link_type, const GURL& url) override;
   std::string GetTitle() const override;
   std::optional<std::string> GetSubtitle() const override;
@@ -253,6 +255,12 @@
   // is asked to verify phone number, change password, etc.
   base::OnceClosure show_accounts_dialog_callback_;
 
+  // Because the tab that shows the accounts dialog may be invisible initially,
+  // e.g. when a user opens a new tab, we'd delay showing the dialog until the
+  // tab becomes visible. This callback notifies the controller when the dialog
+  // is displayed to the user for the first time.
+  base::OnceClosure accounts_displayed_callback_;
+
   // If dialog has NOT been populated with accounts yet as a result of the IDP
   // sign-in flow and the IDP sign-in pop-up window has been closed, we use this
   // boolean to let the widget know it should unhide itself when the dialog is
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 54f7f22..0b6e85b 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
@@ -202,6 +202,7 @@
   void OnLoginToIdP(const GURL& idp_config_url,
                     const GURL& idp_login_url) override {}
   void OnMoreDetails() override {}
+  void OnAccountsDisplayed() override {}
   gfx::NativeView GetNativeView() override { return gfx::NativeView(); }
 
   content::WebContents* GetWebContents() override { return web_contents_; }
diff --git a/chrome/browser/ui/webid/account_selection_view.h b/chrome/browser/ui/webid/account_selection_view.h
index f4ce52f6..5f53fb79 100644
--- a/chrome/browser/ui/webid/account_selection_view.h
+++ b/chrome/browser/ui/webid/account_selection_view.h
@@ -33,6 +33,8 @@
     virtual void OnLoginToIdP(const GURL& idp_config_url,
                               const GURL& idp_login_url) = 0;
     virtual void OnMoreDetails() = 0;
+    // Informs the controller that the accounts dialog has been displayed.
+    virtual void OnAccountsDisplayed() = 0;
     // The web page view containing the focused field.
     virtual gfx::NativeView GetNativeView() = 0;
     // The WebContents for the page.
diff --git a/chrome/browser/ui/webid/identity_dialog_controller.cc b/chrome/browser/ui/webid/identity_dialog_controller.cc
index 75c5f2a5..233dc648 100644
--- a/chrome/browser/ui/webid/identity_dialog_controller.cc
+++ b/chrome/browser/ui/webid/identity_dialog_controller.cc
@@ -33,10 +33,12 @@
     bool show_auto_reauthn_checkbox,
     AccountSelectionCallback on_selected,
     LoginToIdPCallback on_add_account,
-    DismissCallback dismiss_callback) {
+    DismissCallback dismiss_callback,
+    AccountsDisplayedCallback accounts_displayed_callback) {
   on_account_selection_ = std::move(on_selected);
   on_login_ = std::move(on_add_account);
   on_dismiss_ = std::move(dismiss_callback);
+  on_accounts_displayed_ = std::move(accounts_displayed_callback);
   if (!account_view_)
     account_view_ = AccountSelectionView::Create(this);
   account_view_->Show(top_frame_for_display, iframe_for_display,
@@ -97,6 +99,10 @@
   std::move(on_more_details_).Run();
 }
 
+void IdentityDialogController::OnAccountsDisplayed() {
+  std::move(on_accounts_displayed_).Run();
+}
+
 void IdentityDialogController::ShowIdpSigninFailureDialog(
     base::OnceClosure user_notified_callback) {
   NOTIMPLEMENTED();
diff --git a/chrome/browser/ui/webid/identity_dialog_controller.h b/chrome/browser/ui/webid/identity_dialog_controller.h
index 2c58e521..0018676 100644
--- a/chrome/browser/ui/webid/identity_dialog_controller.h
+++ b/chrome/browser/ui/webid/identity_dialog_controller.h
@@ -48,7 +48,8 @@
       bool show_auto_reauthn_checkbox,
       AccountSelectionCallback on_selected,
       LoginToIdPCallback on_add_account,
-      DismissCallback dismiss_callback) override;
+      DismissCallback dismiss_callback,
+      AccountsDisplayedCallback accounts_displayed_callback) override;
   void ShowFailureDialog(const std::string& top_frame_for_display,
                          const std::optional<std::string>& iframe_for_display,
                          const std::string& idp_for_display,
@@ -85,6 +86,7 @@
   void OnLoginToIdP(const GURL& idp_config_url,
                     const GURL& idp_login_url) override;
   void OnMoreDetails() override;
+  void OnAccountsDisplayed() override;
   gfx::NativeView GetNativeView() override;
   content::WebContents* GetWebContents() override;
 
@@ -94,6 +96,7 @@
   DismissCallback on_dismiss_;
   LoginToIdPCallback on_login_;
   MoreDetailsCallback on_more_details_;
+  AccountsDisplayedCallback on_accounts_displayed_;
   raw_ptr<content::WebContents> rp_web_contents_;
 };
 
diff --git a/chrome/browser/ui/webui/policy/policy_test_ui_browsertest.cc b/chrome/browser/ui/webui/policy/policy_test_ui_browsertest.cc
index 1a597e03d..e27325a 100644
--- a/chrome/browser/ui/webui/policy/policy_test_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/policy/policy_test_ui_browsertest.cc
@@ -13,8 +13,10 @@
 #include "chrome/browser/policy/chrome_browser_policy_connector.h"
 #include "chrome/browser/policy/profile_policy_connector.h"
 #include "chrome/browser/policy/profile_policy_connector_builder.h"
+#include "chrome/browser/policy/schema_registry_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_impl.h"
+#include "chrome/browser/ui/webui/policy/policy_ui.h"
 #include "chrome/browser/ui/webui/policy/policy_ui_handler.h"
 #include "chrome/common/channel_info.h"
 #include "chrome/common/url_constants.h"
@@ -277,10 +279,10 @@
   std::unique_ptr<PolicyUIHandler> handler = SetUpHandler();
   const std::string jsonString =
       R"([
-      {"level": 0,"scope": 0,"source": 0,
-      "name": "AutofillAddressEnabled","value": false},
-      {"level": 1,"scope": 1,"source": 2,
-      "name": "CloudReportingEnabled","value": true}
+      {"level": 0,"scope": 0,"source": 0, "namespace": "chrome",
+       "name": "AutofillAddressEnabled","value": false},
+      {"level": 1,"scope": 1,"source": 2, "namespace": "chrome",
+       "name": "CloudReportingEnabled","value": true}
       ])";
   const std::string revertAppliedPoliciesButtonDisabledJs =
       R"(
@@ -633,6 +635,24 @@
 #endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
 
 namespace {
+
+const char kExtensionId[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+const char kExtensionSchemaJson[] = R"({
+  "type": "object",
+  "properties": {
+    "normal_boolean": {
+      "type": "boolean"
+    },
+    "normal_number": {
+      "type": "number"
+    },
+    "sensitive_number": {
+      "type": "number",
+      "sensitiveValue": true,
+    }
+  }
+})";
+
 class PolicyTestUITest : public PlatformBrowserTest {
  public:
   PolicyTestUITest() {
@@ -647,6 +667,15 @@
 
   void SetUpOnMainThread() override {
     PlatformBrowserTest::SetUpOnMainThread();
+
+    // Add a fake "extension" to the SchemaRegistry.
+    auto* registry = GetProfile()->GetPolicySchemaRegistryService()->registry();
+    std::string error;
+    registry->RegisterComponent(
+        policy::PolicyNamespace(policy::POLICY_DOMAIN_EXTENSIONS, kExtensionId),
+        policy::Schema::Parse(kExtensionSchemaJson, &error));
+    ASSERT_THAT(error, testing::IsEmpty());
+
     // Enable kPolicyTestPageEnabled policy.
     policy::PolicyMap policy_map;
     base::Value::List policy_list;
@@ -714,6 +743,7 @@
   }
 
  private:
+  policy::Schema extension_schema_;
   base::test::ScopedFeatureList scoped_feature_list_;
   testing::NiceMock<policy::MockConfigurationPolicyProvider> provider_;
 };
@@ -807,6 +837,138 @@
   EXPECT_EQ(content::EvalJs(web_contents(), getSourceValueJs), "sourceCloud");
 }
 
+IN_PROC_BROWSER_TEST_F(PolicyTestUITest, GetSchema) {
+  base::Value schema = PolicyUI::GetSchema(GetProfile());
+  ASSERT_NE(nullptr, schema.GetDict().FindDict(kExtensionId));
+  // The extension's schema should exclude "sensitive_number", because it's
+  // a sensitive policy.
+  ASSERT_EQ(base::Value::Dict()
+                .Set("normal_boolean", "boolean")
+                .Set("normal_number", "number"),
+            *schema.GetDict().FindDict(kExtensionId));
+}
+
+IN_PROC_BROWSER_TEST_F(PolicyTestUITest, TestExtensionPoliciesIncluded) {
+  if (!policy::utils::IsPolicyTestingEnabled(/*pref_service=*/nullptr,
+                                             chrome::GetChannel())) {
+    GTEST_SKIP() << "chrome://policy/test not allowed on this build.";
+  }
+
+  ASSERT_TRUE(content::NavigateToURL(web_contents(),
+                                     GURL(chrome::kChromeUIPolicyTestURL)));
+
+  base::Value schema = PolicyUI::GetSchema(GetProfile());
+  base::Value chrome_policy_names(base::Value::Type::LIST);
+  for (const auto [key, _] : *schema.GetDict().FindDict("chrome")) {
+    chrome_policy_names.GetList().Append(key);
+  }
+  base::Value extension_policy_names(base::Value::Type::LIST);
+  for (const auto [key, _] : *schema.GetDict().FindDict(kExtensionId)) {
+    extension_policy_names.GetList().Append(key);
+  }
+
+  // The namespace <select> should offer the Chrome policy domain, and the
+  // extension's namespace.
+  const std::string getNamespacesFromSelect =
+      R"(
+        Array.from(
+            document
+                .querySelector('policy-test-table')
+                .shadowRoot
+                .querySelector('policy-test-row')
+                .shadowRoot
+                .querySelector('.namespace')
+                .options)
+            .map(e => e.innerText)
+      )";
+  EXPECT_EQ(
+      base::Value(base::Value::List().Append("Chrome").Append(kExtensionId)),
+      content::EvalJs(web_contents(), getNamespacesFromSelect));
+
+  // Get list of available policies from the <datalist>. They should initially
+  // correspond to the Chrome policy domain.
+  const std::string getPolicyNamesFromDatalist =
+      R"(
+        Array.from(
+            document
+                .querySelector('policy-test-table')
+                .shadowRoot
+                .querySelector('policy-test-row')
+                .shadowRoot
+                .getElementById('policy-name-list')
+                .options)
+            .map(e => e.innerText)
+      )";
+  content::EvalJsResult policy_names =
+      content::EvalJs(web_contents(), getPolicyNamesFromDatalist);
+  EXPECT_EQ(chrome_policy_names, policy_names);
+
+  // Switch to the extension's namespace. This should change the <datalist>'s
+  // policy names.
+  std::string switchToExtensionNamespace =
+      R"(
+        const namespaceDropdown =
+            document
+                .querySelector('policy-test-table')
+                .shadowRoot
+                .querySelector('policy-test-row')
+                .shadowRoot
+                .querySelector('.namespace');
+        namespaceDropdown.value = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
+        namespaceDropdown.dispatchEvent(new CustomEvent('change'));
+      )";
+  std::ignore = content::EvalJs(web_contents(), switchToExtensionNamespace);
+  content::EvalJsResult policy_names2 =
+      content::EvalJs(web_contents(), getPolicyNamesFromDatalist);
+  EXPECT_EQ(extension_policy_names, policy_names2);
+}
+
+IN_PROC_BROWSER_TEST_F(PolicyTestUITest, TestSchemaChangeReflected) {
+  if (!policy::utils::IsPolicyTestingEnabled(/*pref_service=*/nullptr,
+                                             chrome::GetChannel())) {
+    GTEST_SKIP() << "chrome://policy/test not allowed on this build.";
+  }
+
+  ASSERT_TRUE(content::NavigateToURL(web_contents(),
+                                     GURL(chrome::kChromeUIPolicyTestURL)));
+
+  base::Value schema = PolicyUI::GetSchema(GetProfile());
+  base::Value chrome_policy_names(base::Value::Type::LIST);
+  for (const auto [key, _] : *schema.GetDict().FindDict("chrome")) {
+    chrome_policy_names.GetList().Append(key);
+  }
+  base::Value extension_policy_names(base::Value::Type::LIST);
+  for (const auto [key, _] : *schema.GetDict().FindDict(kExtensionId)) {
+    extension_policy_names.GetList().Append(key);
+  }
+
+  // The namespace <select> should offer the Chrome policy domain, and the
+  // extension's namespace.
+  const std::string getNamespacesFromSelect =
+      R"(
+        Array.from(
+            document
+                .querySelector('policy-test-table')
+                .shadowRoot
+                .querySelector('policy-test-row')
+                .shadowRoot
+                .querySelector('.namespace')
+                .options)
+            .map(e => e.innerText)
+      )";
+  EXPECT_EQ(
+      base::Value(base::Value::List().Append("Chrome").Append(kExtensionId)),
+      content::EvalJs(web_contents(), getNamespacesFromSelect));
+
+  // Delete the extension from the schema. It should disappear from the
+  // namespace <select>.
+  auto* registry = GetProfile()->GetPolicySchemaRegistryService()->registry();
+  registry->UnregisterComponent(
+      policy::PolicyNamespace(policy::POLICY_DOMAIN_EXTENSIONS, kExtensionId));
+  EXPECT_EQ(base::Value(base::Value::List().Append("Chrome")),
+            content::EvalJs(web_contents(), getNamespacesFromSelect));
+}
+
 IN_PROC_BROWSER_TEST_F(PolicyTestUITest, TestPolicyNameChangesInputType) {
   if (!policy::utils::IsPolicyTestingEnabled(/*pref_service=*/nullptr,
                                              chrome::GetChannel())) {
diff --git a/chrome/browser/ui/webui/policy/policy_ui.cc b/chrome/browser/ui/webui/policy/policy_ui.cc
index 6a97cda5..8e4ea33b 100644
--- a/chrome/browser/ui/webui/policy/policy_ui.cc
+++ b/chrome/browser/ui/webui/policy/policy_ui.cc
@@ -7,13 +7,16 @@
 
 #include <memory>
 
+#include "base/check.h"
 #include "base/json/json_string_value_serializer.h"
 #include "base/json/json_writer.h"
 #include "base/strings/stringprintf.h"
 #include "base/system/sys_info.h"
+#include "base/values.h"
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/enterprise/browser_management/management_service_factory.h"
+#include "chrome/browser/policy/schema_registry_service.h"
 #include "chrome/browser/policy/value_provider/chrome_policies_value_provider.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/policy/policy_ui_handler.h"
@@ -28,6 +31,7 @@
 #include "components/policy/core/common/policy_loader_common.h"
 #include "components/policy/core/common/policy_pref_names.h"
 #include "components/policy/core/common/policy_utils.h"
+#include "components/policy/core/common/schema_registry.h"
 #include "components/policy/policy_constants.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/strings/grit/components_strings.h"
@@ -167,14 +171,7 @@
   source->AddResourcePath("logs/", IDR_POLICY_LOGS_POLICY_LOGS_HTML);
   source->AddResourcePath("logs", IDR_POLICY_LOGS_POLICY_LOGS_HTML);
 
-  // Test page should only load if testing is enabled and the profile is not
-  // managed by cloud.
-  const bool allow_policy_test_page =
-      policy::utils::IsPolicyTestingEnabled(profile->GetPrefs(),
-                                            chrome::GetChannel()) &&
-      !policy::ManagementServiceFactory::GetForProfile(profile)
-           ->HasManagementAuthority(
-               policy::EnterpriseManagementAuthority::CLOUD);
+  const bool allow_policy_test_page = PolicyUI::ShouldLoadTestPage(profile);
   if (allow_policy_test_page) {
     // Localized strings for chrome://policy/test.
     static constexpr webui::LocalizedString kPolicyTestStrings[] = {
@@ -185,6 +182,7 @@
         {"testDesc", IDS_POLICY_TEST_DESC},
         {"testRevertAppliedPolicies", IDS_POLICY_TEST_REVERT},
         {"testClearPolicies", IDS_CLEAR},
+        {"testTableNamespace", IDS_POLICY_HEADER_NAMESPACE},
         {"testTableName", IDS_POLICY_HEADER_NAME},
         {"testTableSource", IDS_POLICY_HEADER_SOURCE},
         {"testTableScope", IDS_POLICY_TEST_TABLE_SCOPE},
@@ -204,25 +202,10 @@
     source->AddResourcePath("test/", IDR_POLICY_TEST_POLICY_TEST_HTML);
     source->AddResourcePath("test", IDR_POLICY_TEST_POLICY_TEST_HTML);
 
-    // Create a string policy_names_to_types_str mapping policy names to their
-    // input types.
-    policy::Schema chrome_schema =
-        policy::Schema::Wrap(policy::GetChromeSchemaData());
-    ChromePoliciesValueProvider value_provider(profile);
-    base::Value::List policy_names =
-        (*value_provider.GetNames().FindDict("chrome"))
-            .FindList("policyNames")
-            ->Clone();
-
-    policy_names.EraseIf([&](auto& policy) {
-      return policy::IsPolicyNameSensitive(policy.GetString());
-    });
-
-    std::string policy_names_to_types;
-    JSONStringValueSerializer serializer(&policy_names_to_types);
-    serializer.Serialize(
-        policy::utils::GetPolicyNameToTypeMapping(policy_names, chrome_schema));
-    source->AddString("policyNamesToTypes", policy_names_to_types);
+    std::string schema;
+    JSONStringValueSerializer serializer(&schema);
+    serializer.Serialize(PolicyUI::GetSchema(profile));
+    source->AddString("initialSchema", schema);
 
     // Strings for policy levels, scopes and sources.
     static constexpr webui::LocalizedString kPolicyTestTypes[] = {
@@ -267,3 +250,62 @@
   registry->RegisterBooleanPref(policy::policy_prefs::kPolicyTestPageEnabled,
                                 true);
 }
+
+// static
+bool PolicyUI::ShouldLoadTestPage(Profile* profile) {
+  // Test page should only load if testing is enabled and the profile is not
+  // managed by cloud.
+  return policy::utils::IsPolicyTestingEnabled(profile->GetPrefs(),
+                                               chrome::GetChannel()) &&
+         !policy::ManagementServiceFactory::GetForProfile(profile)
+              ->HasManagementAuthority(
+                  policy::EnterpriseManagementAuthority::CLOUD);
+}
+
+// static
+base::Value PolicyUI::GetSchema(Profile* profile) {
+  policy::SchemaRegistry* registry =
+      profile->GetPolicySchemaRegistryService()->registry();
+  static const policy::PolicyDomain kDomains[] = {
+      policy::POLICY_DOMAIN_CHROME,
+      policy::POLICY_DOMAIN_EXTENSIONS,
+  };
+  // Build a dictionary like this:
+  // {
+  //   "chrome": {
+  //     "PolicyOne": "number",
+  //     "PolicyTwo": "string",
+  //     ...
+  //   },
+  //   "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": {
+  //     "PolicyOne": "number",
+  //     ...
+  //   },
+  //   ...
+  // }
+  base::Value::Dict dict;
+  for (const auto domain : kDomains) {
+    const policy::ComponentMap* components =
+        registry->schema_map()->GetComponents(domain);
+    if (!components) {
+      continue;
+    }
+    for (const auto& [component_id, schema] : *components) {
+      DCHECK_EQ(schema.type(), base::Value::Type::DICT);
+      base::Value::List policy_names;
+      auto it = schema.GetPropertiesIterator();
+      for (; !it.IsAtEnd(); it.Advance()) {
+        if (it.schema().IsSensitiveValue() ||
+            policy::IsPolicyNameSensitive(it.key())) {
+          continue;
+        }
+        policy_names.Append(it.key());
+      }
+      // Use "chrome" instead of the empty string for the Chrome namespace,
+      // for better debuggability. Use the extension ID for other namespaces.
+      dict.Set(domain == policy::POLICY_DOMAIN_CHROME ? "chrome" : component_id,
+               policy::utils::GetPolicyNameToTypeMapping(policy_names, schema));
+    }
+  }
+  return base::Value(std::move(dict));
+}
diff --git a/chrome/browser/ui/webui/policy/policy_ui.h b/chrome/browser/ui/webui/policy/policy_ui.h
index aefde9cc..83d38ae 100644
--- a/chrome/browser/ui/webui/policy/policy_ui.h
+++ b/chrome/browser/ui/webui/policy/policy_ui.h
@@ -8,6 +8,8 @@
 #include "components/prefs/pref_registry_simple.h"
 #include "content/public/browser/web_ui_controller.h"
 
+class Profile;
+
 namespace content {
 class WebUI;
 }
@@ -23,6 +25,8 @@
   ~PolicyUI() override;
 
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
+  static bool ShouldLoadTestPage(Profile* profile);
+  static base::Value GetSchema(Profile* profile);
 };
 
 #endif  // CHROME_BROWSER_UI_WEBUI_POLICY_POLICY_UI_H_
diff --git a/chrome/browser/ui/webui/policy/policy_ui_handler.cc b/chrome/browser/ui/webui/policy/policy_ui_handler.cc
index 87c5e5b..a564461 100644
--- a/chrome/browser/ui/webui/policy/policy_ui_handler.cc
+++ b/chrome/browser/ui/webui/policy/policy_ui_handler.cc
@@ -19,6 +19,7 @@
 #include "base/functional/callback.h"
 #include "base/functional/callback_helpers.h"
 #include "base/i18n/time_formatting.h"
+#include "base/json/json_string_value_serializer.h"
 #include "base/json/json_writer.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
@@ -46,6 +47,7 @@
 #include "chrome/browser/policy/value_provider/value_provider_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/chrome_select_file_policy.h"
+#include "chrome/browser/ui/webui/policy/policy_ui.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/grit/branded_strings.h"
 #include "components/crx_file/id_util.h"
@@ -173,6 +175,10 @@
   policy_value_and_status_observation_.Observe(
       policy_value_and_status_aggregator_.get());
 
+  schema_registry_observation_.Observe(Profile::FromWebUI(web_ui())
+                                           ->GetPolicySchemaRegistryService()
+                                           ->registry());
+
   web_ui()->RegisterMessageCallback(
       "exportPoliciesJSON",
       base::BindRepeating(&PolicyUIHandler::HandleExportPoliciesJson,
@@ -231,6 +237,18 @@
   SendStatus();
 }
 
+void PolicyUIHandler::OnSchemaRegistryUpdated(bool has_new_schemas) {
+  SendSchema();
+}
+
+void PolicyUIHandler::SendSchema() {
+  Profile* profile = Profile::FromWebUI(web_ui());
+  if (!IsJavascriptAllowed() || !PolicyUI::ShouldLoadTestPage(profile)) {
+    return;
+  }
+  FireWebUIListener("schema-updated", PolicyUI::GetSchema(profile));
+}
+
 void PolicyUIHandler::HandleExportPoliciesJson(const base::Value::List& args) {
   export_to_json_count_ += 1;
   if (!IsJavascriptAllowed()) {
@@ -246,6 +264,7 @@
     const base::Value::List& args) {
   // Send initial policy values and status to UI page.
   AllowJavascript();
+  SendSchema();
   SendPolicies();
   SendStatus();
 }
diff --git a/chrome/browser/ui/webui/policy/policy_ui_handler.h b/chrome/browser/ui/webui/policy/policy_ui_handler.h
index c7f1dca4..5686b480 100644
--- a/chrome/browser/ui/webui/policy/policy_ui_handler.h
+++ b/chrome/browser/ui/webui/policy/policy_ui_handler.h
@@ -19,6 +19,7 @@
 #include "build/build_config.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/policy/policy_value_and_status_aggregator.h"
+#include "components/policy/core/common/schema_registry.h"
 #include "content/public/browser/web_ui_data_source.h"
 #include "content/public/browser/web_ui_message_handler.h"
 #include "extensions/buildflags/buildflags.h"
@@ -26,9 +27,9 @@
 class PrefChangeRegistrar;
 
 // The JavaScript message handler for the chrome://policy page.
-class PolicyUIHandler
-    : public content::WebUIMessageHandler,
-      public policy::PolicyValueAndStatusAggregator::Observer {
+class PolicyUIHandler : public content::WebUIMessageHandler,
+                        public policy::PolicyValueAndStatusAggregator::Observer,
+                        public policy::SchemaRegistry::Observer {
  public:
   PolicyUIHandler();
 
@@ -46,6 +47,9 @@
   // policy::PolicyValueAndStatusAggregator::Observer implementation.
   void OnPolicyValueAndStatusChanged() override;
 
+  // policy::SchemaRegistry::Observer implementation.
+  void OnSchemaRegistryUpdated(bool has_new_schemas) override;
+
   void set_web_ui_for_test(content::WebUI* web_ui) { set_web_ui(web_ui); }
 
  private:
@@ -76,6 +80,10 @@
   // separately.
   void SendPolicies();
 
+  // Send the current policy schema to the UI: the list of supported Chrome &
+  // extension policies, and their types.
+  void SendSchema();
+
   // Send the status of cloud policy to the UI. For each scope that has cloud
   // policy enabled (device and/or user), a dictionary containing status
   // information.
@@ -95,6 +103,9 @@
   base::ScopedObservation<policy::PolicyValueAndStatusAggregator,
                           policy::PolicyValueAndStatusAggregator::Observer>
       policy_value_and_status_observation_{this};
+  base::ScopedObservation<policy::SchemaRegistry,
+                          policy::SchemaRegistry::Observer>
+      schema_registry_observation_{this};
 
   std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_;
 
diff --git a/chrome/browser/upgrade_detector/upgrade_detector_impl.cc b/chrome/browser/upgrade_detector/upgrade_detector_impl.cc
index 66d768c0..7f989b1 100644
--- a/chrome/browser/upgrade_detector/upgrade_detector_impl.cc
+++ b/chrome/browser/upgrade_detector/upgrade_detector_impl.cc
@@ -12,8 +12,6 @@
 #include "base/build_time.h"
 #include "base/check_op.h"
 #include "base/command_line.h"
-#include "base/debug/alias.h"
-#include "base/debug/dump_without_crashing.h"
 #include "base/feature_list.h"
 #include "base/functional/bind.h"
 #include "base/no_destructor.h"
@@ -262,12 +260,8 @@
   CHECK(!build_date_.is_null());
 
   if (!simulating_outdated_ && is_network_time && build_date_ > current_time) {
-    base::Time build_date = build_date_;
-    base::debug::Alias(&current_time);
-    base::debug::Alias(&build_date);
-    // TODO(crbug.com/1407664): Once this is shown to no longer be hitting,
-    // change this to a NOTREACHED_NORETURN().
-    base::debug::DumpWithoutCrashing();
+    // Sometimes unexpected things happen with clocks; ignore these edge cases.
+    // See https://crbug.com/40062693 for related discussions.
     return;
   }
 
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index 541a969..ac3f974 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -270,6 +270,8 @@
     "web_app_icon_generator.h",
     "web_app_icon_manager.cc",
     "web_app_icon_manager.h",
+    "web_app_icon_operations.cc",
+    "web_app_icon_operations.h",
     "web_app_id_constants.h",
     "web_app_install_finalizer.cc",
     "web_app_install_finalizer.h",
diff --git a/chrome/browser/web_applications/commands/external_app_resolution_command.cc b/chrome/browser/web_applications/commands/external_app_resolution_command.cc
index 579c269f..2d82be0d 100644
--- a/chrome/browser/web_applications/commands/external_app_resolution_command.cc
+++ b/chrome/browser/web_applications/commands/external_app_resolution_command.cc
@@ -32,6 +32,7 @@
 #include "chrome/browser/web_applications/web_app_command_scheduler.h"
 #include "chrome/browser/web_applications/web_app_constants.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
+#include "chrome/browser/web_applications/web_app_icon_operations.h"
 #include "chrome/browser/web_applications/web_app_install_finalizer.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/browser/web_applications/web_app_install_utils.h"
diff --git a/chrome/browser/web_applications/commands/fetch_install_info_from_install_url_command.cc b/chrome/browser/web_applications/commands/fetch_install_info_from_install_url_command.cc
index 81a9ae8..35475f9 100644
--- a/chrome/browser/web_applications/commands/fetch_install_info_from_install_url_command.cc
+++ b/chrome/browser/web_applications/commands/fetch_install_info_from_install_url_command.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/web_applications/locks/shared_web_contents_lock.h"
 #include "chrome/browser/web_applications/web_app_command_manager.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
+#include "chrome/browser/web_applications/web_app_icon_operations.h"
 #include "chrome/browser/web_applications/web_app_install_utils.h"
 #include "chrome/browser/web_applications/web_contents/web_app_data_retriever.h"
 #include "chrome/browser/web_applications/web_contents/web_app_url_loader.h"
diff --git a/chrome/browser/web_applications/commands/fetch_manifest_and_install_command.cc b/chrome/browser/web_applications/commands/fetch_manifest_and_install_command.cc
index 8700262..9de0269 100644
--- a/chrome/browser/web_applications/commands/fetch_manifest_and_install_command.cc
+++ b/chrome/browser/web_applications/commands/fetch_manifest_and_install_command.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/web_applications/web_app_command_manager.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_icon_operations.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/browser/web_applications/web_app_install_utils.h"
 #include "chrome/browser/web_applications/web_app_utils.h"
diff --git a/chrome/browser/web_applications/commands/fetch_manifest_and_install_command_browsertest.cc b/chrome/browser/web_applications/commands/fetch_manifest_and_install_command_browsertest.cc
index 0d1f325..cc47f22 100644
--- a/chrome/browser/web_applications/commands/fetch_manifest_and_install_command_browsertest.cc
+++ b/chrome/browser/web_applications/commands/fetch_manifest_and_install_command_browsertest.cc
@@ -2,9 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <cstdlib>
 #include <memory>
 #include <utility>
 
+#include "base/numerics/safe_conversions.h"
+#include "base/ranges/algorithm.h"
 #include "base/run_loop.h"
 #include "base/test/bind.h"
 #include "base/test/test_future.h"
@@ -17,14 +20,17 @@
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "chrome/browser/ui/web_applications/web_app_controller_browsertest.h"
 #include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
+#include "chrome/browser/web_applications/test/web_app_icon_test_utils.h"
 #include "chrome/browser/web_applications/test/web_app_test_utils.h"
 #include "chrome/browser/web_applications/web_app.h"
 #include "chrome/browser/web_applications/web_app_command_scheduler.h"
+#include "chrome/browser/web_applications/web_app_icon_generator.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_registry_update.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/skia/include/core/SkColor.h"
 
 namespace web_app {
 
@@ -283,4 +289,84 @@
       app_view->toolbar()->custom_tab_bar()->IsShowingCloseButtonForTesting());
 }
 
+// Tests that when an SVG icon is passed in the manifest with a size of |any|,
+// the icon is downloaded correctly for the web app during installation.
+// Parameterized to work with SVG icons that have an intrinsic size (width and
+// height defined) as well as no intrinsic size.
+class FetchManifestAndInstallCommandTestWithSVG
+    : public FetchManifestAndInstallCommandTest,
+      public ::testing::WithParamInterface<bool> {
+ protected:
+  GURL GetSiteUrlBasedOnSVGParams() {
+    if (GetParam()) {
+      return https_server()->GetURL(
+          "/banners/"
+          "manifest_test_page.html?manifest=manifest_svg_icon_any.json");
+    } else {
+      return https_server()->GetURL(
+          "/banners/"
+          "manifest_test_page.html?manifest=manifest_svg_icon_no_intrinsic_"
+          "size.json");
+    }
+  }
+
+  // There is a minor loss in image quality during resizing that leads to colors
+  // being not equal for precision for 2-3 decimal places. This is a helper
+  // function to work around that behavior by verifying that the difference is
+  // within some error margin.
+  bool VerifyColorsMatchWithinErrorPrecision(SkColor read_color,
+                                             SkColor expected_color) {
+    bool colors_match = true;
+    std::array<float, 4> read_colors = SkColor4f::FromColor(read_color).array();
+    std::array<float, 4> expected_colors =
+        SkColor4f::FromColor(expected_color).array();
+    float precision = 0.02f;
+    for (int i = 0; i < 4; i++) {
+      colors_match =
+          colors_match &&
+          (std::abs(read_colors[i] - expected_colors[i]) < precision);
+    }
+
+    return colors_match;
+  }
+};
+
+IN_PROC_BROWSER_TEST_P(FetchManifestAndInstallCommandTestWithSVG,
+                       SuccessInstallSVGIcons) {
+  EXPECT_TRUE(NavigateAndAwaitInstallabilityCheck(
+      browser(), GetSiteUrlBasedOnSVGParams()));
+
+  base::RunLoop loop;
+  webapps::AppId installed_app_id;
+  provider().scheduler().FetchManifestAndInstall(
+      webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON,
+      browser()->tab_strip_model()->GetActiveWebContents()->GetWeakPtr(),
+      CreateDialogCallback(),
+      base::BindLambdaForTesting([&](const webapps::AppId& app_id,
+                                     webapps::InstallResultCode code) {
+        EXPECT_EQ(code, webapps::InstallResultCode::kSuccessNewInstall);
+        EXPECT_TRUE(provider().registrar_unsafe().IsLocallyInstalled(app_id));
+        installed_app_id = app_id;
+        loop.Quit();
+      }),
+      /*use_fallback=*/false);
+  loop.Run();
+
+  for (const int& icon_size : web_app::SizesToGenerate()) {
+    SkColor small_icon_color = IconManagerReadAppIconPixel(
+        provider().icon_manager(), installed_app_id, icon_size,
+        /*x=*/icon_size / 2, /*y=*/icon_size / 2);
+    EXPECT_TRUE(
+        VerifyColorsMatchWithinErrorPrecision(small_icon_color, SK_ColorRED));
+  }
+}
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         FetchManifestAndInstallCommandTestWithSVG,
+                         ::testing::Bool(),
+                         [](const testing::TestParamInfo<bool>& info) {
+                           return info.param ? "SVGIconIntrinsicSize"
+                                             : "SVGIconNoIntrinsicSize";
+                         });
+
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/commands/install_from_sync_command.cc b/chrome/browser/web_applications/commands/install_from_sync_command.cc
index e48405a..746ac7f 100644
--- a/chrome/browser/web_applications/commands/install_from_sync_command.cc
+++ b/chrome/browser/web_applications/commands/install_from_sync_command.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/web_applications/locks/shared_web_contents_with_app_lock.h"
 #include "chrome/browser/web_applications/web_app_command_manager.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
+#include "chrome/browser/web_applications/web_app_icon_operations.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/browser/web_applications/web_app_install_utils.h"
 #include "chrome/browser/web_applications/web_app_registry_update.h"
diff --git a/chrome/browser/web_applications/commands/install_preloaded_verified_app_command.cc b/chrome/browser/web_applications/commands/install_preloaded_verified_app_command.cc
index 9e1849f..8fdedbe 100644
--- a/chrome/browser/web_applications/commands/install_preloaded_verified_app_command.cc
+++ b/chrome/browser/web_applications/commands/install_preloaded_verified_app_command.cc
@@ -20,6 +20,7 @@
 #include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
 #include "chrome/browser/web_applications/web_app_command_manager.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
+#include "chrome/browser/web_applications/web_app_icon_operations.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/browser/web_applications/web_app_install_params.h"
 #include "chrome/browser/web_applications/web_app_install_utils.h"
diff --git a/chrome/browser/web_applications/commands/manifest_update_check_command.cc b/chrome/browser/web_applications/commands/manifest_update_check_command.cc
index 137d6cf..b434309 100644
--- a/chrome/browser/web_applications/commands/manifest_update_check_command.cc
+++ b/chrome/browser/web_applications/commands/manifest_update_check_command.cc
@@ -17,6 +17,7 @@
 #include "chrome/browser/web_applications/manifest_update_manager.h"
 #include "chrome/browser/web_applications/web_app.h"
 #include "chrome/browser/web_applications/web_app_helpers.h"
+#include "chrome/browser/web_applications/web_app_icon_operations.h"
 #include "chrome/browser/web_applications/web_app_install_utils.h"
 #include "chrome/browser/web_applications/web_app_registrar.h"
 #include "chrome/browser/web_applications/web_app_registry_update.h"
diff --git a/chrome/browser/web_applications/commands/manifest_update_check_command_unittest.cc b/chrome/browser/web_applications/commands/manifest_update_check_command_unittest.cc
index 6f25fe7c..336358d 100644
--- a/chrome/browser/web_applications/commands/manifest_update_check_command_unittest.cc
+++ b/chrome/browser/web_applications/commands/manifest_update_check_command_unittest.cc
@@ -25,6 +25,7 @@
 #include "chrome/browser/web_applications/web_app_icon_generator.h"
 #include "chrome/browser/web_applications/web_app_icon_manager.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
+#include "chrome/browser/web_applications/web_app_install_utils.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "chrome/browser/web_applications/web_app_ui_manager.h"
 #include "chrome/browser/web_applications/web_contents/web_app_icon_downloader.h"
@@ -40,6 +41,16 @@
 static const int kUnimportantIconSize1 = 4;
 static const int kUnimportantIconSize2 = 8;
 
+namespace {
+apps::FileHandlers CreateFileHandlersFromManifest(
+    const std::vector<blink::mojom::ManifestFileHandlerPtr>& file_handler,
+    const GURL& app_scope) {
+  WebAppInstallInfo web_app_info;
+  PopulateFileHandlerInfoFromManifest(file_handler, app_scope, &web_app_info);
+  return web_app_info.file_handlers;
+}
+}  // namespace
+
 class ManifestUpdateCheckUtilsTest : public testing::Test {
  public:
   ManifestUpdateCheckUtilsTest() = default;
diff --git a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_install_command_helper.cc b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_install_command_helper.cc
index b136aed..ca6be862 100644
--- a/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_install_command_helper.cc
+++ b/chrome/browser/web_applications/isolated_web_apps/isolated_web_app_install_command_helper.cc
@@ -31,6 +31,7 @@
 #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_validator.h"
 #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_version.h"
 #include "chrome/browser/web_applications/isolated_web_apps/pending_install_info.h"
+#include "chrome/browser/web_applications/web_app_icon_operations.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "chrome/browser/web_applications/web_app_install_utils.h"
 #include "chrome/browser/web_applications/web_app_utils.h"
diff --git a/chrome/browser/web_applications/web_app_icon_operations.cc b/chrome/browser/web_applications/web_app_icon_operations.cc
new file mode 100644
index 0000000..1acba43
--- /dev/null
+++ b/chrome/browser/web_applications/web_app_icon_operations.cc
@@ -0,0 +1,205 @@
+// 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/web_applications/web_app_icon_operations.h"
+
+#include <set>
+#include <vector>
+
+#include "base/containers/contains.h"
+#include "base/containers/extend.h"
+#include "base/containers/flat_set.h"
+#include "base/values.h"
+#include "chrome/browser/web_applications/web_app_icon_generator.h"
+#include "chrome/browser/web_applications/web_app_install_info.h"
+#include "chrome/browser/web_applications/web_app_install_utils.h"
+#include "components/services/app_service/public/cpp/icon_info.h"
+#include "ui/gfx/geometry/size.h"
+#include "url/gurl.h"
+
+namespace web_app {
+
+namespace {
+
+base::flat_set<GURL> GetAllIconUrlsForSizeAny(
+    base::flat_map<IconPurpose, GURL> icon_purpose_to_urls) {
+  base::flat_set<GURL> urls;
+  for (const auto& data : icon_purpose_to_urls) {
+    urls.insert(data.second);
+  }
+  return urls;
+}
+
+void PopulateIconUrlsForSizeAnyIfNeeded(
+    std::vector<IconUrlWithSize>& icon_vector,
+    base::flat_set<GURL> icon_urls_to_download_if_any,
+    SizeSet icon_sizes_found,
+    bool is_app_icon = false) {
+  std::set<SquareSizePx> sizes_to_generate = web_app::SizesToGenerate();
+
+  // There isn't a lot of use-cases where we need to account for the 16x16 icon
+  // except for the main app shortcut icon, which is why we also need to
+  // generate an extra size of 16x16 if not found in the manifest. This helps
+  // handle blurriness of icons to a certain extent because a "closer" size is
+  // found for very small sized icons instead of needing to resize a much larger
+  // icon. However, this is just a hacky fix, and will be tackled more
+  // efficiently once b/322428992 is started.
+  if (is_app_icon) {
+    sizes_to_generate.emplace(16);
+  }
+
+  for (const auto& url : icon_urls_to_download_if_any) {
+    for (const auto& width : sizes_to_generate) {
+      gfx::Size size_to_generate_in(width, width);
+
+      // Only generate SVG icons for those sizes that are not passed in
+      // explicitly via the manifest.
+      if (!icon_sizes_found.contains(size_to_generate_in)) {
+        icon_vector.push_back(
+            IconUrlWithSize::Create(url, size_to_generate_in));
+      }
+    }
+  }
+}
+
+std::vector<IconUrlWithSize> GetAppIconUrls(
+    const WebAppInstallInfo& web_app_info) {
+  std::vector<IconUrlWithSize> urls;
+
+  for (const apps::IconInfo& info : web_app_info.manifest_icons) {
+    urls.push_back(IconUrlWithSize::CreateForUnspecifiedSize(info.url));
+  }
+
+  PopulateIconUrlsForSizeAnyIfNeeded(
+      std::ref(urls),
+      GetAllIconUrlsForSizeAny(web_app_info.icons_with_size_any.manifest_icons),
+      web_app_info.icons_with_size_any.manifest_icon_provided_sizes,
+      /*is_app_icon=*/true);
+  return urls;
+}
+
+std::vector<IconUrlWithSize> GetShortcutMenuIcons(
+    const WebAppInstallInfo& web_app_info) {
+  std::vector<IconUrlWithSize> urls;
+  for (const WebAppShortcutsMenuItemInfo& shortcut :
+       web_app_info.shortcuts_menu_item_infos) {
+    for (IconPurpose purpose : kIconPurposes) {
+      for (const WebAppShortcutsMenuItemInfo::Icon& icon :
+           shortcut.GetShortcutIconInfosForPurpose(purpose)) {
+        urls.push_back(IconUrlWithSize::CreateForUnspecifiedSize(icon.url));
+      }
+    }
+  }
+
+  PopulateIconUrlsForSizeAnyIfNeeded(
+      std::ref(urls),
+      GetAllIconUrlsForSizeAny(
+          web_app_info.icons_with_size_any.shortcut_menu_icons),
+      web_app_info.icons_with_size_any.shortcut_menu_icons_provided_sizes);
+  return urls;
+}
+
+std::vector<IconUrlWithSize> GetFileHandlingIcons(
+    const WebAppInstallInfo& web_app_info) {
+  std::vector<IconUrlWithSize> urls;
+
+  for (const apps::FileHandler& file_handler : web_app_info.file_handlers) {
+    for (const apps::IconInfo& icon : file_handler.downloaded_icons) {
+      urls.push_back(IconUrlWithSize::CreateForUnspecifiedSize(icon.url));
+    }
+  }
+
+  PopulateIconUrlsForSizeAnyIfNeeded(
+      std::ref(urls),
+      GetAllIconUrlsForSizeAny(
+          web_app_info.icons_with_size_any.file_handling_icons),
+      web_app_info.icons_with_size_any.file_handling_icon_provided_sizes);
+  return urls;
+}
+
+std::vector<IconUrlWithSize> GetHomeTabIcons(
+    const WebAppInstallInfo& web_app_info) {
+  std::vector<IconUrlWithSize> urls;
+
+  if (!web_app::HomeTabIconsExistInTabStrip(web_app_info)) {
+    return urls;
+  }
+
+  const auto& home_tab = absl::get<blink::Manifest::HomeTabParams>(
+      web_app_info.tab_strip.value().home_tab);
+
+  for (const auto& icon : home_tab.icons) {
+    urls.push_back(IconUrlWithSize::CreateForUnspecifiedSize(icon.src));
+  }
+
+  PopulateIconUrlsForSizeAnyIfNeeded(
+      std::ref(urls),
+      GetAllIconUrlsForSizeAny(web_app_info.icons_with_size_any.home_tab_icons),
+      web_app_info.icons_with_size_any.home_tab_icon_provided_sizes);
+  return urls;
+}
+
+IconUrlSizeSet RemoveDuplicates(std::vector<IconUrlWithSize> from_urls) {
+  return IconUrlSizeSet{from_urls};
+}
+
+}  // namespace
+
+IconUrlWithSize IconUrlWithSize::CreateForUnspecifiedSize(
+    const GURL& icon_url) {
+  return IconUrlWithSize(icon_url, gfx::Size());
+}
+
+IconUrlWithSize IconUrlWithSize::Create(const GURL& icon_url,
+                                        const gfx::Size& size) {
+  CHECK(!size.IsZero());
+  return IconUrlWithSize(icon_url, size);
+}
+
+IconUrlWithSize::IconUrlWithSize(GURL url, gfx::Size size)
+    : url(url), size(size) {}
+
+IconUrlWithSize::~IconUrlWithSize() = default;
+
+IconUrlWithSize::IconUrlWithSize(const IconUrlWithSize& icon_urls_with_size) =
+    default;
+
+IconUrlWithSize::IconUrlWithSize(IconUrlWithSize&& icon_urls_with_size) =
+    default;
+
+IconUrlWithSize& IconUrlWithSize::operator=(
+    const IconUrlWithSize& icon_urls_with_size) = default;
+
+bool IconUrlWithSize::operator<(const IconUrlWithSize& rhs) const {
+  if (url != rhs.url) {
+    return url < rhs.url;
+  }
+
+  if (size.width() != rhs.size.width()) {
+    return size.width() < rhs.size.width();
+  }
+
+  return size.height() < rhs.size.height();
+}
+
+bool IconUrlWithSize::operator==(const IconUrlWithSize& rhs) const = default;
+
+std::string IconUrlWithSize::ToString() const {
+  return base::StringPrintf("icon_url: %s, size: %s", url.spec().c_str(),
+                            size.ToString().c_str());
+}
+
+IconUrlSizeSet GetValidIconUrlsToDownload(
+    const WebAppInstallInfo& web_app_info) {
+  std::vector<IconUrlWithSize> icon_urls_with_sizes;
+
+  base::Extend(icon_urls_with_sizes, GetAppIconUrls(web_app_info));
+  base::Extend(icon_urls_with_sizes, GetShortcutMenuIcons(web_app_info));
+  base::Extend(icon_urls_with_sizes, GetFileHandlingIcons(web_app_info));
+  base::Extend(icon_urls_with_sizes, GetHomeTabIcons(web_app_info));
+
+  return RemoveDuplicates(std::move(icon_urls_with_sizes));
+}
+
+}  // namespace web_app
diff --git a/chrome/browser/web_applications/web_app_icon_operations.h b/chrome/browser/web_applications/web_app_icon_operations.h
new file mode 100644
index 0000000..0f5b00a
--- /dev/null
+++ b/chrome/browser/web_applications/web_app_icon_operations.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_WEB_APPLICATIONS_WEB_APP_ICON_OPERATIONS_H_
+#define CHROME_BROWSER_WEB_APPLICATIONS_WEB_APP_ICON_OPERATIONS_H_
+
+#include <tuple>
+
+#include "base/containers/flat_set.h"
+#include "chrome/browser/web_applications/web_app_install_info.h"
+#include "ui/gfx/geometry/size.h"
+#include "url/gurl.h"
+
+namespace gfx {
+class Size;
+}  // namespace gfx
+
+namespace web_app {
+
+// A size of (0,0) means unspecified width & height. Use
+// CreateForUnspecifiedSize() to construct the icon metadata for those
+// use-cases, otherwise Create() will crash.
+struct IconUrlWithSize {
+  static IconUrlWithSize CreateForUnspecifiedSize(const GURL& icon_url);
+  static IconUrlWithSize Create(const GURL& icon_url, const gfx::Size& size);
+
+  IconUrlWithSize(GURL url, gfx::Size size);
+  ~IconUrlWithSize();
+  IconUrlWithSize(const IconUrlWithSize& icon_urls_with_size);
+  IconUrlWithSize(IconUrlWithSize&& icon_urls_with_size);
+  IconUrlWithSize& operator=(const IconUrlWithSize& icon_urls_with_size);
+
+  bool operator<(const IconUrlWithSize& rhs) const;
+  bool operator==(const IconUrlWithSize& rhs) const;
+  std::string ToString() const;
+
+  GURL url;
+  gfx::Size size;
+};
+
+using IconUrlSizeSet = base::flat_set<IconUrlWithSize>;
+
+// Form a list of icons and their sizes to download: Remove icons with invalid
+// urls.
+IconUrlSizeSet GetValidIconUrlsToDownload(
+    const WebAppInstallInfo& web_app_info);
+
+}  // namespace web_app
+
+#endif  // CHROME_BROWSER_WEB_APPLICATIONS_WEB_APP_ICON_OPERATIONS_H_
diff --git a/chrome/browser/web_applications/web_app_install_finalizer_unittest.cc b/chrome/browser/web_applications/web_app_install_finalizer_unittest.cc
index ceb4563..88430688 100644
--- a/chrome/browser/web_applications/web_app_install_finalizer_unittest.cc
+++ b/chrome/browser/web_applications/web_app_install_finalizer_unittest.cc
@@ -481,8 +481,8 @@
   // Update the app, adding a file handler.
   std::vector<blink::mojom::ManifestFileHandlerPtr> file_handlers;
   AddFileHandler(&file_handlers);
-  info->file_handlers =
-      CreateFileHandlersFromManifest(file_handlers, info->start_url);
+  PopulateFileHandlerInfoFromManifest(file_handlers, info->start_url,
+                                      info.get());
 
   base::test::TestFuture<const webapps::AppId&, webapps::InstallResultCode,
                          OsHooksErrors>
diff --git a/chrome/browser/web_applications/web_app_install_info.cc b/chrome/browser/web_applications/web_app_install_info.cc
index 91ffe94..d908950c1 100644
--- a/chrome/browser/web_applications/web_app_install_info.cc
+++ b/chrome/browser/web_applications/web_app_install_info.cc
@@ -250,6 +250,72 @@
   return base::Value(std::move(root));
 }
 
+// IconsWithSizeAny
+IconsWithSizeAny::IconsWithSizeAny() = default;
+IconsWithSizeAny::~IconsWithSizeAny() = default;
+IconsWithSizeAny::IconsWithSizeAny(
+    const IconsWithSizeAny& icons_with_size_any) = default;
+IconsWithSizeAny& IconsWithSizeAny::operator=(
+    const IconsWithSizeAny& icons_with_size_any) = default;
+bool IconsWithSizeAny::operator==(
+    const IconsWithSizeAny& icons_with_size_any) const = default;
+
+base::Value IconsWithSizeAny::ToDebugValue() const {
+  base::Value::Dict icons;
+  base::Value::Dict manifest;
+  for (const auto& icon : manifest_icons) {
+    manifest.Set(ConvertToString(icon.first), icon.second.spec());
+  }
+  icons.Set("manifest_icons", base::Value(std::move(manifest)));
+  base::Value::List manifest_sizes;
+  for (const auto& size : manifest_icon_provided_sizes) {
+    manifest_sizes.Append(size.ToString());
+  }
+  icons.Set("manifest_provided_sizes", base::Value(std::move(manifest_sizes)));
+
+  base::Value::Dict shortcut_icon;
+  for (const auto& shicon : shortcut_menu_icons) {
+    shortcut_icon.Set(ConvertToString(shicon.first), shicon.second.spec());
+  }
+  icons.Set("shortcut_icons", base::Value(std::move(shortcut_icon)));
+  base::Value::List shortcut_sizes;
+  for (const auto& size : shortcut_menu_icons_provided_sizes) {
+    shortcut_sizes.Append(size.ToString());
+  }
+  icons.Set("shortcut_menu_icons_provided_sizes",
+            base::Value(std::move(shortcut_sizes)));
+
+  base::Value::Dict file_handlers;
+  for (const auto& fhicon : file_handling_icons) {
+    file_handlers.Set(ConvertToString(fhicon.first), fhicon.second.spec());
+  }
+  icons.Set("file_handling_icons", base::Value(std::move(file_handlers)));
+  base::Value::List file_handling_sizes;
+  for (const auto& size : file_handling_icon_provided_sizes) {
+    file_handling_sizes.Append(size.ToString());
+  }
+  icons.Set("file_handling_icons_manifest_provided_sizes",
+            base::Value(std::move(file_handling_sizes)));
+
+  base::Value::Dict tab_icons;
+  for (const auto& thicon : home_tab_icons) {
+    tab_icons.Set(ConvertToString(thicon.first), thicon.second.spec());
+  }
+  icons.Set("home_tab_icons", base::Value(std::move(tab_icons)));
+  base::Value::List home_tab_sizes;
+  for (const auto& size : home_tab_icon_provided_sizes) {
+    home_tab_sizes.Append(size.ToString());
+  }
+  icons.Set("home_tab_icons_manifest_provided_sizes",
+            base::Value(std::move(home_tab_sizes)));
+
+  return base::Value(std::move(icons));
+}
+
+std::string IconsWithSizeAny::ToString() const {
+  return ToDebugValue().DebugString();
+}
+
 // WebAppInstallInfo
 
 // static
diff --git a/chrome/browser/web_applications/web_app_install_info.h b/chrome/browser/web_applications/web_app_install_info.h
index 0fe9ed0..e0b5658c 100644
--- a/chrome/browser/web_applications/web_app_install_info.h
+++ b/chrome/browser/web_applications/web_app_install_info.h
@@ -52,6 +52,19 @@
     kIconPurposes{IconPurpose::ANY, IconPurpose::MONOCHROME,
                   IconPurpose::MASKABLE};
 
+struct SizeComparator {
+  constexpr bool operator()(const gfx::Size& left,
+                            const gfx::Size& right) const {
+    if (left.height() != right.height()) {
+      return left.height() < right.height();
+    }
+
+    return left.width() < right.width();
+  }
+};
+
+using SizeSet = base::flat_set<gfx::Size, SizeComparator>;
+
 apps::IconInfo::Purpose ManifestPurposeToIconInfoPurpose(
     IconPurpose manifest_purpose);
 
@@ -176,6 +189,32 @@
   IconSizes downloaded_icon_sizes{};
 };
 
+struct IconsWithSizeAny {
+  IconsWithSizeAny();
+  ~IconsWithSizeAny();
+  IconsWithSizeAny(const IconsWithSizeAny& icons_with_size_any);
+  IconsWithSizeAny& operator=(const IconsWithSizeAny& icons_with_size_any);
+  bool operator==(const IconsWithSizeAny& icons_with_size_any) const;
+
+  base::Value ToDebugValue() const;
+  std::string ToString() const;
+
+  // 4 different maps are needed to keep track of the icons since there is no
+  // guarantee that the icons specified for each component that uses icons (like
+  // file handlers) will exist at the higher level `icons` entry for the
+  // manifest.
+  // A single GURL is stored per IconPurpose since only the last available icon
+  // needs to be considered as per the manifest spec.
+  base::flat_map<IconPurpose, GURL> manifest_icons;
+  SizeSet manifest_icon_provided_sizes;
+  base::flat_map<IconPurpose, GURL> shortcut_menu_icons;
+  SizeSet shortcut_menu_icons_provided_sizes;
+  base::flat_map<IconPurpose, GURL> file_handling_icons;
+  SizeSet file_handling_icon_provided_sizes;
+  base::flat_map<IconPurpose, GURL> home_tab_icons;
+  SizeSet home_tab_icon_provided_sizes;
+};
+
 // Structure used when installing a web page as an app.
 struct WebAppInstallInfo {
   enum MobileCapable {
@@ -401,6 +440,8 @@
   // web apps.
   std::optional<GeneratedIconFix> generated_icon_fix;
 
+  IconsWithSizeAny icons_with_size_any;
+
  private:
   // Used this method in Clone() method. Use Clone() to deep copy explicitly.
   WebAppInstallInfo(const WebAppInstallInfo& other);
diff --git a/chrome/browser/web_applications/web_app_install_utils.cc b/chrome/browser/web_applications/web_app_install_utils.cc
index 08c0090..4e8ec73 100644
--- a/chrome/browser/web_applications/web_app_install_utils.cc
+++ b/chrome/browser/web_applications/web_app_install_utils.cc
@@ -89,27 +89,6 @@
 constexpr SquareSizePx kMaxIconSize =
     webapps::InstallableEvaluator::kMaximumIconSizeInPx;
 
-// Returns whether the home tab icons exist.
-bool HomeTabIconsExistInTabStrip(const WebAppInstallInfo* web_app_info) {
-  if (!web_app_info->tab_strip.has_value()) {
-    return false;
-  }
-
-  if (!absl::holds_alternative<blink::Manifest::HomeTabParams>(
-          web_app_info->tab_strip.value().home_tab)) {
-    return false;
-  }
-
-  const auto& home_tab = absl::get<blink::Manifest::HomeTabParams>(
-      web_app_info->tab_strip.value().home_tab);
-
-  if (home_tab.icons.empty()) {
-    return false;
-  }
-
-  return true;
-}
-
 // Append non-empty square icons from |icons_map| onto the |square_icons| list.
 void AddSquareIconsFromMap(std::vector<SkBitmap>* square_icons,
                            const IconsMap& icons_map) {
@@ -154,8 +133,9 @@
 
 // Populate |web_app_info|'s shortcuts_menu_item_infos vector using the
 // blink::Manifest's shortcuts vector.
-std::vector<WebAppShortcutsMenuItemInfo> ToWebAppShortcutsMenuItemInfos(
-    const std::vector<blink::Manifest::ShortcutItem>& shortcuts) {
+void PopulateWebAppShortcutsMenuItemInfos(
+    const std::vector<blink::Manifest::ShortcutItem>& shortcuts,
+    WebAppInstallInfo* web_app_info) {
   std::vector<WebAppShortcutsMenuItemInfo> web_app_shortcut_infos;
   web_app_shortcut_infos.reserve(shortcuts.size());
   int num_shortcut_icons = 0;
@@ -178,6 +158,12 @@
 
         WebAppShortcutsMenuItemInfo::Icon info;
 
+        if (base::Contains(icon.sizes, gfx::Size()) &&
+            icon.src.spec().find(".svg") != std::string::npos) {
+          web_app_info->icons_with_size_any.shortcut_menu_icons[purpose] =
+              icon.src;
+        }
+
         // Filter out non-square or too large icons.
         auto valid_size_it =
             base::ranges::find_if(icon.sizes, [](const gfx::Size& size) {
@@ -190,6 +176,19 @@
         // sizes into account.
         info.square_size_px = valid_size_it->width();
 
+        // Keep track of the sizes passed in via the manifest which will be
+        // later used to compute how many SVG icons of size:any we need to
+        // download.
+        if (!web_app_info->icons_with_size_any.shortcut_menu_icons.empty()) {
+          for (const auto& icon_size : icon.sizes) {
+            if (icon_size == gfx::Size()) {
+              continue;
+            }
+            web_app_info->icons_with_size_any.shortcut_menu_icons_provided_sizes
+                .emplace(icon_size);
+          }
+        }
+
         DCHECK_LE(num_shortcut_icons, kMaxIcons);
         if (num_shortcut_icons < kMaxIcons) {
           info.url = icon.src;
@@ -209,7 +208,7 @@
     web_app_shortcut_infos.push_back(std::move(shortcut_info));
   }
 
-  return web_app_shortcut_infos;
+  web_app_info->shortcuts_menu_item_infos = std::move(web_app_shortcut_infos);
 }
 
 std::vector<SquareSizePx> GetSquareSizePxs(
@@ -376,14 +375,28 @@
       for (const auto& icon :
            shortcut.GetShortcutIconInfosForPurpose(purpose)) {
         auto it = icons_map.find(icon.url);
+        // Resize bitmaps only if the icon size is non-zero. The alternative
+        // happens mostly for icons with a size of ANY passed in via the
+        // manifest, for which we do not need to resize since it is downloaded
+        // in all sizes..
         if (it != icons_map.end()) {
-          std::set<SquareSizePx> sizes_to_generate;
-          sizes_to_generate.emplace(icon.square_size_px);
-          SizeToBitmap resized_bitmaps(
-              ConstrainBitmapsToSizes(it->second, sizes_to_generate));
+          if (icon.square_size_px != 0) {
+            std::set<SquareSizePx> sizes_to_generate;
+            sizes_to_generate.emplace(icon.square_size_px);
+            SizeToBitmap resized_bitmaps(
+                ConstrainBitmapsToSizes(it->second, sizes_to_generate));
 
-          // Don't overwrite as a shortcut item could have multiple icon urls.
-          bitmaps.insert(resized_bitmaps.begin(), resized_bitmaps.end());
+            // Don't overwrite as a shortcut item could have multiple icon urls.
+            bitmaps.insert(resized_bitmaps.begin(), resized_bitmaps.end());
+          } else {
+            // For icons that do not need resizing, pass in the bitmaps as it
+            // is, barring ones that have no intrinsic size.
+            for (const auto& bitmap : it->second) {
+              if (!bitmap.empty()) {
+                bitmaps[bitmap.width()] = bitmap;
+              }
+            }
+          }
         }
       }
       shortcut_icon_bitmaps.SetBitmapsForPurpose(purpose, std::move(bitmaps));
@@ -423,9 +436,11 @@
         continue;
 
       for (const SkBitmap& bitmap : downloaded_bitmaps_for_url->second) {
-        // Filter out non-square or too large icons.
-        if (bitmap.width() != bitmap.height() || bitmap.width() > kMaxIconSize)
+        // Filter out bitmaps that are empty, non-square or are too large.
+        if (bitmap.empty() || bitmap.width() != bitmap.height() ||
+            bitmap.width() > kMaxIconSize) {
           continue;
+        }
 
         // Add the size to the FileHandler icon metadata.
         apps::IconInfo icon_info_with_size(icon_info_without_size);
@@ -447,7 +462,7 @@
 void PopulateHomeTabIcons(WebAppInstallInfo* web_app_info,
                           const IconsMap& icons_map,
                           IconsMap& other_icon_bitmaps) {
-  if (!HomeTabIconsExistInTabStrip(web_app_info)) {
+  if (!HomeTabIconsExistInTabStrip(*web_app_info)) {
     return;
   }
 
@@ -469,8 +484,9 @@
     }
 
     for (const SkBitmap& bitmap : downloaded_bitmaps_for_url->second) {
-      // Filter out non-square or too large icons
-      if (bitmap.width() != bitmap.height() || bitmap.width() > kMaxIconSize) {
+      // Filter out bitmaps that are empty, non-square or are too large.
+      if (bitmap.empty() || bitmap.width() != bitmap.height() ||
+          bitmap.width() > kMaxIconSize) {
         continue;
       }
 
@@ -510,49 +526,11 @@
 
 }  // namespace
 
-IconUrlWithSize IconUrlWithSize::CreateForUnspecifiedSize(
-    const GURL& icon_url) {
-  return IconUrlWithSize(icon_url, gfx::Size());
-}
-
-IconUrlWithSize IconUrlWithSize::Create(const GURL& icon_url,
-                                        const gfx::Size& size) {
-  CHECK(!size.IsZero());
-  return IconUrlWithSize(icon_url, size);
-}
-
-IconUrlWithSize::IconUrlWithSize(GURL url, gfx::Size size)
-    : url(url), size(size) {}
-
-IconUrlWithSize::~IconUrlWithSize() = default;
-
-IconUrlWithSize::IconUrlWithSize(const IconUrlWithSize& icon_urls_with_size) =
-    default;
-
-IconUrlWithSize::IconUrlWithSize(IconUrlWithSize&& icon_urls_with_size) =
-    default;
-
-IconUrlWithSize& IconUrlWithSize::operator=(
-    const IconUrlWithSize& icon_urls_with_size) = default;
-
-bool IconUrlWithSize::operator<(const IconUrlWithSize& rhs) const {
-  if (url != rhs.url) {
-    return url < rhs.url;
-  }
-
-  if (size.width() != rhs.size.width()) {
-    return size.width() < rhs.size.width();
-  }
-
-  return size.height() < rhs.size.height();
-}
-
-bool IconUrlWithSize::operator==(const IconUrlWithSize& rhs) const = default;
-
-apps::FileHandlers CreateFileHandlersFromManifest(
+void PopulateFileHandlerInfoFromManifest(
     const std::vector<blink::mojom::ManifestFileHandlerPtr>&
         manifest_file_handlers,
-    const GURL& app_scope) {
+    const GURL& app_scope,
+    WebAppInstallInfo* web_app_info) {
   apps::FileHandlers web_app_file_handlers;
 
   for (const auto& manifest_file_handler : manifest_file_handlers) {
@@ -580,23 +558,45 @@
           icon_info.url = image_resource.src;
           icon_info.purpose =
               ManifestPurposeToIconInfoPurpose(manifest_purpose);
+          if (base::Contains(image_resource.sizes, gfx::Size()) &&
+              image_resource.src.spec().find(".svg") != std::string::npos) {
+            web_app_info->icons_with_size_any
+                .file_handling_icons[manifest_purpose] = image_resource.src;
+          }
+
           web_app_file_handler.downloaded_icons.push_back(std::move(icon_info));
           // The list will be pruned and the sizes will be filled in when images
           // are actually downloaded.
         }
+
+        // Keep track of the sizes passed in via the manifest which will be
+        // later used to compute how many SVG icons of size:any we need to
+        // download.
+        if (!web_app_info->icons_with_size_any.file_handling_icons.empty()) {
+          for (const auto& icon_size : image_resource.sizes) {
+            if (icon_size == gfx::Size()) {
+              continue;
+            }
+            web_app_info->icons_with_size_any.file_handling_icon_provided_sizes
+                .emplace(icon_size);
+          }
+        }
       }
     }
 
     web_app_file_handlers.push_back(std::move(web_app_file_handler));
   }
 
-  return web_app_file_handlers;
+  web_app_info->file_handlers = std::move(web_app_file_handlers);
 }
 
-// Create the WebAppInstallInfo icons list *outside* of |web_app_info|, so
-// that we can decide later whether or not to replace the existing icons.
-std::vector<apps::IconInfo> CreateWebAppInstallInfoIcons(
-    const std::vector<blink::Manifest::ImageResource> icons) {
+// Construct a list of icons from the parsed icons field of the manifest
+// *outside* of |web_app_info|, and update the current web_app_info if found.
+// If any icons are correctly specified in the manifest, they take precedence
+// over any we picked up from web page metadata.
+void UpdateWebAppInstallInfoIconsFromManifestIfNeeded(
+    const std::vector<blink::Manifest::ImageResource> icons,
+    WebAppInstallInfo* web_app_info) {
   std::vector<apps::IconInfo> web_app_icons;
   for (const auto& icon : icons) {
     // An icon's purpose vector should never be empty (the manifest parser
@@ -607,6 +607,11 @@
       apps::IconInfo info;
 
       if (!icon.sizes.empty()) {
+        if (base::Contains(icon.sizes, gfx::Size()) &&
+            icon.src.spec().find(".svg") != std::string::npos) {
+          web_app_info->icons_with_size_any.manifest_icons[purpose] = icon.src;
+        }
+
         // Filter out non-square or too large icons.
         auto valid_size =
             base::ranges::find_if(icon.sizes, [](const gfx::Size& size) {
@@ -616,6 +621,7 @@
         if (valid_size == icon.sizes.end()) {
           continue;
         }
+
         // TODO(https://crbug.com/1071308): Take the declared icon density and
         // sizes into account.
         info.square_size_px = valid_size->width();
@@ -627,11 +633,36 @@
 
       // Limit the number of icons we store on the user's machine.
       if (web_app_icons.size() == kMaxIcons) {
-        return web_app_icons;
+        break;
       }
     }
+
+    // Keep track of the sizes passed in via the manifest which will be
+    // later used to compute how many SVG icons of size:any we need to
+    // download.
+    // This is handled outside the loop above to reduce the number of iterations
+    // so that purpose and size metadata is parsed sequentially one after the
+    // other.
+    if (!web_app_info->icons_with_size_any.manifest_icons.empty()) {
+      for (const auto& icon_size : icon.sizes) {
+        if (icon_size == gfx::Size()) {
+          continue;
+        }
+        web_app_info->icons_with_size_any.manifest_icon_provided_sizes.emplace(
+            icon_size);
+      }
+    }
+
+    if (web_app_icons.size() == kMaxIcons) {
+      break;
+    }
   }
-  return web_app_icons;
+
+  // If any icons have been found from the manifest, set them inside the
+  // |web_app_info|.
+  if (!web_app_icons.empty()) {
+    web_app_info->manifest_icons = std::move(web_app_icons);
+  }
 }
 
 // Create the WebAppInstallInfo icons list *outside* of |web_app_info|, so
@@ -639,15 +670,23 @@
 // home tab icons.
 // Icons are replaced if we filter out icons that are too large or non-square
 // which limits the number of icons.
-std::vector<blink::Manifest::ImageResource> FilterWebAppHomeTabIcons(
-    const std::vector<blink::Manifest::ImageResource>& icons) {
+void PopulateHomeTabIconsFromHomeTabManifestParams(
+    WebAppInstallInfo* web_app_info) {
+  auto& home_tab = absl::get<blink::Manifest::HomeTabParams>(
+      web_app_info->tab_strip->home_tab);
   std::vector<blink::Manifest::ImageResource> home_tab_icons;
-  for (const auto& icon : icons) {
+  for (const auto& icon : home_tab.icons) {
     // An icon's purpose vector should never be empty (the manifest parser
     // should have added ANY if there was no purpose specified in the manifest).
     DCHECK(!icon.purpose.empty());
 
     if (!icon.sizes.empty()) {
+      if (base::Contains(icon.sizes, gfx::Size()) &&
+          icon.src.spec().find(".svg") != std::string::npos) {
+        for (const auto& purpose : icon.purpose) {
+          web_app_info->icons_with_size_any.home_tab_icons[purpose] = icon.src;
+        }
+      }
       // Filter out non-square or too large icons.
       auto valid_size =
           base::ranges::find_if(icon.sizes, [](const gfx::Size& size) {
@@ -657,6 +696,19 @@
       if (valid_size == icon.sizes.end()) {
         continue;
       }
+
+      // Keep track of the sizes passed in via the manifest which will be
+      // later used to compute how many SVG icons of size:any we need to
+      // download.
+      if (!web_app_info->icons_with_size_any.home_tab_icons.empty()) {
+        for (const auto& icon_size : icon.sizes) {
+          if (icon_size == gfx::Size()) {
+            continue;
+          }
+          web_app_info->icons_with_size_any.home_tab_icon_provided_sizes
+              .emplace(icon_size);
+        }
+      }
     }
 
     home_tab_icons.push_back(std::move(icon));
@@ -666,7 +718,9 @@
       break;
     }
   }
-  return home_tab_icons;
+
+  home_tab.icons = std::move(home_tab_icons);
+  web_app_info->tab_strip->home_tab = home_tab;
 }
 
 void UpdateWebAppInfoFromManifest(const blink::mojom::Manifest& manifest,
@@ -731,19 +785,12 @@
   if (!manifest.display_override.empty())
     web_app_info->display_override = manifest.display_override;
 
-  // Create the WebAppInstallInfo icons list *outside* of |web_app_info|, so
-  // that we can decide later whether or not to replace the existing icons.
-  std::vector<apps::IconInfo> web_app_icons =
-      CreateWebAppInstallInfoIcons(manifest.icons);
-
-  // If any icons are correctly specified in the manifest, they take precedence
-  // over any we picked up from web page metadata.
-  if (!web_app_icons.empty())
-    web_app_info->manifest_icons = std::move(web_app_icons);
+  UpdateWebAppInstallInfoIconsFromManifestIfNeeded(manifest.icons,
+                                                   web_app_info);
 
   // TODO(crbug.com/1218210): Confirm incoming icons to write to web_app_info.
-  web_app_info->file_handlers = CreateFileHandlersFromManifest(
-      manifest.file_handlers, web_app_info->scope);
+  PopulateFileHandlerInfoFromManifest(manifest.file_handlers,
+                                      web_app_info->scope, web_app_info);
 
   web_app_info->share_target = ToWebAppShareTarget(manifest.share_target);
 
@@ -772,8 +819,7 @@
   }
 
   DCHECK(web_app_info->shortcuts_menu_item_infos.empty());
-  web_app_info->shortcuts_menu_item_infos =
-      ToWebAppShortcutsMenuItemInfos(manifest.shortcuts);
+  PopulateWebAppShortcutsMenuItemInfos(manifest.shortcuts, web_app_info);
 
   web_app_info->capture_links = manifest.capture_links;
 
@@ -801,12 +847,8 @@
 
   web_app_info->tab_strip = manifest.tab_strip;
 
-  if (HomeTabIconsExistInTabStrip(web_app_info)) {
-    auto& home_tab = absl::get<blink::Manifest::HomeTabParams>(
-        web_app_info->tab_strip->home_tab);
-
-    home_tab.icons = FilterWebAppHomeTabIcons(home_tab.icons);
-    web_app_info->tab_strip->home_tab = home_tab;
+  if (HomeTabIconsExistInTabStrip(*web_app_info)) {
+    PopulateHomeTabIconsFromHomeTabManifestParams(web_app_info);
   }
 }
 
@@ -818,92 +860,6 @@
   return info;
 }
 
-namespace {
-
-std::vector<IconUrlWithSize> GetAppIconUrls(
-    const WebAppInstallInfo& web_app_info) {
-  std::vector<IconUrlWithSize> urls;
-
-  for (const apps::IconInfo& info : web_app_info.manifest_icons) {
-    urls.emplace_back(IconUrlWithSize::CreateForUnspecifiedSize(info.url));
-  }
-
-  return urls;
-}
-
-std::vector<IconUrlWithSize> GetShortcutIcons(
-    const WebAppInstallInfo& web_app_info) {
-  std::vector<IconUrlWithSize> urls;
-  for (const WebAppShortcutsMenuItemInfo& shortcut :
-       web_app_info.shortcuts_menu_item_infos) {
-    for (IconPurpose purpose : kIconPurposes) {
-      for (const WebAppShortcutsMenuItemInfo::Icon& icon :
-           shortcut.GetShortcutIconInfosForPurpose(purpose)) {
-        urls.emplace_back(IconUrlWithSize::CreateForUnspecifiedSize(icon.url));
-      }
-    }
-  }
-
-  return urls;
-}
-
-std::vector<IconUrlWithSize> GetFileHandlingIcons(
-    const WebAppInstallInfo& web_app_info) {
-  std::vector<IconUrlWithSize> urls;
-
-  for (const apps::FileHandler& file_handler : web_app_info.file_handlers) {
-    for (const apps::IconInfo& icon : file_handler.downloaded_icons) {
-      urls.emplace_back(IconUrlWithSize::CreateForUnspecifiedSize(icon.url));
-    }
-  }
-
-  return urls;
-}
-
-std::vector<IconUrlWithSize> GetHomeTabIcons(
-    const WebAppInstallInfo& web_app_info) {
-  std::vector<IconUrlWithSize> urls;
-
-  if (!HomeTabIconsExistInTabStrip(&web_app_info)) {
-    return urls;
-  }
-
-  const auto& home_tab = absl::get<blink::Manifest::HomeTabParams>(
-      web_app_info.tab_strip.value().home_tab);
-
-  for (const auto& icon : home_tab.icons) {
-    urls.emplace_back(IconUrlWithSize::CreateForUnspecifiedSize(icon.src));
-  }
-
-  return urls;
-}
-
-IconUrlSizeSet RemoveDuplicates(std::vector<IconUrlWithSize> from_urls) {
-  return IconUrlSizeSet{from_urls};
-}
-
-void RemoveInvalidUrls(std::vector<IconUrlWithSize>& urls) {
-  std::erase_if(urls, [](const IconUrlWithSize& url_with_size) {
-    return !url_with_size.url.is_valid();
-  });
-}
-
-}  // namespace
-
-IconUrlSizeSet GetValidIconUrlsToDownload(
-    const WebAppInstallInfo& web_app_info) {
-  std::vector<IconUrlWithSize> icon_urls;
-
-  base::Extend(icon_urls, GetAppIconUrls(web_app_info));
-  base::Extend(icon_urls, GetShortcutIcons(web_app_info));
-  base::Extend(icon_urls, GetFileHandlingIcons(web_app_info));
-  base::Extend(icon_urls, GetHomeTabIcons(web_app_info));
-
-  RemoveInvalidUrls(std::ref(icon_urls));
-
-  return RemoveDuplicates(std::move(icon_urls));
-}
-
 void PopulateOtherIcons(WebAppInstallInfo* web_app_info,
                         const IconsMap& icons_map) {
   IconsMap& other_icon_bitmaps = web_app_info->other_icon_bitmaps;
@@ -1393,4 +1349,24 @@
 #endif
 }
 
+bool HomeTabIconsExistInTabStrip(const WebAppInstallInfo& web_app_info) {
+  if (!web_app_info.tab_strip.has_value()) {
+    return false;
+  }
+
+  if (!absl::holds_alternative<blink::Manifest::HomeTabParams>(
+          web_app_info.tab_strip.value().home_tab)) {
+    return false;
+  }
+
+  const auto& home_tab = absl::get<blink::Manifest::HomeTabParams>(
+      web_app_info.tab_strip.value().home_tab);
+
+  if (home_tab.icons.empty()) {
+    return false;
+  }
+
+  return true;
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/web_app_install_utils.h b/chrome/browser/web_applications/web_app_install_utils.h
index c1ea340e..52bd435 100644
--- a/chrome/browser/web_applications/web_app_install_utils.h
+++ b/chrome/browser/web_applications/web_app_install_utils.h
@@ -12,6 +12,7 @@
 #include "base/strings/string_piece.h"
 #include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
 #include "chrome/browser/web_applications/web_app_constants.h"
+#include "chrome/browser/web_applications/web_app_icon_operations.h"
 #include "chrome/browser/web_applications/web_app_install_finalizer.h"
 #include "chrome/browser/web_applications/web_app_install_info.h"
 #include "components/services/app_service/public/cpp/file_handler.h"
@@ -35,10 +36,6 @@
 enum class WebappUninstallSource;
 }  // namespace webapps
 
-namespace gfx {
-class Size;
-}  // namespace gfx
-
 namespace web_app {
 
 class WebApp;
@@ -51,27 +48,10 @@
   kUnknown,
 };
 
-// A size of (0,0) means unspecified width & height. Use
-// CreateForUnspecifiedSize() to construct the icon metadata for those
-// use-cases, otherwise Create() will crash.
-struct IconUrlWithSize {
-  static IconUrlWithSize CreateForUnspecifiedSize(const GURL& icon_url);
-  static IconUrlWithSize Create(const GURL& icon_url, const gfx::Size& size);
-
-  IconUrlWithSize(GURL url, gfx::Size size);
-  ~IconUrlWithSize();
-  IconUrlWithSize(const IconUrlWithSize& icon_urls_with_size);
-  IconUrlWithSize(IconUrlWithSize&& icon_urls_with_size);
-  IconUrlWithSize& operator=(const IconUrlWithSize& icon_urls_with_size);
-
-  bool operator<(const IconUrlWithSize& rhs) const;
-  bool operator==(const IconUrlWithSize& rhs) const;
-
-  GURL url;
-  gfx::Size size;
-};
-
-using IconUrlSizeSet = base::flat_set<IconUrlWithSize>;
+// A map of |IconUrlWithSize| to http status results. `http_status_code` is
+// never 0.
+using DownloadedIconsHttpResults =
+    base::flat_map<IconUrlWithSize, int /*http_status_code*/>;
 
 // A map of |IconUrlWithSize| to http status results. `http_status_code` is
 // never 0.
@@ -79,10 +59,11 @@
     base::flat_map<IconUrlWithSize, int /*http_status_code*/>;
 
 // Converts from the manifest type to the Chrome type.
-apps::FileHandlers CreateFileHandlersFromManifest(
+void PopulateFileHandlerInfoFromManifest(
     const std::vector<blink::mojom::ManifestFileHandlerPtr>&
         manifest_file_handlers,
-    const GURL& app_scope);
+    const GURL& app_scope,
+    WebAppInstallInfo* web_app_info);
 
 // Update the given WebAppInstallInfo with information from the manifest.
 // Will sanitise the manifest fields to be suitable for installation to prevent
@@ -96,11 +77,6 @@
     const blink::mojom::Manifest& manifest,
     const GURL& manifest_url);
 
-// Form a list of icons and their sizes to download: Remove icons with invalid
-// urls.
-IconUrlSizeSet GetValidIconUrlsToDownload(
-    const WebAppInstallInfo& web_app_info);
-
 // Populate non-product icons in WebAppInstallInfo using the IconsMap. This
 // currently covers shortcut item icons and file handler icons. It ignores
 // icons that might have already existed in `web_app_info`.
@@ -182,6 +158,10 @@
 void ApplyParamsToFinalizeOptions(
     const WebAppInstallParams& install_params,
     WebAppInstallFinalizer::FinalizeOptions& options);
+
+// Returns whether the home tab icons exist.
+bool HomeTabIconsExistInTabStrip(const WebAppInstallInfo& web_app_info);
+
 }  // namespace web_app
 
 #endif  // CHROME_BROWSER_WEB_APPLICATIONS_WEB_APP_INSTALL_UTILS_H_
diff --git a/chrome/browser/web_applications/web_app_install_utils_unittest.cc b/chrome/browser/web_applications/web_app_install_utils_unittest.cc
index 68e114400..8589d9e9 100644
--- a/chrome/browser/web_applications/web_app_install_utils_unittest.cc
+++ b/chrome/browser/web_applications/web_app_install_utils_unittest.cc
@@ -1218,6 +1218,143 @@
   EXPECT_EQ(1U, web_app_info.other_icon_bitmaps.size());
 }
 
+// Tests proper parsing of ManifestImageResource icons from the manifest into
+// |icons_with_size_any| based on the absence of a size parameter.
+TEST(WebAppInstallUtils, PopulateAnyIconsCorrectlyManifestParsingSVGOnly) {
+  WebAppFileHandlerManager::SetIconsSupportedByOsForTesting(/*value=*/true);
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures({blink::features::kFileHandlingIcons}, {});
+
+  WebAppInstallInfo web_app_info;
+  // Generate expected data structure for |icons_with_size_any|.
+  IconsWithSizeAny expected_icon_metadata;
+
+  const GURL manifest_icon_no_size_url(
+      "https://www.example.com/manifest_image_no_size.svg");
+  const GURL manifest_icon_size_url(
+      "https://www.example.com/manifest_image_size.svg");
+  const GURL file_handling_no_size_url(
+      "https://www.example.com/file_handling_no_size.svg");
+  const GURL file_handling_size_url(
+      "https://www.example.com/file_handling_size.png");
+  const GURL shortcut_icon_no_size_url(
+      "https://www.example.com/shortcut_menu_icon_no_size.svg");
+  const GURL shortcut_icon_size_url(
+      "https://www.example.com/shortcut_menu_icon_size.svg");
+  const GURL tab_strip_icon_no_size_url(
+      "https://www.example.com/tab_strip_icon_no_size.svg");
+  const GURL tab_strip_icon_size_url(
+      "https://www.example.com/tab_strip_icon_size.jpg");
+
+  blink::mojom::Manifest manifest;
+
+  // Sample manifest icons, one with a size specified, one without.
+  blink::Manifest::ImageResource manifest_icon_no_size;
+  manifest_icon_no_size.src = manifest_icon_no_size_url;
+  manifest_icon_no_size.sizes = {{0, 0}, {196, 196}};
+  manifest_icon_no_size.purpose = {
+      blink::mojom::ManifestImageResource_Purpose::ANY,
+      blink::mojom::ManifestImageResource_Purpose::MONOCHROME};
+  manifest.icons.push_back(std::move(manifest_icon_no_size));
+
+  // Set up the expected icon metadata for manifest icons.
+  expected_icon_metadata.manifest_icons[IconPurpose::ANY] =
+      manifest_icon_no_size_url;
+  expected_icon_metadata.manifest_icons[IconPurpose::MONOCHROME] =
+      manifest_icon_no_size_url;
+  expected_icon_metadata.manifest_icon_provided_sizes.emplace(196, 196);
+
+  blink::Manifest::ImageResource manifest_icon_size;
+  manifest_icon_size.src = manifest_icon_size_url;
+  manifest_icon_size.sizes = {{24, 24}};
+  manifest_icon_size.purpose = {
+      blink::mojom::ManifestImageResource_Purpose::ANY};
+  manifest.icons.push_back(std::move(manifest_icon_size));
+  expected_icon_metadata.manifest_icon_provided_sizes.emplace(24, 24);
+
+  // Sample file handler with no size specified for icons.
+  auto file_handler = blink::mojom::ManifestFileHandler::New();
+  file_handler->action = GURL("https://www.action.com/");
+  file_handler->name = u"Random File";
+  file_handler->accept[u"text/html"] = {u".html"};
+
+  blink::Manifest::ImageResource file_handling_icon_no_size;
+  file_handling_icon_no_size.src = file_handling_no_size_url;
+  file_handling_icon_no_size.sizes = {{0, 0}};
+  file_handling_icon_no_size.purpose = {
+      blink::mojom::ManifestImageResource_Purpose::MASKABLE};
+  file_handler->icons.push_back(std::move(file_handling_icon_no_size));
+
+  // Set up the expected icon metadata for file handling icons.
+  expected_icon_metadata.file_handling_icons[IconPurpose::MASKABLE] =
+      file_handling_no_size_url;
+
+  blink::Manifest::ImageResource file_handling_icon_size;
+  file_handling_icon_size.src = file_handling_size_url;
+  file_handling_icon_size.sizes = {{64, 64}};
+  file_handling_icon_size.purpose = {
+      blink::mojom::ManifestImageResource_Purpose::MONOCHROME};
+  file_handler->icons.push_back(std::move(file_handling_icon_size));
+  manifest.file_handlers.push_back(std::move(file_handler));
+  expected_icon_metadata.file_handling_icon_provided_sizes.emplace(64, 64);
+
+  // Sample shortcut menu item info with no size specified for icons.
+  blink::Manifest::ShortcutItem shortcut_item;
+  shortcut_item.name = u"Shortcut Name";
+  shortcut_item.url = GURL("https://www.example.com");
+
+  blink::Manifest::ImageResource shortcut_icon_no_size;
+  shortcut_icon_no_size.src = shortcut_icon_no_size_url;
+  shortcut_icon_no_size.sizes = {{0, 0}, {512, 512}};
+  shortcut_icon_no_size.purpose = {
+      blink::mojom::ManifestImageResource_Purpose::ANY};
+  shortcut_item.icons.push_back(std::move(shortcut_icon_no_size));
+
+  // Set up the expected icon metadata for shortcut menu icons.
+  expected_icon_metadata.shortcut_menu_icons[IconPurpose::ANY] =
+      shortcut_icon_no_size_url;
+  expected_icon_metadata.shortcut_menu_icons_provided_sizes.emplace(512, 512);
+
+  blink::Manifest::ImageResource shortcut_icon_with_size;
+  shortcut_icon_with_size.src = shortcut_icon_size_url;
+  shortcut_icon_with_size.sizes = {{48, 48}};
+  shortcut_icon_with_size.purpose = {
+      blink::mojom::ManifestImageResource_Purpose::MASKABLE};
+  shortcut_item.icons.push_back(std::move(shortcut_icon_with_size));
+  manifest.shortcuts.push_back(std::move(shortcut_item));
+  expected_icon_metadata.shortcut_menu_icons_provided_sizes.emplace(48, 48);
+
+  // Sample home tab strip metadata with no size specified for icons.
+  TabStrip tab_strip;
+  blink::Manifest::HomeTabParams home_tab_params;
+
+  blink::Manifest::ImageResource tab_strip_icon_no_size;
+  tab_strip_icon_no_size.src = tab_strip_icon_no_size_url;
+  tab_strip_icon_no_size.sizes = {{0, 0}};
+  tab_strip_icon_no_size.purpose = {
+      blink::mojom::ManifestImageResource_Purpose::MONOCHROME};
+  home_tab_params.icons.push_back(std::move(tab_strip_icon_no_size));
+
+  blink::Manifest::ImageResource tab_strip_icon_size;
+  tab_strip_icon_size.src = tab_strip_icon_size_url;
+  tab_strip_icon_size.sizes = {{16, 16}};
+  tab_strip_icon_size.purpose = {
+      blink::mojom::ManifestImageResource_Purpose::ANY};
+  home_tab_params.icons.push_back(std::move(tab_strip_icon_size));
+  tab_strip.home_tab = std::move(home_tab_params);
+  manifest.tab_strip = std::move(tab_strip);
+
+  // Set up the expected icon metadata for home tab icons.
+  expected_icon_metadata.home_tab_icons[IconPurpose::MONOCHROME] =
+      tab_strip_icon_no_size_url;
+  expected_icon_metadata.home_tab_icon_provided_sizes.emplace(16, 16);
+
+  UpdateWebAppInfoFromManifest(
+      manifest, GURL("https://www.random_manifest.com"), &web_app_info);
+
+  ASSERT_EQ(expected_icon_metadata, web_app_info.icons_with_size_any);
+}
+
 class FileHandlersFromManifestTest : public ::testing::TestWithParam<bool> {
  public:
   FileHandlersFromManifestTest() {
@@ -1287,8 +1424,10 @@
   std::vector<blink::mojom::ManifestFileHandlerPtr> manifest_file_handlers =
       CreateManifestFileHandlers(6);
 
-  apps::FileHandlers file_handlers =
-      CreateFileHandlersFromManifest(manifest_file_handlers, GetStartUrl());
+  WebAppInstallInfo web_app_info;
+  PopulateFileHandlerInfoFromManifest(manifest_file_handlers, GetStartUrl(),
+                                      &web_app_info);
+  const apps::FileHandlers& file_handlers = web_app_info.file_handlers;
   ASSERT_EQ(file_handlers.size(), 6U);
   for (unsigned i = 0; i < 6U; ++i) {
     EXPECT_EQ(file_handlers[i].action, MakeActionUrl(i));
@@ -1331,8 +1470,8 @@
   std::vector<blink::mojom::ManifestFileHandlerPtr> manifest_file_handlers =
       CreateManifestFileHandlers(1);
   WebAppInstallInfo web_app_info;
-  web_app_info.file_handlers =
-      CreateFileHandlersFromManifest(manifest_file_handlers, GetStartUrl());
+  PopulateFileHandlerInfoFromManifest(manifest_file_handlers, GetStartUrl(),
+                                      &web_app_info);
 
   const GURL first_image_url = MakeImageUrl(0);
   const GURL second_image_url = MakeImageUrlForSecondImage(0);
@@ -1452,8 +1591,8 @@
   // Put icons in for file handlers
   std::vector<blink::mojom::ManifestFileHandlerPtr> manifest_file_handlers =
       CreateManifestFileHandlers(1);
-  web_app_info.file_handlers =
-      CreateFileHandlersFromManifest(manifest_file_handlers, GetStartUrl());
+  PopulateFileHandlerInfoFromManifest(manifest_file_handlers, GetStartUrl(),
+                                      &web_app_info);
 
   const GURL kFileHandlerIconUrl1 = MakeImageUrlForSecondImage(0);
   const GURL kFileHandlerIconUrl2 = MakeImageUrl(0);
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 0733f46..23d2afe 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1707134359-e6900c26dd8d9314e69a156829d2be43bc83e245.profdata
+chrome-linux-main-1707154473-ecd2011c5d936b3235a193485760fcd7c24ca1ba.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 081a778..29f661d 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1707148532-0de0b9a5f93e680e7a3980b3901b180419a98c3b.profdata
+chrome-mac-arm-main-1707163073-ba7cb4460930a55402519dcdd56add2981a5d707.profdata
diff --git a/chrome/common/controlled_frame/api/controlled_frame_internal.json b/chrome/common/controlled_frame/api/controlled_frame_internal.json
index e700cf81..faf380d 100644
--- a/chrome/common/controlled_frame/api/controlled_frame_internal.json
+++ b/chrome/common/controlled_frame/api/controlled_frame_internal.json
@@ -102,15 +102,15 @@
                 "description": "Whether this context menu item is enabled or disabled. Defaults to true."
               }
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "description": "Called when the item has been created in the browser. If there were any problems creating the item, details will be available in $(ref:runtime.lastError).",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "description": "Called when the item has been created in the browser. If there were any problems creating the item, details will be available in $(ref:runtime.lastError).",
+          "parameters": [],
+          "does_not_support_promises": "Synchronous return and callback crbug.com/1143032"
+        }
       }
     ]
   }
diff --git a/chrome/common/extensions/api/chrome_web_view_internal.json b/chrome/common/extensions/api/chrome_web_view_internal.json
index f8bd7ce..74072962 100644
--- a/chrome/common/extensions/api/chrome_web_view_internal.json
+++ b/chrome/common/extensions/api/chrome_web_view_internal.json
@@ -122,15 +122,15 @@
                 "description": "Whether this context menu item is enabled or disabled. Defaults to true."
               }
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "description": "Called when the item has been created in the browser. If there were any problems creating the item, details will be available in $(ref:runtime.lastError).",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "description": "Called when the item has been created in the browser. If there were any problems creating the item, details will be available in $(ref:runtime.lastError).",
+          "parameters": [],
+          "does_not_support_promises": "Synchronous return and callback crbug.com/1143032"
+        }
       },
       {
         "name": "contextMenusUpdate",
@@ -208,15 +208,14 @@
                 "optional": true
               }
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "parameters": [],
-            "description": "Called when the context menu has been updated."
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": [],
+          "description": "Called when the context menu has been updated."
+        }
       },
       {
         "name": "contextMenusRemove",
@@ -235,15 +234,14 @@
             ],
             "name": "menuItemId",
             "description": "The ID of the context menu item to remove."
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "parameters": [],
-            "description": "Called when the context menu has been removed."
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": [],
+          "description": "Called when the context menu has been removed."
+        }
       },
       {
         "name": "contextMenusRemoveAll",
@@ -254,15 +252,14 @@
             "type": "integer",
             "name": "instanceId",
             "nodoc": true
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "parameters": [],
-            "description": "Called when removal is complete."
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": [],
+          "description": "Called when removal is complete."
+        }
       },
       {
         "name": "showContextMenu",
diff --git a/chrome/common/extensions/api/context_menus.json b/chrome/common/extensions/api/context_menus.json
index 3ad7a0a9e..5ede00e 100644
--- a/chrome/common/extensions/api/context_menus.json
+++ b/chrome/common/extensions/api/context_menus.json
@@ -191,15 +191,15 @@
                 "description": "Whether this context menu item is enabled or disabled. Defaults to <code>true</code>."
               }
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "description": "Called when the item has been created in the browser. If an error occurs during creation, details will be available in $(ref:runtime.lastError).",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "description": "Called when the item has been created in the browser. If an error occurs during creation, details will be available in $(ref:runtime.lastError).",
+          "parameters": [],
+          "does_not_support_promises": "Synchronous return and callback crbug.com/1143032"
+        }
       },
       {
         "name": "update",
@@ -284,15 +284,14 @@
                 "optional": true
               }
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "parameters": [],
-            "description": "Called when the context menu has been updated."
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": [],
+          "description": "Called when the context menu has been updated."
+        }
       },
       {
         "name": "remove",
@@ -306,29 +305,26 @@
             ],
             "name": "menuItemId",
             "description": "The ID of the context menu item to remove."
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "parameters": [],
-            "description": "Called when the context menu has been removed."
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": [],
+          "description": "Called when the context menu has been removed."
+        }
       },
       {
         "name": "removeAll",
         "type": "function",
         "description": "Removes all context menu items added by this extension.",
-        "parameters": [
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "parameters": [],
-            "description": "Called when removal is complete."
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": [],
+          "description": "Called when removal is complete."
+        }
       }
     ],
     "events": [
diff --git a/chrome/common/extensions/api/desktop_capture.json b/chrome/common/extensions/api/desktop_capture.json
index 100f2d8a..caf3044 100644
--- a/chrome/common/extensions/api/desktop_capture.json
+++ b/chrome/common/extensions/api/desktop_capture.json
@@ -67,30 +67,30 @@
               }
             },
             "description": "Mirrors members of <a href=\"https://w3c.github.io/mediacapture-screen-share/#dom-displaymediastreamconstraints\">DisplayMediaStreamConstraints</a> which need to be applied before the user makes their selection, and must therefore be provided to chooseDesktopMedia() rather than be deferred to getUserMedia()."
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {
-                "name": "streamId",
-                "type": "string",
-                "description": "An opaque string that can be passed to <code>getUserMedia()</code> API to generate media stream that corresponds to the source selected by the user. If user didn't select any source (i.e. canceled the prompt) then the callback is called with an empty <code>streamId</code>. The created <code>streamId</code> can be used only once and expires after a few seconds when it is not used."
-              },
-              {
-                "name": "options",
-                "type": "object",
-                "description": "Contains properties that describe the stream.",
-                "properties": {
-                   "canRequestAudioTrack": {
-                     "type": "boolean",
-                     "description": "True if \"audio\" is included in parameter sources, and the end user does not uncheck the \"Share audio\" checkbox. Otherwise false, and in this case, one should not ask for audio stream through getUserMedia call."
-                   }
-                }
-              }
-            ]
           }
         ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "streamId",
+              "type": "string",
+              "description": "An opaque string that can be passed to <code>getUserMedia()</code> API to generate media stream that corresponds to the source selected by the user. If user didn't select any source (i.e. canceled the prompt) then the callback is called with an empty <code>streamId</code>. The created <code>streamId</code> can be used only once and expires after a few seconds when it is not used."
+            },
+            {
+              "name": "options",
+              "type": "object",
+              "description": "Contains properties that describe the stream.",
+              "properties": {
+                 "canRequestAudioTrack": {
+                   "type": "boolean",
+                   "description": "True if \"audio\" is included in parameter sources, and the end user does not uncheck the \"Share audio\" checkbox. Otherwise false, and in this case, one should not ask for audio stream through getUserMedia call."
+                 }
+              }
+            }
+          ],
+          "does_not_support_promises": "Synchronous return and callback crbug.com/1143032, Multi-parameter callback crbug.com/1313625"
+        },
         "returns": {
           "type": "integer",
           "description": "An id that can be passed to cancelChooseDesktopMedia() in case the prompt need to be canceled."
diff --git a/chrome/installer/linux/common/apt.include b/chrome/installer/linux/common/apt.include
index 05c6228..8e45302f 100644
--- a/chrome/installer/linux/common/apt.include
+++ b/chrome/installer/linux/common/apt.include
@@ -33,191 +33,226 @@
 Tp8Gv9FJiKuU8PKiWsF4EGR/kAFyCB8QbJeQ6HrOT0CXLOaYHRu2TvJ4taY9doXn
 98TgU03XTLcYoSp49cdkkis4K+9hd2dUqARVCG7UVd9PY60VVCKi47BVKQARAQAB
 tFRHb29nbGUgSW5jLiAoTGludXggUGFja2FnZXMgU2lnbmluZyBBdXRob3JpdHkp
-IDxsaW51eC1wYWNrYWdlcy1rZXltYXN0ZXJAZ29vZ2xlLmNvbT6JAjgEEwECACIF
-AlcMjNMCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEHch9jvTi0eW5CAP
-/RELE/OAoA4o1cMBxJsljWgCgDig2Ge91bFCN0vExLcP0iByra7qPWJowXDJ5sCj
-UBnCkrxGo5D15U7cW5FC0+qWU73q0AuG3OjKDQ49ecdRkYHwcvwWQvT5Lz3DwOGW
-4armfEuzWXcUDeShR7AgfcTq+Pfoo3dHqdB8TmtNySu/AdJFmVH/xTiWYWrOSibh
-yLuaSW/0cTkHW0GDk06MlDkcdkTzhO5GMDO7PUxBgCysTXFR0T9TVWDo9VwvuMww
-2pE5foleA0X6PD/6GQpy3aX2xry8rhFvYplEa5zwXhqsscdKXlp1ZPZ4PMvvwe49
-5mY9n/1Rx1TmMvIcLHKP61sURMOve97Gipk/iD6oaeeT8I0khexHCQy7JMROoPMr
-z5onVOt2rAGZScIZsm5FYGSt9eDKBWI6qpJ/5QoVhkRWjOXOchZlJHo+kLdg6jq2
-vOnIlFnXo0p6Rqf/IEq5PMh70vVZpk4tNYNy4zRx03ZTA9qXRLW+ftxSQIYMY5eC
-Z31lqSH4EjqgtUG+zn2A6juKayb1nkt2O3F1wWOm6oTzNsAP5LdReJRlw151Jp4U
-4ftGtw7ygq+nvokXL7YLuu8sbFqfFXcTPrAZa5M9gnC7GCnIQyF/WvqUnrcaC1jp
-qBc+pkSJhROhN12QY8Po8AT8/UaUh/dPIiW5A4o8pOPEiEYEEBECAAYFAlcNtn8A
-CgkQoECDD3+sWZGy3wCfWTMZWsipX+yG/VB4Q1FunIfEVHYAnimEXCjZ3IVyy5F1
-yU36PihDCjWqiEYEEBECAAYFAlcNtvEACgkQMUcsOzG36APnRwCeJ/bfGf8FBa4q
-5TMw8p1GS1jWT5EAn2sc02481HHdTmZiW/CGWXmgE+OPuQINBFcMjcgBEACrL9gH
-hdr6gQX4ZMA5slp628xOrHCsdLO54WNdPRKeFHXJqSSJi3fs8FxBWI4FnejeKUGb
-F+MrOlFpKqELxaMje7bwZyap3izztZHszP3YmOoTBJvREGKdCkL82cLsChYD/Prg
-E8crvkhSnq9evcsKAnziMxg/wDCChUL3Evqo29BeoB81f+E9wkrUTMCT/kVxt3pG
-RalKX0UhrtKrpm8yRfjufJfwjkdwgvinkRGZ2GrWHj4LzMbi9/udYaJZ66Yw0hEU
-4USxUB9vNtmSFrb4EB91T2rhc68dgQ4jYBI7K4Ebb8XaWAxb+IAq31l1UkiEA32F
-4qUMoL6rChB4y6nHxOnTvs+XEb5TBwXVogjLRKTQs5U/HV9l7j+HAchk5y3im2N2
-UKmMxHqotvPZZUZPdaCRxUedQf9gR0yLZV+U9BcDuwjzL/zjrthNZYlEGJ6HZ/TL
-STp4dDH+uXuLqMVWy5iquKtnbrnNTQtv5twD+Ajpgy60YLOJ9YaiJ4GjifOpzSk8
-3e1rJ3p/pX6B5NWQinVLZJzxyeOoh3iMjdmCDSnEXLrCmYv5g6jyV/Wbd4GYFuMK
-8TT7+PQdWLcbZ/Lxc5w0s+c7+f5OfmKXO5KPHnnUsrF5DBaKRPjScpwePQitxeIg
-lUgEMDkNruBhu1PzCxd3BtXgu++K3WdoH3VcgwARAQABiQREBBgBAgAPBQJXDI3I
-AhsCBQkFo5qAAikJEHch9jvTi0eWwV0gBBkBAgAGBQJXDI3IAAoJEBOXvFNkDbVR
-QSYP/0Ewr3T7e0soTz8g4QJLLVqZDZdX8Iez04idNHuvAu0AwdZ2wl0C+tMkD7l4
-R2aI6BKe/9wPndk/NJe+ZYcD/uzyiKIJQD48PrifNnwvHu9A80rE4BppQnplENeh
-ibbWaGNJQONGFJx7QTYlFjS5LNlG1AX6mQjxvb423zOWSOmEamYXYBmYyMG6vkr/
-XTPzsldky8XFuPrJUZslL/Wlx31XQ1IrtkHHOYqWwr0hTc50/2O8H0ewl/dBZLq3
-EminZZ+tsTugof0j4SbxYhplw99nGwbN1uXy4L8/dWOUXnY5OgaTKZPF15zRMxXN
-9FeylBVYpp5kzre/rRI6mQ2lafYHdbjvd7ryHF5JvYToSDXd0mzF2nLzm6jwsO84
-7ZNd5GdTD6/vcef1IJta1nSwA/hhLtgtlz6/tNncp3lEdCjAMx29jYPDX+Lqs9JA
-xcJHufr82o6wM9TF24Q8ra8NbvB63odVidCfiHoOsIFDUrazH8XuaQzyZkI0bbzL
-mgMAvMO6u1zPfe/TK6LdJg7AeAKScOJS38D5mmwaD1bABr67ebA/X5HdaomSDKVd
-UYaewfTGBIsrWmCmKpdb+WfX4odFpNzXW/qskiBp5WSesKvN1QUkLJZDZD1kz2++
-Xul5B97s5LxLTLRwvgLoNaUFr3lnejzNLgdBpf6FnkA59syRUuIP/jiAZ2uJzXVK
-PeRJqMGL+Ue2HiVEe8ima3SQIceqW8jKS7c7Nic6dMWxgnDpk5tJmVjrgfc0a9c1
-FY4GomUBbZFj+j73+WRk3EaVKIsty+xz48+rlJjdYFVCJo0Jp67jjjXOt6EOHTni
-OA/ANtzRIzDMnWrwJZ7AxCGJ4YjLShkcRM9S30X0iuAkxNILX++SNOd8aqc2bFof
-yTCkcbk6CIc1W00vffv1QGTNjstNpVSl9+bRmlJDqJWnDGk5Nl4Ncqd8X51V0tYE
-g6WEK4OM83wx5Ew/TdTRq5jJkbCu2GYNaNNNgXW7bXSvT5VINbuP6dmbi1/8s0jK
-JQOEBI3RxxoB+01Dgx9YdNfjsCM3hvQvykaWMALeZIpzbXxV118Y9QQUIRe2L+4X
-ZACEAhWjj2K1wP7ODGTQrrM4q4sIw1l3l7yO9aXXN7likAAddT4WEpGV0CiorReO
-J1y/sKJRJSI/npN1UK7wMazZ+yzhxN0qzG8sqREKJQnNuuGQQ/qIGb/oe4dPO0Fi
-hAUGkWoa0bgtGVijN5fQSbMbV50kZYqaa9GnNQRnchmZb+pK2xLcK85hD1np37/A
-m5o2ggoONj3qI3JaRHsZaOs1qPQcyd46OyIFUpHJIfk4nezDCoQYd93bWUGqDwxI
-/n/CsdO0365yqDO/ADscehlVqdAupVv2uQINBFiGv8wBEACtrmK7c12DfxkPAJSD
-12VanxLLvvjYW0KEWKxN6TMRQCawLhGwFf7FLNpab829DFMhBcNVgJ8aU0YIIu9f
-HroIaGi+bkBkDkSWEhSTlYa6ISfBn6Zk9AGBWB/SIelOncuAcI/Ik6BdDzIXnDN7
-cXsMgV1ql7jIbdbsdX63wZEFwqbaiL1GWd4BUKhj0H46ZTEVBLl0MfHNlYl+X3ib
-9WpRS6iBAGOWs8Kqw5xVE7oJm9DDXXWOdPUE8/FVti+bmOz+ICwQETY9I2EmyNXy
-UG3iaKs07VAf7SPHhgyBEkMngt5ZGcH4gs1m2l/HFQ0StNFNhXuzlHvQhDzd9M1n
-qpstEe+f8AZMgyNnM+uGHJq9VVtaNnwtMDastvNkUOs+auMXbNwsl5y/O6ZPX5I5
-IvJmUhbSh0UOguGPJKUu/bl65theahz4HGBA0Q5nzgNLXVmU6aic143iixxMk+/q
-A59I6KelgWGj9QBPAHU68//J4dPFtlsRKZ7vI0vD14wnMvaJFv6tyTSgNdWsQOCW
-i+n16rGfMx1LNZTO1bO6TE6+ZLuvOchGJTYP4LbCeWLL8qDbdfz3oSKHUpyalELJ
-ljzin6r3qoA3TqvoGK5OWrFozuhWrWt3tIto53oJ34vJCsRZ0qvKDn9PQX9r3o56
-hKhn8G9z/X5tNlfrzeSYikWQcQARAQABiQREBBgBAgAPBQJYhr/MAhsCBQkFo5qA
-AikJEHch9jvTi0eWwV0gBBkBAgAGBQJYhr/MAAoJEGSUxtaZfCFeW4kP/iZq+blR
-DzgRzOw16x80vyBjfPOUKd++dSUkcr4Khi5vjBygNdVSWcKZaBKVkdBmCvf+p9bY
-wzfL+RdxvGEv8WKNTNjdaWcJ2chU2O4H5Am3QsduQ/sSf+jTzlnMe7NpfF9n3uo3
-4o+xEFOOcnyF3cHrhxWOCde9rX6kbnUQriIMXZteJY8e9Rs+Iv46DoL1eOlavAgD
-UJbIf/iLt219OdtWI7ZqopA0d+tcn7FL3fwuvyvn5WZRYHIerB4EYgBI6bCwl5JQ
-ejORlhuYx1oknyPjnzPJ9Los74chrf7OHOJ06iIQf1zlC9V/niA2xiM9NwePtTQO
-CTEJVB6IEoEtH6rozpAdriprH9fRnZkJxINNnCoYk1op9wVh3xfUHbOCvGQbB54c
-qN+amp9dEquCAe6Yt1WodTspL1zPXJ5Mv43Dud76TNEwQDywuebg4NFQnBTPXZGp
-LQYbUVhXSuMlVZXNEUx8xSz7vECm0S4x2h12RBKbK2RfI4oCq/wpD1dQRsZaKSYL
-FbZw5j2yk6nBBrtfahd7sWVX1F+YdisbTeT5iUhESAWqW9bCyCnNRFy6V34IgW9P
-e9yLu8WbVSJAFvnALxsc6hGyvs5dbXbruWKmi5mvk6tCFWdFlBVrrhx1QgqMtcS3
-jv3S7GHyCA3CS1lEgsifYkeOARAgJ1hZ5BvUurUP+wb66lIhDB0U9NuFdJUTc6nO
-/1cy3i9mGCVoqwmTcB1BJ9E1hncMUP1/MvrAgkBBrAWJiD2Xj9QV/uBozA7nLxrV
-7cf1de9OLgH4eNEfX25xj8BBPYnyVyHsyk5ZHDhjj9SaurfvlFWYi13i5ieMpyLV
-JV4+r2Wi1x1UgKVAlB78sHYnbDzSoHPLBcIxtIKp30LJ0PEkat8SG7G2wgtv1Rdh
-mcZEBV05vMnrGGO991e+pKzRNPYH8rD3VQKJlvaFwsJuBTW42gZ3KfpUNKI2ugCc
-nRNpoHFWNCrzlJ0CFI48LMlmUSs+7i/l+QGleaLKQxRTNNpAmevLrS7ga4Iq0IEq
-xey6VW6RSk/Z1Z37J8B7PISSR0rZn6TeyQgFWf/FOLw6OtwOquGmMeGSqj2Uzxyb
-ygtsvUZz0BxYymoWFd4F8sp43oL2TXU6Wp7QIpBaFgkSf/UQxfR6wcQ3ivafeS1l
-g8vUFuMfuMLto6T0JiZw8uKSuDWltSReF+FXVnhawz72BZMy8RIoshGdpWHn/YbN
-6L+JOuxZnvkMAZvSLT3c0H4XCDYtEfK2mJMqD2ynX5tGR8Fy3GAaEjhx36TvzTjC
-XRmJ+FnlSW1p77x+UjFUFcpY8skv+f0Gip30iynAb1hoAdibIDab612OWi/4vX0D
-aM6t68Uq8rsabeJYsZG4uQINBF01/K4BEACskZL08crrKfX2aD2w8OUS3jVGSW7K
-10Jr/dgl6ZB7Xx/y3c9lhBim7oRIsl6tpR/DBP50UnTIgBbvynbJ6tbWGptt64Az
-nI7el9pH0k63DOKcfqRUgJKTM4OUZSkcuqQ2qnkvn+g0oiJ3VhaVYOJdJfJF/pLj
-5Oi3UEL2afoEd048/lZEaATRvEqLj+h2pSfETEl5wCWyRnuMSu6ay9NmVzRxiJhP
-DGW2ppQTxJuaKj+6Vqw5WISu9nsRxTPE1DW8f7LYyPBwgultuSYKZoCdfoYE8ff4
-71oZIuCKcGSSBHQbR6MBTD6KJtqzBzpfJ8zZJmVO4lg0CJgp9xX2QZ8hPkpaBbnq
-2JCMS1zriCMN8iGhW6ZHYmZQJtWuubuZt51VL9QmEUUhCF1t+3ld11SaowY4NFKI
-LUdYbC2zAOQIEEJkWRIHKleuc2zYSNSoXl06oGgwCKQb5l+LlcYHx4+/F3+KzyAq
-0NqBC1rMnhbn3tcckdZyhLEpnx9/y33ypo6ZZ0s6dLGrmSpJpedEz6zr8siBa4uT
-3IvVF4xjfpzSt3cMD/Lzhbnk5onUfkmoCmQ/pkuKpMr35hHtdDxshLcLPFkTncMj
-EVAOBToHDbKDSplueyJm48ELPi9ZmuyNu7WsB8TWVEAkUShxdeHALVpY1D+MjXK+
-Z5ap6/tppj+fmwARAQABiQREBBgBCAAPBQJdNfyuAhsCBQkFo5qAAikJEHch9jvT
-i0eWwV0gBBkBCAAGBQJdNfyuAAoJEHi9ZUc8s70TzUAP/1Qq69M1CMd302TMnp1Y
-h1O06wkCPFGnMFMVwYRXH5ggoYUb3IoCOmIAHOEn6v9fho0rYImS+oRDFeE08dOx
-eI+Co0xVisVHJ1JJvdnu216BaXEsztZ0KGyUlFidXROrwndlpE3qlz4t1wh/EEaU
-H2TaQjRJ+O1mXJtF6vLB1+YvMTMz3+/3aeX/elDz9aatHSpjBVS2NzbHurb9g7mq
-D45nB80yTBsPYT7439O9m70OqsxjoDqe0bL/XlIXsM9w3ei/Us7rSfSY5zgIKf7/
-iu+aJcMAQC9Zir7XASUVsbBZywfpo2v4/ACWCHJ63lFST2Qrlf4Rjj1PhF0ifvB2
-XMR6SewNkDgVlQV+YRPO1XwTOmloFU8qepkt8nm0QM1lhdOQdKVe0QyNn6btyUCK
-I7p4pKc8/yfZm5j6EboXiGAb3XCcSFhR6pFrad12YMcKBhFYvLCaCN6g1q5sSDxv
-xqfRETvEFVwqOzlfiUH9KVY3WJcOZ3Cpbeu3QCpPkTiVZgbnR+WU9JSGQFEi7iZT
-rT8tct4hIg1Pa35B1lGZIlpYmzvdN5YoV9ohJoa1Bxj7qialTT/Su1Eb/toOOkOl
-qQ7B+1NBXzv9FmiBntC4afykHIeEIESNX9LdmvB+kQMW7d1d7Bs0aW2okPDt02vg
-wH2VEtQTtfq5B98jbwNW9mbXTvMQAKKCKl+H8T72WdueqgPKHEkXDZtJmTn6nyne
-YlETvdmHGEIb1ejxuJ5URlAYnciY+kvSQ/boKjVHNGmf6+JBexd+HqPhkeextV6J
-cnmi47HDvIU/TSynhuqZeK/3SZAV7ESqQl42q7wm7Pqw0dkv4jjFCRxDA+Qq2aH6
-szJ7DZxTRWqfR3Zbe78NyFVXKxhFQO72zHzC3pFu/Ak59hmTU23yoXVo5t+5O+Q2
-1kX2dbuLd6Px1bnT+EmyneoPP1Emea5jgsw2/ECqHnvNt6cbp+42XYldGh+PBHBm
-ucC3Mn7sALajHe5k2XkNlfbjSNlmutxQFH1qq9rh/JVyxJNHeGzV5G0timAwfdJF
-UzE1vNU5P0w4O8HrCsX5Ecfgcw2BQ9vPCE3OfG+11xp6oiNMRVsR5pTu7RiI1BQA
-yICWUW/wXuhhHkkwNTiwfciJfVA8ckOiRubik8geEH5boOxgeAaBu6yusQVHnRRy
-G4wjQ+qsWo+wDI9WMdtpNG1toJrSUL4OYa4oX3YogSv5hGrbYIaP4HwO6O2oTMnS
-0lRIGJOqbEQcmKUa/nWT/3NipTnYzyMjMlEQe89YKjd+32tjMfOSdIOvwCGaTizd
-WnKPF77qB9D0v8C/7AdHmEFqf2ZX8vK31aaY+ZpPWG5IHlf6f/buIMBalJOxIBev
-eBqxcHwQuQINBGF4DJ8BEACk2Gwau+s/pKmOTnGLMnB3ybQsiVGLRhsw2SqSTvSy
-BthAyW1UAqdRqNA8/FdMlvVuppG8+vCLXPmpP63C+9M2tyQeOR2aVQp+u1EIwN4l
-Pu4wrh6vdtgSRim8uxBdLIHG16z0xxVhE2rM/Ot/gucfkpoEw289VaR7sPmIxfVT
-m1QcqCGiFQl3rZnma6Bz8UOXJoE8wO+LK5WkcdmFz6+Z3BLSb5IL9lhsArFToNq5
-dN2SSTbCTdHRzrRuoCdefYHdxoLCM4kJfggRRgWhKoEJro+ZipESq1T5yHV/iAJy
-+3DuC8LbYLvsjt9VZYARw8xIGb90Vj3ThWuMoVr/IVmKT7foC5Whe0PTI/b2frNa
-WCxxC4cRVxMusiBX66mclQ4Mvzwj50G1WKygULYcvPQ81Tg0pvgTKqgxwL9luN9M
-iDVtkn9CZx7NFlszVr+ic7nVJjANnJebFHCEZfJbQo4uIwKfYbhopUkCa41iXpes
-bVzAKqNwePgyNTAMFyYnjAUE8FVUmx7ZJVb15iEbMs38gJKJ/Wb8wtJRflAfkhrE
-zh1M/43WUAU3RfPmXTrGeyDCYKTHiXTnj748uH6U40sB9q+qeEhZdTj0KufjgtWa
-FWsZTkVrtGOaI6xfX6py/k3hjU3es+7ddElxhPBcqNE3pkPRqb9wz+exSdM7hiUz
-NwARAQABiQREBBgBCAAPBQJheAyfAhsCBQkFo5qAAikJEHch9jvTi0eWwV0gBBkB
-CAAGBQJheAyfAAoJEE6yfbKjuIuLggkP/1INRyRToLmY1ms9DTWMQ0lwbBL8J3xu
-/neKIOKVGOdw9zcWlGugUoOthSbT8bjvuybH1Vjx4wFM+cnuMVfjD58Xu6ZpgCHN
-1wXYMuzYweBFKaMg4oSwTKuAJBJ2IhfEm/cAryVvKY2zY+uyzgizx3vAg3sjkAPD
-crSCJP2nkuHcJ3nzUbKNAjmdMsnWDrqqZVwP99nuyMk8bAtueZ0SKvIpCv2wIeYO
-7zkj61vuQOFOGhl98OBui5wUhtgQw//esTWYiGNKSmD3derd2JHVA01tBmCWV4KM
-LDbg3CcMMQ1x3V1me6EG3giwBL1I9xTsBUbEa6eEN9U0zdKvoMbSogON5wCuxAzO
-/CXGMreJtBUupHEc69oTuwe426Ihi3AbRrPAg3tnGGFCt11HoQFNnRPWb3unF8Ul
-A2rSytvwFyQi3pzBYt5VsTIA7NEHGuJs+/Oor6AOInzht1cp7AfmDGfGy2N5ow+4
-GI6FPe2UqIg2+nFiGr9hRZOvXRgLQL8dlDnFChymldxm/J/UFdJGSWRldEDsPrzH
-QESKvsV9EjnJQR5p5zkQK6jx0zqSlDgiNG2GT3/CSvwIdCih6Cl9HThHtYNm3ZYN
-0bU9W2jeoLh3AINNTcrp0tAHZuQLFxukbj56O5eB+nfk67/X2iNii46ZdJQNwbT9
-YN6CstQz+Cnqg7YP/3G6Y6NHIQggXnlYIi3iwN72hEgEqz6vIRK87lBGW2r3eQ0c
-DZuE3+5Q4FYciw+B2RKeDhjdmPHypA5o+RiAyI7JOZwJalqHO3nwJG5sr0rRzcJs
-bGvpbzso2JuTyTURv4tBNq45b9y0Qdzt5PpNrPJbQADJWn+HWsbVJB5gWBTdoQYg
-pyTr84nQyscWAUFTRbmHvtjCCfLdvU8wM7ubAQ5Dwi1pABRttRAMuPA94HzaBF5y
-XkghxHpnW0IcXGiwgch9LQyaO9VSRhiPH6r5Zuk7KvGhHph7SC5JgUn9vJmmp1zc
-d0mXQ2Zh8M81J3Ri3iGPHM2CqplAxXNbIrnztbEJhN2I+77m73Z4d+K1ivg6xQht
-eSZhwhx7/Z3Tl+U2jYOEFIn/UFmV3UxRSJa/jQRcjvMKprSp4tAZ2yJI3babjRbi
-xgUEtlK105/JepxcAdw9vosxO/rR7VqCzu0copdxC0GAH8og+A9/3LPhlRGy3Qhf
-zjy9JHWHj4EIsol02BS8+dWvAoYerkve9O9+h6/B5wM/Yng9BjT+OrNvkfmqK2cs
-pBXwYedOrC4uWcUmueEVrv5P4FF36wJ+ejvPS6vdTxVTdLXjouUHwTQQZVlNjWY3
-cIyj03nZ19c+b30+2FzG/uSnb/ePWsRLY7Iyz4ygr8etweBPnEIvjwpAZxOuuQIN
-BGPs+VgBEADKbgLL+vAabKV2rGSDgY+IttTAtg9w9Uor1+Q/CIWGxi/JQy7l7XTK
-jmS0wvdwU+9f/eGsjxigbvAcSsV1szyKfVQQFT2m9KhDrBqNCAvQ5Tg6ZQdNe51o
-HwjiIQ1i7z8QoT22VucdTYqcMLAHe+g0aNqLLSSWLAiW4z+nerclinjiTRCw/aWZ
-JR1ozQd2eKwAw6rk19bHcihXo2E0K1EDmdHcNA8ytypxwWWXBftCYRWXi5J02GeZ
-azxmx/DULnFgy2J4G0ULTqGWsbf/tCt22jqgyX+vFj/sJPn+l3IJqpyNY5yBG6Gc
-ejeP9vRoQrapGqHkcx+37f2vjwmpj5548JI52KEC1yZeFwp8HjGLp+zGajpnokrK
-d4XJHniW9+bPLq7Yp7PNn65MaYvZUjv5enKd45fFK6vJ3Ys/fx6PBXKKBs9flRIg
-dXOKSvtV+bGIG0I/p/JEZ/wPxRgxHPDK5jbcI6KBVm3Uk+CHFC4IBAtzdSh6H4Zf
-w1EH3dQZMLVBB/Sj34UQhlwAOlAXtZH3vks/KpclWK8gnqz3i8HN0ezvcnQlRiRO
-8IqlN9/PmFqZeNTerklT7Tt0jXqiopLHL0FXR2LsndeORfxDE1rhVOUxloeuIsY8
-x6gO8h2bGg41YapROjYxZZEcakg9Nch4XAlxeqB4ISttfbiVxeL2DQARAQABiQRE
-BBgBCAAPBQJj7PlYAhsCBQkFo5qAAikJEHch9jvTi0eWwV0gBBkBCAAGBQJj7PlY
-AAoJEOiJefubMKzyRuUP/jzITdamLoLDxEHOra7Mt2S6peHr3XMbpWEdRlA1vzl7
-AaMYO78Pbm7YkWuEByaXM6vGCC8vhrxZq09dBo+oNlpKHjV6UzVhrQLtw1CrvE1U
-DSlw3ltD4pddky5BoDz0EKVNJchPPqPg7im+EAbzLxDYT0y/tRhqzQ6EODNUivLa
-zWjY+aXWqOVv2Ny071ytFSIXq/1G71pCSAYdi3i7I/cfMoN+g27Nf9Zfc7QWbw02
-mcmTqpmwsrCDu6RR0k4gQhsss0tT1libKzfA20Mox+bhPv1ptI3A0ifh13mFqkf0
-EC4MmeThacU5qn0BBk+AlfZZcoLYNdBl5JfVVekjeuvsVJtJ5zx3luK3DuzbRdbJ
-AHb5mh61HE2BHXTgYiH8tqO1q0soVz02c/1KaF7LyevFVkXHoe5eycY4+RuOyIVg
-yzG09Vic7vacENMM/hl6Ms5prLYq0JvykmQIfxTSC6q4MZV35LTZfH3jt6/K8eoa
-3lXTJUU8Pu4C7sDlAFhe+1y3Or3dLWNkMigw/3c57xWlStcEF+LPMdXE/pVSbEz3
-sgT6CNVGo30+4yunYP3IQFQaTjh9BbnPK66iZhpzsynHZ+daAYD8CX26Da69Ligj
-NTIsQnGlzozxFiW5pxIiMWAKKC5xGy9MHLqWhsbUUy+dDLN7r58B4ptusrzk64DU
-pbcQAJ+wzIvCe2qf5C7yveT/ohGfSL1dX9uFK0TbLqIdSaqzmx3t1+SZUjtuymg6
-4MoUgSt1N6mEfT0TSG9AMkRGcyb6uHxOVm05L/BjLDH7ZqFKHkm3d0jkvjyjNH5Y
-lsTGJerxmpOemf8RAZDwygz5LZ1L5zNfzlkv6beKD60ofBppd28ZxgjeHxbBCdfc
-gFQUK6vxZJ19ygbKJDhylNdwjXUaAaCTKnEzzDHGgtUJO22kIFEKk9/z88sowIrT
-+Te7hBKG2nVYMNBWEWb8Tqh8b1NIYgpwmawcdBjuu6QSnqVIi+YvRmMHJFqHicrn
-OhzaPz2w2nK56ZnCv1f5X0s6MXu9BM7/zLdwEE0K3RHmWvF4G9HN7XmTQPNKG4fI
-+GDY8Gp885LtGdSIXYV4j7NDvEWcuqgPpyQjvpFEB/vDSyqe8yUNGmNVT5wPK6lH
-k10Hv2g9cmkeW0qDiRpDg7nHoFcdUSkAyElzxs++Z8CJMVpzl/TJyJt/ZHm02XNs
-owP8HFWvNcyCGwnk9aYCJRuo+/UgjmQvDnVvoHO+XwrMkjSH7JKJQZvzrJ5x8cZs
-XvM9FyHYq3n7u3R+ASMBVwxF9yAex9CfwRg/3OhzOnkbDsu9HwEEOrV2xMQQQ9MO
-t74fIbGkM3hzws0asNoIV1ec52U1X/NP1W8GT9GRX5OX8uTi
+IDxsaW51eC1wYWNrYWdlcy1rZXltYXN0ZXJAZ29vZ2xlLmNvbT6JAk4EEwEIADgC
+GwMCHgECF4AWIQTrTBv9TwQvbd3M7JF3IfY704tHlgUCVwyM0wULCQgHAgYVCgkI
+CwIEFgIDAQAKCRB3IfY704tHlkGrD/9aIOPxoABbhHDa+GbM1XHSeV99q2UOIsYc
+A5Jg3k2+Vbjr/006cL9Kk+rdbruZJtERo2z+HVVhkJisvySbsd0UbWfiY5AdHzNP
+azpitbX9cNYi0ghDZsD5UgP3cWdx21BJPO0v9PBG9U4z1TQ+pmsQphtNzMC4tK+A
+H/7WTXnVPzKXTYziIEIPgHeassSj7Yfwa8kLiBR5tAehHDNNMi/mMf4d6a+wO46x
+hhRx/BLjoaIxsZw9f5VxDAqGbCrW8IccwJX8vTc89y+6vpzSurdqYrplZWGpcnfT
+3SPBxodLhS7wMehdy6NKNO14vDGR/GP43+6oZ91Cyv2CYHSPpZM6+qMwMmGVkHS2
+6PrCVPhPoDywf/7UeFsC4KZMI6LIGD2YI9UEOlcCAEbRwWVjXCSwRZ9vRkxOxK4Q
+xNMLAIf3YmUZPnqGVcvNssgsapvjmI3CAWpAPWlP5GTcHxrVGiYz7hNZcA0PfgxF
+pmB0QXNxr/x737I9Q8FCZasSlNqocaiKF6gKBxFOKfiKx5DRZ63EZ07Z3HE6y+w3
++97UIJhjxVrONgb7ZX9paE8NtLG/X0ZldUzqWngfnFVasnCDiQC+ls2Tu9Oa+yMJ
+rMe3VM4EcZTjYoESUjKzEHP72hn+GoAk7saWWVK6xYUJPM18Ua1mGx8xwoXt/t95
+W40b92HbJrkCDQRXDI3IARAAqy/YB4Xa+oEF+GTAObJaetvMTqxwrHSzueFjXT0S
+nhR1yakkiYt37PBcQViOBZ3o3ilBmxfjKzpRaSqhC8WjI3u28Gcmqd4s87WR7Mz9
+2JjqEwSb0RBinQpC/NnC7AoWA/z64BPHK75IUp6vXr3LCgJ84jMYP8AwgoVC9xL6
+qNvQXqAfNX/hPcJK1EzAk/5Fcbd6RkWpSl9FIa7Sq6ZvMkX47nyX8I5HcIL4p5ER
+mdhq1h4+C8zG4vf7nWGiWeumMNIRFOFEsVAfbzbZkha2+BAfdU9q4XOvHYEOI2AS
+OyuBG2/F2lgMW/iAKt9ZdVJIhAN9heKlDKC+qwoQeMupx8Tp077PlxG+UwcF1aII
+y0Sk0LOVPx1fZe4/hwHIZOct4ptjdlCpjMR6qLbz2WVGT3WgkcVHnUH/YEdMi2Vf
+lPQXA7sI8y/8467YTWWJRBieh2f0y0k6eHQx/rl7i6jFVsuYqrirZ265zU0Lb+bc
+A/gI6YMutGCzifWGoieBo4nzqc0pPN3tayd6f6V+geTVkIp1S2Sc8cnjqId4jI3Z
+gg0pxFy6wpmL+YOo8lf1m3eBmBbjCvE0+/j0HVi3G2fy8XOcNLPnO/n+Tn5ilzuS
+jx551LKxeQwWikT40nKcHj0IrcXiIJVIBDA5Da7gYbtT8wsXdwbV4Lvvit1naB91
+XIMAEQEAAYkEWwQYAQgAJgIbAhYhBOtMG/1PBC9t3czskXch9jvTi0eWBQJXDI3I
+BQkFo5qAAinBXSAEGQECAAYFAlcMjcgACgkQE5e8U2QNtVFBJg//QTCvdPt7SyhP
+PyDhAkstWpkNl1fwh7PTiJ00e68C7QDB1nbCXQL60yQPuXhHZojoEp7/3A+d2T80
+l75lhwP+7PKIoglAPjw+uJ82fC8e70DzSsTgGmlCemUQ16GJttZoY0lA40YUnHtB
+NiUWNLks2UbUBfqZCPG9vjbfM5ZI6YRqZhdgGZjIwbq+Sv9dM/OyV2TLxcW4+slR
+myUv9aXHfVdDUiu2Qcc5ipbCvSFNznT/Y7wfR7CX90FkurcSaKdln62xO6Ch/SPh
+JvFiGmXD32cbBs3W5fLgvz91Y5Redjk6BpMpk8XXnNEzFc30V7KUFVimnmTOt7+t
+EjqZDaVp9gd1uO93uvIcXkm9hOhINd3SbMXacvObqPCw7zjtk13kZ1MPr+9x5/Ug
+m1rWdLAD+GEu2C2XPr+02dyneUR0KMAzHb2Ng8Nf4uqz0kDFwke5+vzajrAz1MXb
+hDytrw1u8Hreh1WJ0J+Ieg6wgUNStrMfxe5pDPJmQjRtvMuaAwC8w7q7XM9979Mr
+ot0mDsB4ApJw4lLfwPmabBoPVsAGvrt5sD9fkd1qiZIMpV1Rhp7B9MYEiytaYKYq
+l1v5Z9fih0Wk3Ndb+qySIGnlZJ6wq83VBSQslkNkPWTPb75e6XkH3uzkvEtMtHC+
+Aug1pQWveWd6PM0uB0Gl/oWeQDn2zJEJEHch9jvTi0eWVo8P/2OVSzfPFfPUhJSw
+zmgNX2WsW6WN91wtbf0oUpORK4otjJETUTvurVHPin473mSAeIypzMO1pHS6Q1uy
+Pj5Em8x7BgGza1hBLUTvTIpRfS+J54hoaQL6XGnrE3/QIl/AxGK5aqc9h7EqsTbh
+Pckg6BELWueKg1PpCGWtQ1igCcsTUt/kgJ54TjT7dUyuFCAapVgY6lMlEta4dIYJ
+dbeQWkZR043o6u7R0HvYHl0P13thD41guhdZsPNah6km5hd7IEXuBNo/HReSHniI
+zCKolpIkJyn9X1g+SKJ5aQ6MvFd2L4pkqJKt+nNvkoQXITw9yExDHJSQChX5Qnwe
+eJoU0S2Qc6W9jL9qyOw3U+su2/oPzTk2xRu1CwiYLeNjZSNYhU9Az78CsvNrZUUK
+CmiZrkmN8tRlFFps3TaF/fodwuYfWPC/R9WpKbtaqjjz3PqXHYbh5NyURVw/EqvM
+y1yP26PsQn41tE5Ebndl6P2YzjAZQLKNTc584BXq7Tqj55jeeH/sS2XXv5gF2S+t
+m9+Nwyuavl1mC5CNaL+KbkX6w/OadINUOArQW2HC1SwqP184fN9cJCx3NeB24kKg
+84M42qQPUOIHfiu0R06JKaPWibk9WAU6ssQLcrbRs5NZ0ySqJWU0tpS/W4Zlz1Yj
+Ytnce0VAbz25OAACZ0adKnWgKv8OuQINBFiGv8wBEACtrmK7c12DfxkPAJSD12Va
+nxLLvvjYW0KEWKxN6TMRQCawLhGwFf7FLNpab829DFMhBcNVgJ8aU0YIIu9fHroI
+aGi+bkBkDkSWEhSTlYa6ISfBn6Zk9AGBWB/SIelOncuAcI/Ik6BdDzIXnDN7cXsM
+gV1ql7jIbdbsdX63wZEFwqbaiL1GWd4BUKhj0H46ZTEVBLl0MfHNlYl+X3ib9WpR
+S6iBAGOWs8Kqw5xVE7oJm9DDXXWOdPUE8/FVti+bmOz+ICwQETY9I2EmyNXyUG3i
+aKs07VAf7SPHhgyBEkMngt5ZGcH4gs1m2l/HFQ0StNFNhXuzlHvQhDzd9M1nqpst
+Ee+f8AZMgyNnM+uGHJq9VVtaNnwtMDastvNkUOs+auMXbNwsl5y/O6ZPX5I5IvJm
+UhbSh0UOguGPJKUu/bl65theahz4HGBA0Q5nzgNLXVmU6aic143iixxMk+/qA59I
+6KelgWGj9QBPAHU68//J4dPFtlsRKZ7vI0vD14wnMvaJFv6tyTSgNdWsQOCWi+n1
+6rGfMx1LNZTO1bO6TE6+ZLuvOchGJTYP4LbCeWLL8qDbdfz3oSKHUpyalELJljzi
+n6r3qoA3TqvoGK5OWrFozuhWrWt3tIto53oJ34vJCsRZ0qvKDn9PQX9r3o56hKhn
+8G9z/X5tNlfrzeSYikWQcQARAQABiQRbBBgBCAAmAhsCFiEE60wb/U8EL23dzOyR
+dyH2O9OLR5YFAliGv8wFCQWjmoACKcFdIAQZAQIABgUCWIa/zAAKCRBklMbWmXwh
+XluJD/4mavm5UQ84EczsNesfNL8gY3zzlCnfvnUlJHK+CoYub4wcoDXVUlnCmWgS
+lZHQZgr3/qfW2MM3y/kXcbxhL/FijUzY3WlnCdnIVNjuB+QJt0LHbkP7En/o085Z
+zHuzaXxfZ97qN+KPsRBTjnJ8hd3B64cVjgnXva1+pG51EK4iDF2bXiWPHvUbPiL+
+Og6C9XjpWrwIA1CWyH/4i7dtfTnbViO2aqKQNHfrXJ+xS938Lr8r5+VmUWByHqwe
+BGIASOmwsJeSUHozkZYbmMdaJJ8j458zyfS6LO+HIa3+zhzidOoiEH9c5QvVf54g
+NsYjPTcHj7U0DgkxCVQeiBKBLR+q6M6QHa4qax/X0Z2ZCcSDTZwqGJNaKfcFYd8X
+1B2zgrxkGweeHKjfmpqfXRKrggHumLdVqHU7KS9cz1yeTL+Nw7ne+kzRMEA8sLnm
+4ODRUJwUz12RqS0GG1FYV0rjJVWVzRFMfMUs+7xAptEuMdoddkQSmytkXyOKAqv8
+KQ9XUEbGWikmCxW2cOY9spOpwQa7X2oXe7FlV9RfmHYrG03k+YlIREgFqlvWwsgp
+zURculd+CIFvT3vci7vFm1UiQBb5wC8bHOoRsr7OXW1267lipouZr5OrQhVnRZQV
+a64cdUIKjLXEt4790uxh8ggNwktZRILIn2JHjgEQICdYWeQb1AkQdyH2O9OLR5b3
+MA/8DRZi0s7SLQwaQiJrT7GrACsIMjYo6SapUVxDMF28QfANW809ANpq2Let+yAD
+mEibSgpiDiO7rq6PvYnHmPyxmTbEwMtm1bDi0j55/TybnNN6hnUo8F+o0ywCJjfo
+T8GDuBX50ODoOYUMmIoYwyMz/UtNi8iHtxTBPR5b7l1Vt8EfUb3wrwGa4i22mjgL
+KU49h7Oyi1VYZRrM+0hlrmaLF79tT9msDnn83mgq9qefkJuU4nBqUXui/CY5b8vJ
+XC+8tD+q1wCiUM8uv2LJs/5JyK80zFJbkBXA/ZCYtU0LJEpUf7HjbIAdCMDWjpc4
+j+IyjU+Axv+NkMLgYRhaadnPRVzqY8f2T2Bs+EQWk2i61BVQMqakGtwBWIMCp2fn
+GDCxIL/FCN1kIA0J0h9ommhMgZdOJaAktsddr/LwVh/hcYX8Mfy94vPs+E3Kb6Oi
+iwPkkN6umQvdFa9Rhh9SUNvmtXzMo3WELLobtvVKC+fdFVatDsJurTRKLDKEvPjS
+xFlJ/T8t9yItTBAZ7+ab4nJhWoEbzkVTgNizLCJNmdAEtiKa9dEZOZl0DVmxBhB1
+aqMfHA3S5UhZXmGBHwCF6PcpnM3C4XY2MjQ/sRxdFa7/HFBKOO176h6HyujQ/AyO
+llmvJCCg9Hz0Wk0tjTMFsnAbh7dB2GTNQwBNZ60gUCWR+mG5Ag0EXTX8rgEQAKyR
+kvTxyusp9fZoPbDw5RLeNUZJbsrXQmv92CXpkHtfH/Ldz2WEGKbuhEiyXq2lH8ME
+/nRSdMiAFu/Kdsnq1tYam23rgDOcjt6X2kfSTrcM4px+pFSAkpMzg5RlKRy6pDaq
+eS+f6DSiIndWFpVg4l0l8kX+kuPk6LdQQvZp+gR3Tjz+VkRoBNG8SouP6HalJ8RM
+SXnAJbJGe4xK7prL02ZXNHGImE8MZbamlBPEm5oqP7pWrDlYhK72exHFM8TUNbx/
+stjI8HCC6W25JgpmgJ1+hgTx9/jvWhki4IpwZJIEdBtHowFMPoom2rMHOl8nzNkm
+ZU7iWDQImCn3FfZBnyE+SloFuerYkIxLXOuIIw3yIaFbpkdiZlAm1a65u5m3nVUv
+1CYRRSEIXW37eV3XVJqjBjg0UogtR1hsLbMA5AgQQmRZEgcqV65zbNhI1KheXTqg
+aDAIpBvmX4uVxgfHj78Xf4rPICrQ2oELWsyeFufe1xyR1nKEsSmfH3/LffKmjpln
+Szp0sauZKkml50TPrOvyyIFri5Pci9UXjGN+nNK3dwwP8vOFueTmidR+SagKZD+m
+S4qkyvfmEe10PGyEtws8WROdwyMRUA4FOgcNsoNKmW57ImbjwQs+L1ma7I27tawH
+xNZUQCRRKHF14cAtWljUP4yNcr5nlqnr+2mmP5+bABEBAAGJBFsEGAEIACYCGwIW
+IQTrTBv9TwQvbd3M7JF3IfY704tHlgUCXTX8rgUJBaOagAIpwV0gBBkBCAAGBQJd
+NfyuAAoJEHi9ZUc8s70TzUAP/1Qq69M1CMd302TMnp1Yh1O06wkCPFGnMFMVwYRX
+H5ggoYUb3IoCOmIAHOEn6v9fho0rYImS+oRDFeE08dOxeI+Co0xVisVHJ1JJvdnu
+216BaXEsztZ0KGyUlFidXROrwndlpE3qlz4t1wh/EEaUH2TaQjRJ+O1mXJtF6vLB
+1+YvMTMz3+/3aeX/elDz9aatHSpjBVS2NzbHurb9g7mqD45nB80yTBsPYT7439O9
+m70OqsxjoDqe0bL/XlIXsM9w3ei/Us7rSfSY5zgIKf7/iu+aJcMAQC9Zir7XASUV
+sbBZywfpo2v4/ACWCHJ63lFST2Qrlf4Rjj1PhF0ifvB2XMR6SewNkDgVlQV+YRPO
+1XwTOmloFU8qepkt8nm0QM1lhdOQdKVe0QyNn6btyUCKI7p4pKc8/yfZm5j6EboX
+iGAb3XCcSFhR6pFrad12YMcKBhFYvLCaCN6g1q5sSDxvxqfRETvEFVwqOzlfiUH9
+KVY3WJcOZ3Cpbeu3QCpPkTiVZgbnR+WU9JSGQFEi7iZTrT8tct4hIg1Pa35B1lGZ
+IlpYmzvdN5YoV9ohJoa1Bxj7qialTT/Su1Eb/toOOkOlqQ7B+1NBXzv9FmiBntC4
+afykHIeEIESNX9LdmvB+kQMW7d1d7Bs0aW2okPDt02vgwH2VEtQTtfq5B98jbwNW
+9mbXCRB3IfY704tHliw+EAC5FNOwkABxZZ1C8K4wUDl2Oe7mewVRhVNqvTWS4uib
+vFax78HDyLNqKmfi+yRHSQsDAkKr9GzmBc1DOabp4V+IRwj0vADHbcpwoGM7EJ2G
+o/0RtdZiTP98B8DMACu17NwjM1l5EUExqjGEeXp3jEZGMSE8vqjq8djkvl8s5mUM
+j09Wpj3Gl464NNQ/gnB0P/2sp11T0BVb2u32zNLJKh0ZP9QxXT3z93UBOeiT9BzR
+hqFMyl04xpt5rqYDUdiL7y+tZDR28INZZ7aYsCs4NkA22Fh6nI3v43Us38+Kroru
+09ipLE8A5fx3G5LxMwtWJA+zZisrrky86JYEFOULGpFuKrklP2bRyaHePjMeqOzD
+Y5/n5unqk4+EZAPWIM4LFOwDtTD1BWmuDdpP/RjPuPZUhoMSW0p/Vv/FuBAnpgVQ
+9D/kXI3xaAxKgaPp+AzQN50dCosmn643zAGrZTiIDIp1VtXVRFAVinN/mbJkqQJv
+8zM/x0bc6EUNb/K8BP/JJp+x5D13DjtXYUEG8TFHz6YKZe9QzlhK5rZY/Fttwqvy
+KvIKanXEjOf5/azkdOGlSN6Z74G4l22tui3y3CM+vmRrlMiBbLkCTuPfw8rS6uzi
+B5No8PYBwovbqNvpm+dGNHySFTvNyJhzWmvCVt8FZ+c4tqOmwd/D+fhon0Pg42bu
++bkCDQRheAyfARAApNhsGrvrP6Spjk5xizJwd8m0LIlRi0YbMNkqkk70sgbYQMlt
+VAKnUajQPPxXTJb1bqaRvPrwi1z5qT+twvvTNrckHjkdmlUKfrtRCMDeJT7uMK4e
+r3bYEkYpvLsQXSyBxtes9McVYRNqzPzrf4LnH5KaBMNvPVWke7D5iMX1U5tUHKgh
+ohUJd62Z5mugc/FDlyaBPMDviyuVpHHZhc+vmdwS0m+SC/ZYbAKxU6DauXTdkkk2
+wk3R0c60bqAnXn2B3caCwjOJCX4IEUYFoSqBCa6PmYqREqtU+ch1f4gCcvtw7gvC
+22C77I7fVWWAEcPMSBm/dFY904VrjKFa/yFZik+36AuVoXtD0yP29n6zWlgscQuH
+EVcTLrIgV+upnJUODL88I+dBtVisoFC2HLz0PNU4NKb4EyqoMcC/ZbjfTIg1bZJ/
+QmcezRZbM1a/onO51SYwDZyXmxRwhGXyW0KOLiMCn2G4aKVJAmuNYl6XrG1cwCqj
+cHj4MjUwDBcmJ4wFBPBVVJse2SVW9eYhGzLN/ICSif1m/MLSUX5QH5IaxM4dTP+N
+1lAFN0Xz5l06xnsgwmCkx4l054++PLh+lONLAfavqnhIWXU49Crn44LVmhVrGU5F
+a7RjmiOsX1+qcv5N4Y1N3rPu3XRJcYTwXKjRN6ZD0am/cM/nsUnTO4YlMzcAEQEA
+AYkEWwQYAQgAJgIbAhYhBOtMG/1PBC9t3czskXch9jvTi0eWBQJheAyfBQkFo5qA
+AinBXSAEGQEIAAYFAmF4DJ8ACgkQTrJ9sqO4i4uCCQ//Ug1HJFOguZjWaz0NNYxD
+SXBsEvwnfG7+d4og4pUY53D3NxaUa6BSg62FJtPxuO+7JsfVWPHjAUz5ye4xV+MP
+nxe7pmmAIc3XBdgy7NjB4EUpoyDihLBMq4AkEnYiF8Sb9wCvJW8pjbNj67LOCLPH
+e8CDeyOQA8NytIIk/aeS4dwnefNRso0COZ0yydYOuqplXA/32e7IyTxsC255nRIq
+8ikK/bAh5g7vOSPrW+5A4U4aGX3w4G6LnBSG2BDD/96xNZiIY0pKYPd16t3YkdUD
+TW0GYJZXgowsNuDcJwwxDXHdXWZ7oQbeCLAEvUj3FOwFRsRrp4Q31TTN0q+gxtKi
+A43nAK7EDM78JcYyt4m0FS6kcRzr2hO7B7jboiGLcBtGs8CDe2cYYUK3XUehAU2d
+E9Zve6cXxSUDatLK2/AXJCLenMFi3lWxMgDs0Qca4mz786ivoA4ifOG3VynsB+YM
+Z8bLY3mjD7gYjoU97ZSoiDb6cWIav2FFk69dGAtAvx2UOcUKHKaV3Gb8n9QV0kZJ
+ZGV0QOw+vMdARIq+xX0SOclBHmnnORArqPHTOpKUOCI0bYZPf8JK/Ah0KKHoKX0d
+OEe1g2bdlg3RtT1baN6guHcAg01NyunS0Adm5AsXG6RuPno7l4H6d+Trv9faI2KL
+jpl0lA3BtP1g3oKy1DP4KeoJEHch9jvTi0eWxrwP/0zlWCYOsNH5Id4SZsPKe8im
+evCbj3lvboTYPc4u6HvbbwbYqLerzP2ajWSCdUAK4CMrAuvFildo4k6COh6VaZdi
+DOwsKoJfs6Vd5oud5a+jRnv8+oktRBf5OAVc3RLfBG1RC9qI891JTOjGrTU7dBJr
+RjRWdy9YQd/epN2I0RVtUaJlxKELoFj57FPERZgg+yomiheBARK+fLYY/oFTwJK3
++Kt3rdnBtUeVpEiL6VjU6bqvIpUG+P0u27AspcacgDewg59+thcbY4tnsdo6DSZB
+Q92bBPVGzpXPEhpQ/vZM63CG8qsZfQ1jw82ovmSnkKPLnBQRabFYVl0DCl1uYHg2
+4Up66w6Lj/tT2XbCeBf2n54K9HoUMV9f7/pLoTa0dE3UYI1K4GLZdp+yxMveUEjG
+nh0YOTBmoBtpdy6Udejujil6xbH2gLwbICFm+boKVWwzrYCyfl51ASiq5dmqQwd3
+tPAg9Hc6qtvZ8cswyWyNOQpZo0myvfPaKrHWa9u2GqQmeGBwhckXJxFM/zau0yx6
+NMkSFI49kTglw0A77rcmlJUAQQeoXmTKMl6NM/3AUfvL8Qfu9/74kgoFI9pmQFky
+BtcQMCeB2/JQ9K9ywPhi/gIebjftfMgKQsTW+/6Nl1yZ8q38y2n1J4p/acVlFc2K
+PhbmKL4CvcSdlQS4CbvFuQINBGPs+VgBEADKbgLL+vAabKV2rGSDgY+IttTAtg9w
+9Uor1+Q/CIWGxi/JQy7l7XTKjmS0wvdwU+9f/eGsjxigbvAcSsV1szyKfVQQFT2m
+9KhDrBqNCAvQ5Tg6ZQdNe51oHwjiIQ1i7z8QoT22VucdTYqcMLAHe+g0aNqLLSSW
+LAiW4z+nerclinjiTRCw/aWZJR1ozQd2eKwAw6rk19bHcihXo2E0K1EDmdHcNA8y
+typxwWWXBftCYRWXi5J02GeZazxmx/DULnFgy2J4G0ULTqGWsbf/tCt22jqgyX+v
+Fj/sJPn+l3IJqpyNY5yBG6GcejeP9vRoQrapGqHkcx+37f2vjwmpj5548JI52KEC
+1yZeFwp8HjGLp+zGajpnokrKd4XJHniW9+bPLq7Yp7PNn65MaYvZUjv5enKd45fF
+K6vJ3Ys/fx6PBXKKBs9flRIgdXOKSvtV+bGIG0I/p/JEZ/wPxRgxHPDK5jbcI6KB
+Vm3Uk+CHFC4IBAtzdSh6H4Zfw1EH3dQZMLVBB/Sj34UQhlwAOlAXtZH3vks/Kpcl
+WK8gnqz3i8HN0ezvcnQlRiRO8IqlN9/PmFqZeNTerklT7Tt0jXqiopLHL0FXR2Ls
+ndeORfxDE1rhVOUxloeuIsY8x6gO8h2bGg41YapROjYxZZEcakg9Nch4XAlxeqB4
+ISttfbiVxeL2DQARAQABiQRbBBgBCAAmAhsCFiEE60wb/U8EL23dzOyRdyH2O9OL
+R5YFAmPs+VgFCQWjmoACKcFdIAQZAQgABgUCY+z5WAAKCRDoiXn7mzCs8kblD/48
+yE3Wpi6Cw8RBzq2uzLdkuqXh691zG6VhHUZQNb85ewGjGDu/D25u2JFrhAcmlzOr
+xggvL4a8WatPXQaPqDZaSh41elM1Ya0C7cNQq7xNVA0pcN5bQ+KXXZMuQaA89BCl
+TSXITz6j4O4pvhAG8y8Q2E9Mv7UYas0OhDgzVIry2s1o2Pml1qjlb9jctO9crRUi
+F6v9Ru9aQkgGHYt4uyP3HzKDfoNuzX/WX3O0Fm8NNpnJk6qZsLKwg7ukUdJOIEIb
+LLNLU9ZYmys3wNtDKMfm4T79abSNwNIn4dd5hapH9BAuDJnk4WnFOap9AQZPgJX2
+WXKC2DXQZeSX1VXpI3rr7FSbSec8d5bitw7s20XWyQB2+ZoetRxNgR104GIh/Laj
+tatLKFc9NnP9Smhey8nrxVZFx6HuXsnGOPkbjsiFYMsxtPVYnO72nBDTDP4ZejLO
+aay2KtCb8pJkCH8U0guquDGVd+S02Xx947evyvHqGt5V0yVFPD7uAu7A5QBYXvtc
+tzq93S1jZDIoMP93Oe8VpUrXBBfizzHVxP6VUmxM97IE+gjVRqN9PuMrp2D9yEBU
+Gk44fQW5zyuuomYac7Mpx2fnWgGA/Al9ug2uvS4oIzUyLEJxpc6M8RYluacSIjFg
+CigucRsvTBy6lobG1FMvnQyze6+fAeKbbrK85OuA1AkQdyH2O9OLR5bPGRAAmgSi
+hpu4US/JoWnR/aeiFf9upobXVDnBnqOAXiMUaFeS+hUuh5EWUhDLIWYvXXhPacvb
+pUOlxwLsLIdPRQGGSp1/rqhVRnmWsJ34DoAKxG7Elq8EArK/pF+v4wSUMegjAPJQ
+evIcLvm83z+jHmbk1AEeioBYTq45RbzlHmyLmGK/zT13KnBUWE3sFkECoco+vMli
+8oPeL+JMfiMgPb2vDs+58YlHq5W26pe08BwGzY5LQM7Jt52oxsqgXEX/N95QqgSc
+sc625wCIE8/Qo5pXT0TKk+5ViFojs2Ei3mgXHBXFgISdAtWBEmqN9TESqPPrHzfn
+Fk9t6mPg1r5Nt37IKO7oTzu7/SXrJlXPIQ99Nlq6HO/mMVdYjbWFBPw8+NGVGemQ
+chOODZsksvHJGV4gjMpW1FC37MRNsiai1UMraVxzsrCte4/oqpa7bY8VdWw6p5mv
+fdroLkwHW2cS2lgC8ft7e4npiHXXLAIib+sFHcrIkZu0uJxGCJOkUwkaDrAFKWzZ
+YHc2YUrW5XN7CNBo/fe90r1W9/4esn59SM2mTMarrUn1fiExwFiUci4U+3/7U4Ii
+ViNeNoZ2J1+hqxudlx1OT7Ae2Wg4dLASoEHaMKby4+JVVicA8jdlocrCbpEv1hVV
+47hwiKc+VTQGvCZqs8eT+pbnw1Recd13J9Ny7bO5Ag0EZbladgEQAMSm1QPtyjAr
+XdM1i2Y6439Jc/AJy3ykVjxTaDi6n5z7lgQipaQBSpWbwun4Op0W5fs1t8rYE2iP
+A/KKoqVoEA3o3Hts71uNK+VttkGtUneYv6TvGsV1MYt4NJJOUQF6yPsVcrXMrtJb
+0BXefjmWY4sBdMLXdVDcrRIRdv7r0XBevfX+Lng2BN8z/UtwlmEihHoy60ckJJgq
+47pkfFho51+PjwEZJaPtEgRsXn2sgTMNHukGTrV8ub/aKWVNBPF0wYYF5LA2NHgV
+p148nS11F4OgiNpCkAZmJQCPlyp4emYfxkihjh+TZKw6KcrxwOCx7YeceKK6wWvr
+HHrwjJxl2nhatDIYNIlnVkqTlBp4A9gTdCxmciZ1xXb+QllLycBYMWgu2lo1Kk40
+NOfVljIKLatY88XwmJUySYLGyX5kePI29kc+yVGycYHsSgoOlyM/Vw+GXfuj/BRi
+nKItjITxb6YM25wfhgctUer/NAao7dXprFMDUOz6C720dX/f7ISsiqmi7X1U588o
+mNgLvJ/O8gPnyMtk1gWrwhFZDlVYI5AlYxx3MwoHntLZlvm8iEmR+X9LkhIwZcNd
+vfafIpV+8LlOaIxt+uzNzcMsDHCGomUAf/GYXbI8/x1iHoopZIh99UZObfyxyz2S
+SbVtUEBHXyKXHp0bFWM1Iz2LfQwxeNRRABEBAAGJBHIEGAEKACYWIQTrTBv9TwQv
+bd3M7JF3IfY704tHlgUCZbladgIbAgUJBaOagAJACRB3IfY704tHlsF0IAQZAQoA
+HRYhBA8G/4a+6vTnGGbuUjLuU1WmvG5CBQJluVp2AAoJEDLuU1WmvG5CmB4P/1Rn
+XKHryp3UlaOAq/UAF2YKFS9NAggVwH8PhsFc6nZpruc+CFU1s5jwCuW9aiWgQ+Tj
+BFvQ0h/bHLbujlTSmfyyyo/Ij+4vSxRzlmUa8lHPqyqv7fIsQ82AAs8WE/mV8Dif
+24hsxJSZEH130DTkRqtnXS0FB6sOQPGj5EKAFt3v0vN/Z1QRX2eLmZc2jO7QfkdR
+strvF3borb7xdt26/PM8g8RgYaG+fqIJ/NtGQF0XI+WUxuQ+mtRGEyVpL4qnwwno
+kyxjsMxsJvvGIaPULKR1CahGJD4tAlyE3DvNikMRI2SDojaGyh5cw24mJJVZmx46
+7Q3tE4dwmAu8pCGCldUQBG6eprTL/WauyJcmkJr1qsSK7gyx+Uy8mwXESY/s5bwD
+kzhlzaJ0WjBxqXfoHFIElHJfhLS0efqIr6NFmPUu4cBKJKoZoFBwTPTTEmWz7tE2
+mDgVO9Z6Q9fq7CwZS6J/GchieQgAy3Rxm5BizBZsWisY3BQ4JX1w6wH0Cae4rYCe
+bkutFFWBg7JA3j2nkgfzsD3kYHYf5BllL2yV589dEocNjPios56vPi5kg9UQOFO1
+SaX4Efu1eArNcNteBxKf5pH8okDcgjqj9yXZRs6fI2Uk9zzz0UL63+iRSqSj8Kv6
+iepLCzOph1DHnY2tFghpSFYqlayhdprMJVk7GmLFoiYP/1nT6wq8k/RDS3/W7HEB
+J8Rtxs1vL51nU0e5K7jgbUT9kaG2KBmlnRbgkELjvu0lX6zLFiyPcc5JkvE2AyfZ
+7t5cIfanOS4hc0W9C66RQo2cvUxkn2gtCrM7KCTc16Iwe/uMC2RNEneNLiCetwc5
+DhpjYExR59szzQ9Npx31pefsmkSwKdutEz8W96l29yHYgIDoLYW3b6nuBRBfp4nA
+XQ1gWqfEmFNFlKZBa2pPsKNlFgpchC+EiMQ/db1ElVNyW38K7IOx6hNGpEBJwbPu
+HNef9WU3n2DIIgMBHTHPvbNHiCNTfuOM1+/BMbmK59RmW66TS0UaxZsswHHLZt7v
+NN7SKzXsveT9+A1d6wZlVoy8Y3gykBKnBHGRaGO0zaXczHt4YsUA4L3is6lAjbIo
+pU5M3j2F1RFKRr95+HZT/NXNeGbFvsdKmvP4ELtDAuYVMgYR8GqjI5yP/ccVMsi/
+mhT+cUxO/F7+7nixw1Go637Jqr/NF5kjjrBD8EiGy8QrGm6uBR3NGad0BnMWKa2Y
+oYKF1m3Fs/evBkcymR+hSwFzkXm6WSOb8hzJIayFa6kAc7uSKyR5iG00p/neibbq
+M1aUAQDBwV7g9wPmcdRIjJS2MtK1JXHZCR1gVKb+EObct6RJOVw8s58ES5O9wGZm
+bVtIZ+JHTbuH+tg0EoRNcCbz
 KEYDATA
 }
 
diff --git a/chrome/installer/linux/common/rpm.include b/chrome/installer/linux/common/rpm.include
index 29438f4..31ba8cb 100644
--- a/chrome/installer/linux/common/rpm.include
+++ b/chrome/installer/linux/common/rpm.include
@@ -92,192 +92,227 @@
 Tp8Gv9FJiKuU8PKiWsF4EGR/kAFyCB8QbJeQ6HrOT0CXLOaYHRu2TvJ4taY9doXn
 98TgU03XTLcYoSp49cdkkis4K+9hd2dUqARVCG7UVd9PY60VVCKi47BVKQARAQAB
 tFRHb29nbGUgSW5jLiAoTGludXggUGFja2FnZXMgU2lnbmluZyBBdXRob3JpdHkp
-IDxsaW51eC1wYWNrYWdlcy1rZXltYXN0ZXJAZ29vZ2xlLmNvbT6JAjgEEwECACIF
-AlcMjNMCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEHch9jvTi0eW5CAP
-/RELE/OAoA4o1cMBxJsljWgCgDig2Ge91bFCN0vExLcP0iByra7qPWJowXDJ5sCj
-UBnCkrxGo5D15U7cW5FC0+qWU73q0AuG3OjKDQ49ecdRkYHwcvwWQvT5Lz3DwOGW
-4armfEuzWXcUDeShR7AgfcTq+Pfoo3dHqdB8TmtNySu/AdJFmVH/xTiWYWrOSibh
-yLuaSW/0cTkHW0GDk06MlDkcdkTzhO5GMDO7PUxBgCysTXFR0T9TVWDo9VwvuMww
-2pE5foleA0X6PD/6GQpy3aX2xry8rhFvYplEa5zwXhqsscdKXlp1ZPZ4PMvvwe49
-5mY9n/1Rx1TmMvIcLHKP61sURMOve97Gipk/iD6oaeeT8I0khexHCQy7JMROoPMr
-z5onVOt2rAGZScIZsm5FYGSt9eDKBWI6qpJ/5QoVhkRWjOXOchZlJHo+kLdg6jq2
-vOnIlFnXo0p6Rqf/IEq5PMh70vVZpk4tNYNy4zRx03ZTA9qXRLW+ftxSQIYMY5eC
-Z31lqSH4EjqgtUG+zn2A6juKayb1nkt2O3F1wWOm6oTzNsAP5LdReJRlw151Jp4U
-4ftGtw7ygq+nvokXL7YLuu8sbFqfFXcTPrAZa5M9gnC7GCnIQyF/WvqUnrcaC1jp
-qBc+pkSJhROhN12QY8Po8AT8/UaUh/dPIiW5A4o8pOPEiEYEEBECAAYFAlcNtn8A
-CgkQoECDD3+sWZGy3wCfWTMZWsipX+yG/VB4Q1FunIfEVHYAnimEXCjZ3IVyy5F1
-yU36PihDCjWqiEYEEBECAAYFAlcNtvEACgkQMUcsOzG36APnRwCeJ/bfGf8FBa4q
-5TMw8p1GS1jWT5EAn2sc02481HHdTmZiW/CGWXmgE+OPuQINBFcMjcgBEACrL9gH
-hdr6gQX4ZMA5slp628xOrHCsdLO54WNdPRKeFHXJqSSJi3fs8FxBWI4FnejeKUGb
-F+MrOlFpKqELxaMje7bwZyap3izztZHszP3YmOoTBJvREGKdCkL82cLsChYD/Prg
-E8crvkhSnq9evcsKAnziMxg/wDCChUL3Evqo29BeoB81f+E9wkrUTMCT/kVxt3pG
-RalKX0UhrtKrpm8yRfjufJfwjkdwgvinkRGZ2GrWHj4LzMbi9/udYaJZ66Yw0hEU
-4USxUB9vNtmSFrb4EB91T2rhc68dgQ4jYBI7K4Ebb8XaWAxb+IAq31l1UkiEA32F
-4qUMoL6rChB4y6nHxOnTvs+XEb5TBwXVogjLRKTQs5U/HV9l7j+HAchk5y3im2N2
-UKmMxHqotvPZZUZPdaCRxUedQf9gR0yLZV+U9BcDuwjzL/zjrthNZYlEGJ6HZ/TL
-STp4dDH+uXuLqMVWy5iquKtnbrnNTQtv5twD+Ajpgy60YLOJ9YaiJ4GjifOpzSk8
-3e1rJ3p/pX6B5NWQinVLZJzxyeOoh3iMjdmCDSnEXLrCmYv5g6jyV/Wbd4GYFuMK
-8TT7+PQdWLcbZ/Lxc5w0s+c7+f5OfmKXO5KPHnnUsrF5DBaKRPjScpwePQitxeIg
-lUgEMDkNruBhu1PzCxd3BtXgu++K3WdoH3VcgwARAQABiQREBBgBAgAPBQJXDI3I
-AhsCBQkFo5qAAikJEHch9jvTi0eWwV0gBBkBAgAGBQJXDI3IAAoJEBOXvFNkDbVR
-QSYP/0Ewr3T7e0soTz8g4QJLLVqZDZdX8Iez04idNHuvAu0AwdZ2wl0C+tMkD7l4
-R2aI6BKe/9wPndk/NJe+ZYcD/uzyiKIJQD48PrifNnwvHu9A80rE4BppQnplENeh
-ibbWaGNJQONGFJx7QTYlFjS5LNlG1AX6mQjxvb423zOWSOmEamYXYBmYyMG6vkr/
-XTPzsldky8XFuPrJUZslL/Wlx31XQ1IrtkHHOYqWwr0hTc50/2O8H0ewl/dBZLq3
-EminZZ+tsTugof0j4SbxYhplw99nGwbN1uXy4L8/dWOUXnY5OgaTKZPF15zRMxXN
-9FeylBVYpp5kzre/rRI6mQ2lafYHdbjvd7ryHF5JvYToSDXd0mzF2nLzm6jwsO84
-7ZNd5GdTD6/vcef1IJta1nSwA/hhLtgtlz6/tNncp3lEdCjAMx29jYPDX+Lqs9JA
-xcJHufr82o6wM9TF24Q8ra8NbvB63odVidCfiHoOsIFDUrazH8XuaQzyZkI0bbzL
-mgMAvMO6u1zPfe/TK6LdJg7AeAKScOJS38D5mmwaD1bABr67ebA/X5HdaomSDKVd
-UYaewfTGBIsrWmCmKpdb+WfX4odFpNzXW/qskiBp5WSesKvN1QUkLJZDZD1kz2++
-Xul5B97s5LxLTLRwvgLoNaUFr3lnejzNLgdBpf6FnkA59syRUuIP/jiAZ2uJzXVK
-PeRJqMGL+Ue2HiVEe8ima3SQIceqW8jKS7c7Nic6dMWxgnDpk5tJmVjrgfc0a9c1
-FY4GomUBbZFj+j73+WRk3EaVKIsty+xz48+rlJjdYFVCJo0Jp67jjjXOt6EOHTni
-OA/ANtzRIzDMnWrwJZ7AxCGJ4YjLShkcRM9S30X0iuAkxNILX++SNOd8aqc2bFof
-yTCkcbk6CIc1W00vffv1QGTNjstNpVSl9+bRmlJDqJWnDGk5Nl4Ncqd8X51V0tYE
-g6WEK4OM83wx5Ew/TdTRq5jJkbCu2GYNaNNNgXW7bXSvT5VINbuP6dmbi1/8s0jK
-JQOEBI3RxxoB+01Dgx9YdNfjsCM3hvQvykaWMALeZIpzbXxV118Y9QQUIRe2L+4X
-ZACEAhWjj2K1wP7ODGTQrrM4q4sIw1l3l7yO9aXXN7likAAddT4WEpGV0CiorReO
-J1y/sKJRJSI/npN1UK7wMazZ+yzhxN0qzG8sqREKJQnNuuGQQ/qIGb/oe4dPO0Fi
-hAUGkWoa0bgtGVijN5fQSbMbV50kZYqaa9GnNQRnchmZb+pK2xLcK85hD1np37/A
-m5o2ggoONj3qI3JaRHsZaOs1qPQcyd46OyIFUpHJIfk4nezDCoQYd93bWUGqDwxI
-/n/CsdO0365yqDO/ADscehlVqdAupVv2uQINBFiGv8wBEACtrmK7c12DfxkPAJSD
-12VanxLLvvjYW0KEWKxN6TMRQCawLhGwFf7FLNpab829DFMhBcNVgJ8aU0YIIu9f
-HroIaGi+bkBkDkSWEhSTlYa6ISfBn6Zk9AGBWB/SIelOncuAcI/Ik6BdDzIXnDN7
-cXsMgV1ql7jIbdbsdX63wZEFwqbaiL1GWd4BUKhj0H46ZTEVBLl0MfHNlYl+X3ib
-9WpRS6iBAGOWs8Kqw5xVE7oJm9DDXXWOdPUE8/FVti+bmOz+ICwQETY9I2EmyNXy
-UG3iaKs07VAf7SPHhgyBEkMngt5ZGcH4gs1m2l/HFQ0StNFNhXuzlHvQhDzd9M1n
-qpstEe+f8AZMgyNnM+uGHJq9VVtaNnwtMDastvNkUOs+auMXbNwsl5y/O6ZPX5I5
-IvJmUhbSh0UOguGPJKUu/bl65theahz4HGBA0Q5nzgNLXVmU6aic143iixxMk+/q
-A59I6KelgWGj9QBPAHU68//J4dPFtlsRKZ7vI0vD14wnMvaJFv6tyTSgNdWsQOCW
-i+n16rGfMx1LNZTO1bO6TE6+ZLuvOchGJTYP4LbCeWLL8qDbdfz3oSKHUpyalELJ
-ljzin6r3qoA3TqvoGK5OWrFozuhWrWt3tIto53oJ34vJCsRZ0qvKDn9PQX9r3o56
-hKhn8G9z/X5tNlfrzeSYikWQcQARAQABiQREBBgBAgAPBQJYhr/MAhsCBQkFo5qA
-AikJEHch9jvTi0eWwV0gBBkBAgAGBQJYhr/MAAoJEGSUxtaZfCFeW4kP/iZq+blR
-DzgRzOw16x80vyBjfPOUKd++dSUkcr4Khi5vjBygNdVSWcKZaBKVkdBmCvf+p9bY
-wzfL+RdxvGEv8WKNTNjdaWcJ2chU2O4H5Am3QsduQ/sSf+jTzlnMe7NpfF9n3uo3
-4o+xEFOOcnyF3cHrhxWOCde9rX6kbnUQriIMXZteJY8e9Rs+Iv46DoL1eOlavAgD
-UJbIf/iLt219OdtWI7ZqopA0d+tcn7FL3fwuvyvn5WZRYHIerB4EYgBI6bCwl5JQ
-ejORlhuYx1oknyPjnzPJ9Los74chrf7OHOJ06iIQf1zlC9V/niA2xiM9NwePtTQO
-CTEJVB6IEoEtH6rozpAdriprH9fRnZkJxINNnCoYk1op9wVh3xfUHbOCvGQbB54c
-qN+amp9dEquCAe6Yt1WodTspL1zPXJ5Mv43Dud76TNEwQDywuebg4NFQnBTPXZGp
-LQYbUVhXSuMlVZXNEUx8xSz7vECm0S4x2h12RBKbK2RfI4oCq/wpD1dQRsZaKSYL
-FbZw5j2yk6nBBrtfahd7sWVX1F+YdisbTeT5iUhESAWqW9bCyCnNRFy6V34IgW9P
-e9yLu8WbVSJAFvnALxsc6hGyvs5dbXbruWKmi5mvk6tCFWdFlBVrrhx1QgqMtcS3
-jv3S7GHyCA3CS1lEgsifYkeOARAgJ1hZ5BvUurUP+wb66lIhDB0U9NuFdJUTc6nO
-/1cy3i9mGCVoqwmTcB1BJ9E1hncMUP1/MvrAgkBBrAWJiD2Xj9QV/uBozA7nLxrV
-7cf1de9OLgH4eNEfX25xj8BBPYnyVyHsyk5ZHDhjj9SaurfvlFWYi13i5ieMpyLV
-JV4+r2Wi1x1UgKVAlB78sHYnbDzSoHPLBcIxtIKp30LJ0PEkat8SG7G2wgtv1Rdh
-mcZEBV05vMnrGGO991e+pKzRNPYH8rD3VQKJlvaFwsJuBTW42gZ3KfpUNKI2ugCc
-nRNpoHFWNCrzlJ0CFI48LMlmUSs+7i/l+QGleaLKQxRTNNpAmevLrS7ga4Iq0IEq
-xey6VW6RSk/Z1Z37J8B7PISSR0rZn6TeyQgFWf/FOLw6OtwOquGmMeGSqj2Uzxyb
-ygtsvUZz0BxYymoWFd4F8sp43oL2TXU6Wp7QIpBaFgkSf/UQxfR6wcQ3ivafeS1l
-g8vUFuMfuMLto6T0JiZw8uKSuDWltSReF+FXVnhawz72BZMy8RIoshGdpWHn/YbN
-6L+JOuxZnvkMAZvSLT3c0H4XCDYtEfK2mJMqD2ynX5tGR8Fy3GAaEjhx36TvzTjC
-XRmJ+FnlSW1p77x+UjFUFcpY8skv+f0Gip30iynAb1hoAdibIDab612OWi/4vX0D
-aM6t68Uq8rsabeJYsZG4uQINBF01/K4BEACskZL08crrKfX2aD2w8OUS3jVGSW7K
-10Jr/dgl6ZB7Xx/y3c9lhBim7oRIsl6tpR/DBP50UnTIgBbvynbJ6tbWGptt64Az
-nI7el9pH0k63DOKcfqRUgJKTM4OUZSkcuqQ2qnkvn+g0oiJ3VhaVYOJdJfJF/pLj
-5Oi3UEL2afoEd048/lZEaATRvEqLj+h2pSfETEl5wCWyRnuMSu6ay9NmVzRxiJhP
-DGW2ppQTxJuaKj+6Vqw5WISu9nsRxTPE1DW8f7LYyPBwgultuSYKZoCdfoYE8ff4
-71oZIuCKcGSSBHQbR6MBTD6KJtqzBzpfJ8zZJmVO4lg0CJgp9xX2QZ8hPkpaBbnq
-2JCMS1zriCMN8iGhW6ZHYmZQJtWuubuZt51VL9QmEUUhCF1t+3ld11SaowY4NFKI
-LUdYbC2zAOQIEEJkWRIHKleuc2zYSNSoXl06oGgwCKQb5l+LlcYHx4+/F3+KzyAq
-0NqBC1rMnhbn3tcckdZyhLEpnx9/y33ypo6ZZ0s6dLGrmSpJpedEz6zr8siBa4uT
-3IvVF4xjfpzSt3cMD/Lzhbnk5onUfkmoCmQ/pkuKpMr35hHtdDxshLcLPFkTncMj
-EVAOBToHDbKDSplueyJm48ELPi9ZmuyNu7WsB8TWVEAkUShxdeHALVpY1D+MjXK+
-Z5ap6/tppj+fmwARAQABiQREBBgBCAAPBQJdNfyuAhsCBQkFo5qAAikJEHch9jvT
-i0eWwV0gBBkBCAAGBQJdNfyuAAoJEHi9ZUc8s70TzUAP/1Qq69M1CMd302TMnp1Y
-h1O06wkCPFGnMFMVwYRXH5ggoYUb3IoCOmIAHOEn6v9fho0rYImS+oRDFeE08dOx
-eI+Co0xVisVHJ1JJvdnu216BaXEsztZ0KGyUlFidXROrwndlpE3qlz4t1wh/EEaU
-H2TaQjRJ+O1mXJtF6vLB1+YvMTMz3+/3aeX/elDz9aatHSpjBVS2NzbHurb9g7mq
-D45nB80yTBsPYT7439O9m70OqsxjoDqe0bL/XlIXsM9w3ei/Us7rSfSY5zgIKf7/
-iu+aJcMAQC9Zir7XASUVsbBZywfpo2v4/ACWCHJ63lFST2Qrlf4Rjj1PhF0ifvB2
-XMR6SewNkDgVlQV+YRPO1XwTOmloFU8qepkt8nm0QM1lhdOQdKVe0QyNn6btyUCK
-I7p4pKc8/yfZm5j6EboXiGAb3XCcSFhR6pFrad12YMcKBhFYvLCaCN6g1q5sSDxv
-xqfRETvEFVwqOzlfiUH9KVY3WJcOZ3Cpbeu3QCpPkTiVZgbnR+WU9JSGQFEi7iZT
-rT8tct4hIg1Pa35B1lGZIlpYmzvdN5YoV9ohJoa1Bxj7qialTT/Su1Eb/toOOkOl
-qQ7B+1NBXzv9FmiBntC4afykHIeEIESNX9LdmvB+kQMW7d1d7Bs0aW2okPDt02vg
-wH2VEtQTtfq5B98jbwNW9mbXTvMQAKKCKl+H8T72WdueqgPKHEkXDZtJmTn6nyne
-YlETvdmHGEIb1ejxuJ5URlAYnciY+kvSQ/boKjVHNGmf6+JBexd+HqPhkeextV6J
-cnmi47HDvIU/TSynhuqZeK/3SZAV7ESqQl42q7wm7Pqw0dkv4jjFCRxDA+Qq2aH6
-szJ7DZxTRWqfR3Zbe78NyFVXKxhFQO72zHzC3pFu/Ak59hmTU23yoXVo5t+5O+Q2
-1kX2dbuLd6Px1bnT+EmyneoPP1Emea5jgsw2/ECqHnvNt6cbp+42XYldGh+PBHBm
-ucC3Mn7sALajHe5k2XkNlfbjSNlmutxQFH1qq9rh/JVyxJNHeGzV5G0timAwfdJF
-UzE1vNU5P0w4O8HrCsX5Ecfgcw2BQ9vPCE3OfG+11xp6oiNMRVsR5pTu7RiI1BQA
-yICWUW/wXuhhHkkwNTiwfciJfVA8ckOiRubik8geEH5boOxgeAaBu6yusQVHnRRy
-G4wjQ+qsWo+wDI9WMdtpNG1toJrSUL4OYa4oX3YogSv5hGrbYIaP4HwO6O2oTMnS
-0lRIGJOqbEQcmKUa/nWT/3NipTnYzyMjMlEQe89YKjd+32tjMfOSdIOvwCGaTizd
-WnKPF77qB9D0v8C/7AdHmEFqf2ZX8vK31aaY+ZpPWG5IHlf6f/buIMBalJOxIBev
-eBqxcHwQuQINBGF4DJ8BEACk2Gwau+s/pKmOTnGLMnB3ybQsiVGLRhsw2SqSTvSy
-BthAyW1UAqdRqNA8/FdMlvVuppG8+vCLXPmpP63C+9M2tyQeOR2aVQp+u1EIwN4l
-Pu4wrh6vdtgSRim8uxBdLIHG16z0xxVhE2rM/Ot/gucfkpoEw289VaR7sPmIxfVT
-m1QcqCGiFQl3rZnma6Bz8UOXJoE8wO+LK5WkcdmFz6+Z3BLSb5IL9lhsArFToNq5
-dN2SSTbCTdHRzrRuoCdefYHdxoLCM4kJfggRRgWhKoEJro+ZipESq1T5yHV/iAJy
-+3DuC8LbYLvsjt9VZYARw8xIGb90Vj3ThWuMoVr/IVmKT7foC5Whe0PTI/b2frNa
-WCxxC4cRVxMusiBX66mclQ4Mvzwj50G1WKygULYcvPQ81Tg0pvgTKqgxwL9luN9M
-iDVtkn9CZx7NFlszVr+ic7nVJjANnJebFHCEZfJbQo4uIwKfYbhopUkCa41iXpes
-bVzAKqNwePgyNTAMFyYnjAUE8FVUmx7ZJVb15iEbMs38gJKJ/Wb8wtJRflAfkhrE
-zh1M/43WUAU3RfPmXTrGeyDCYKTHiXTnj748uH6U40sB9q+qeEhZdTj0KufjgtWa
-FWsZTkVrtGOaI6xfX6py/k3hjU3es+7ddElxhPBcqNE3pkPRqb9wz+exSdM7hiUz
-NwARAQABiQREBBgBCAAPBQJheAyfAhsCBQkFo5qAAikJEHch9jvTi0eWwV0gBBkB
-CAAGBQJheAyfAAoJEE6yfbKjuIuLggkP/1INRyRToLmY1ms9DTWMQ0lwbBL8J3xu
-/neKIOKVGOdw9zcWlGugUoOthSbT8bjvuybH1Vjx4wFM+cnuMVfjD58Xu6ZpgCHN
-1wXYMuzYweBFKaMg4oSwTKuAJBJ2IhfEm/cAryVvKY2zY+uyzgizx3vAg3sjkAPD
-crSCJP2nkuHcJ3nzUbKNAjmdMsnWDrqqZVwP99nuyMk8bAtueZ0SKvIpCv2wIeYO
-7zkj61vuQOFOGhl98OBui5wUhtgQw//esTWYiGNKSmD3derd2JHVA01tBmCWV4KM
-LDbg3CcMMQ1x3V1me6EG3giwBL1I9xTsBUbEa6eEN9U0zdKvoMbSogON5wCuxAzO
-/CXGMreJtBUupHEc69oTuwe426Ihi3AbRrPAg3tnGGFCt11HoQFNnRPWb3unF8Ul
-A2rSytvwFyQi3pzBYt5VsTIA7NEHGuJs+/Oor6AOInzht1cp7AfmDGfGy2N5ow+4
-GI6FPe2UqIg2+nFiGr9hRZOvXRgLQL8dlDnFChymldxm/J/UFdJGSWRldEDsPrzH
-QESKvsV9EjnJQR5p5zkQK6jx0zqSlDgiNG2GT3/CSvwIdCih6Cl9HThHtYNm3ZYN
-0bU9W2jeoLh3AINNTcrp0tAHZuQLFxukbj56O5eB+nfk67/X2iNii46ZdJQNwbT9
-YN6CstQz+Cnqg7YP/3G6Y6NHIQggXnlYIi3iwN72hEgEqz6vIRK87lBGW2r3eQ0c
-DZuE3+5Q4FYciw+B2RKeDhjdmPHypA5o+RiAyI7JOZwJalqHO3nwJG5sr0rRzcJs
-bGvpbzso2JuTyTURv4tBNq45b9y0Qdzt5PpNrPJbQADJWn+HWsbVJB5gWBTdoQYg
-pyTr84nQyscWAUFTRbmHvtjCCfLdvU8wM7ubAQ5Dwi1pABRttRAMuPA94HzaBF5y
-XkghxHpnW0IcXGiwgch9LQyaO9VSRhiPH6r5Zuk7KvGhHph7SC5JgUn9vJmmp1zc
-d0mXQ2Zh8M81J3Ri3iGPHM2CqplAxXNbIrnztbEJhN2I+77m73Z4d+K1ivg6xQht
-eSZhwhx7/Z3Tl+U2jYOEFIn/UFmV3UxRSJa/jQRcjvMKprSp4tAZ2yJI3babjRbi
-xgUEtlK105/JepxcAdw9vosxO/rR7VqCzu0copdxC0GAH8og+A9/3LPhlRGy3Qhf
-zjy9JHWHj4EIsol02BS8+dWvAoYerkve9O9+h6/B5wM/Yng9BjT+OrNvkfmqK2cs
-pBXwYedOrC4uWcUmueEVrv5P4FF36wJ+ejvPS6vdTxVTdLXjouUHwTQQZVlNjWY3
-cIyj03nZ19c+b30+2FzG/uSnb/ePWsRLY7Iyz4ygr8etweBPnEIvjwpAZxOuuQIN
-BGPs+VgBEADKbgLL+vAabKV2rGSDgY+IttTAtg9w9Uor1+Q/CIWGxi/JQy7l7XTK
-jmS0wvdwU+9f/eGsjxigbvAcSsV1szyKfVQQFT2m9KhDrBqNCAvQ5Tg6ZQdNe51o
-HwjiIQ1i7z8QoT22VucdTYqcMLAHe+g0aNqLLSSWLAiW4z+nerclinjiTRCw/aWZ
-JR1ozQd2eKwAw6rk19bHcihXo2E0K1EDmdHcNA8ytypxwWWXBftCYRWXi5J02GeZ
-azxmx/DULnFgy2J4G0ULTqGWsbf/tCt22jqgyX+vFj/sJPn+l3IJqpyNY5yBG6Gc
-ejeP9vRoQrapGqHkcx+37f2vjwmpj5548JI52KEC1yZeFwp8HjGLp+zGajpnokrK
-d4XJHniW9+bPLq7Yp7PNn65MaYvZUjv5enKd45fFK6vJ3Ys/fx6PBXKKBs9flRIg
-dXOKSvtV+bGIG0I/p/JEZ/wPxRgxHPDK5jbcI6KBVm3Uk+CHFC4IBAtzdSh6H4Zf
-w1EH3dQZMLVBB/Sj34UQhlwAOlAXtZH3vks/KpclWK8gnqz3i8HN0ezvcnQlRiRO
-8IqlN9/PmFqZeNTerklT7Tt0jXqiopLHL0FXR2LsndeORfxDE1rhVOUxloeuIsY8
-x6gO8h2bGg41YapROjYxZZEcakg9Nch4XAlxeqB4ISttfbiVxeL2DQARAQABiQRE
-BBgBCAAPBQJj7PlYAhsCBQkFo5qAAikJEHch9jvTi0eWwV0gBBkBCAAGBQJj7PlY
-AAoJEOiJefubMKzyRuUP/jzITdamLoLDxEHOra7Mt2S6peHr3XMbpWEdRlA1vzl7
-AaMYO78Pbm7YkWuEByaXM6vGCC8vhrxZq09dBo+oNlpKHjV6UzVhrQLtw1CrvE1U
-DSlw3ltD4pddky5BoDz0EKVNJchPPqPg7im+EAbzLxDYT0y/tRhqzQ6EODNUivLa
-zWjY+aXWqOVv2Ny071ytFSIXq/1G71pCSAYdi3i7I/cfMoN+g27Nf9Zfc7QWbw02
-mcmTqpmwsrCDu6RR0k4gQhsss0tT1libKzfA20Mox+bhPv1ptI3A0ifh13mFqkf0
-EC4MmeThacU5qn0BBk+AlfZZcoLYNdBl5JfVVekjeuvsVJtJ5zx3luK3DuzbRdbJ
-AHb5mh61HE2BHXTgYiH8tqO1q0soVz02c/1KaF7LyevFVkXHoe5eycY4+RuOyIVg
-yzG09Vic7vacENMM/hl6Ms5prLYq0JvykmQIfxTSC6q4MZV35LTZfH3jt6/K8eoa
-3lXTJUU8Pu4C7sDlAFhe+1y3Or3dLWNkMigw/3c57xWlStcEF+LPMdXE/pVSbEz3
-sgT6CNVGo30+4yunYP3IQFQaTjh9BbnPK66iZhpzsynHZ+daAYD8CX26Da69Ligj
-NTIsQnGlzozxFiW5pxIiMWAKKC5xGy9MHLqWhsbUUy+dDLN7r58B4ptusrzk64DU
-pbcQAJ+wzIvCe2qf5C7yveT/ohGfSL1dX9uFK0TbLqIdSaqzmx3t1+SZUjtuymg6
-4MoUgSt1N6mEfT0TSG9AMkRGcyb6uHxOVm05L/BjLDH7ZqFKHkm3d0jkvjyjNH5Y
-lsTGJerxmpOemf8RAZDwygz5LZ1L5zNfzlkv6beKD60ofBppd28ZxgjeHxbBCdfc
-gFQUK6vxZJ19ygbKJDhylNdwjXUaAaCTKnEzzDHGgtUJO22kIFEKk9/z88sowIrT
-+Te7hBKG2nVYMNBWEWb8Tqh8b1NIYgpwmawcdBjuu6QSnqVIi+YvRmMHJFqHicrn
-OhzaPz2w2nK56ZnCv1f5X0s6MXu9BM7/zLdwEE0K3RHmWvF4G9HN7XmTQPNKG4fI
-+GDY8Gp885LtGdSIXYV4j7NDvEWcuqgPpyQjvpFEB/vDSyqe8yUNGmNVT5wPK6lH
-k10Hv2g9cmkeW0qDiRpDg7nHoFcdUSkAyElzxs++Z8CJMVpzl/TJyJt/ZHm02XNs
-owP8HFWvNcyCGwnk9aYCJRuo+/UgjmQvDnVvoHO+XwrMkjSH7JKJQZvzrJ5x8cZs
-XvM9FyHYq3n7u3R+ASMBVwxF9yAex9CfwRg/3OhzOnkbDsu9HwEEOrV2xMQQQ9MO
-t74fIbGkM3hzws0asNoIV1ec52U1X/NP1W8GT9GRX5OX8uTi
-=RQb5
+IDxsaW51eC1wYWNrYWdlcy1rZXltYXN0ZXJAZ29vZ2xlLmNvbT6JAk4EEwEIADgC
+GwMCHgECF4AWIQTrTBv9TwQvbd3M7JF3IfY704tHlgUCVwyM0wULCQgHAgYVCgkI
+CwIEFgIDAQAKCRB3IfY704tHlkGrD/9aIOPxoABbhHDa+GbM1XHSeV99q2UOIsYc
+A5Jg3k2+Vbjr/006cL9Kk+rdbruZJtERo2z+HVVhkJisvySbsd0UbWfiY5AdHzNP
+azpitbX9cNYi0ghDZsD5UgP3cWdx21BJPO0v9PBG9U4z1TQ+pmsQphtNzMC4tK+A
+H/7WTXnVPzKXTYziIEIPgHeassSj7Yfwa8kLiBR5tAehHDNNMi/mMf4d6a+wO46x
+hhRx/BLjoaIxsZw9f5VxDAqGbCrW8IccwJX8vTc89y+6vpzSurdqYrplZWGpcnfT
+3SPBxodLhS7wMehdy6NKNO14vDGR/GP43+6oZ91Cyv2CYHSPpZM6+qMwMmGVkHS2
+6PrCVPhPoDywf/7UeFsC4KZMI6LIGD2YI9UEOlcCAEbRwWVjXCSwRZ9vRkxOxK4Q
+xNMLAIf3YmUZPnqGVcvNssgsapvjmI3CAWpAPWlP5GTcHxrVGiYz7hNZcA0PfgxF
+pmB0QXNxr/x737I9Q8FCZasSlNqocaiKF6gKBxFOKfiKx5DRZ63EZ07Z3HE6y+w3
++97UIJhjxVrONgb7ZX9paE8NtLG/X0ZldUzqWngfnFVasnCDiQC+ls2Tu9Oa+yMJ
+rMe3VM4EcZTjYoESUjKzEHP72hn+GoAk7saWWVK6xYUJPM18Ua1mGx8xwoXt/t95
+W40b92HbJrkCDQRXDI3IARAAqy/YB4Xa+oEF+GTAObJaetvMTqxwrHSzueFjXT0S
+nhR1yakkiYt37PBcQViOBZ3o3ilBmxfjKzpRaSqhC8WjI3u28Gcmqd4s87WR7Mz9
+2JjqEwSb0RBinQpC/NnC7AoWA/z64BPHK75IUp6vXr3LCgJ84jMYP8AwgoVC9xL6
+qNvQXqAfNX/hPcJK1EzAk/5Fcbd6RkWpSl9FIa7Sq6ZvMkX47nyX8I5HcIL4p5ER
+mdhq1h4+C8zG4vf7nWGiWeumMNIRFOFEsVAfbzbZkha2+BAfdU9q4XOvHYEOI2AS
+OyuBG2/F2lgMW/iAKt9ZdVJIhAN9heKlDKC+qwoQeMupx8Tp077PlxG+UwcF1aII
+y0Sk0LOVPx1fZe4/hwHIZOct4ptjdlCpjMR6qLbz2WVGT3WgkcVHnUH/YEdMi2Vf
+lPQXA7sI8y/8467YTWWJRBieh2f0y0k6eHQx/rl7i6jFVsuYqrirZ265zU0Lb+bc
+A/gI6YMutGCzifWGoieBo4nzqc0pPN3tayd6f6V+geTVkIp1S2Sc8cnjqId4jI3Z
+gg0pxFy6wpmL+YOo8lf1m3eBmBbjCvE0+/j0HVi3G2fy8XOcNLPnO/n+Tn5ilzuS
+jx551LKxeQwWikT40nKcHj0IrcXiIJVIBDA5Da7gYbtT8wsXdwbV4Lvvit1naB91
+XIMAEQEAAYkEWwQYAQgAJgIbAhYhBOtMG/1PBC9t3czskXch9jvTi0eWBQJXDI3I
+BQkFo5qAAinBXSAEGQECAAYFAlcMjcgACgkQE5e8U2QNtVFBJg//QTCvdPt7SyhP
+PyDhAkstWpkNl1fwh7PTiJ00e68C7QDB1nbCXQL60yQPuXhHZojoEp7/3A+d2T80
+l75lhwP+7PKIoglAPjw+uJ82fC8e70DzSsTgGmlCemUQ16GJttZoY0lA40YUnHtB
+NiUWNLks2UbUBfqZCPG9vjbfM5ZI6YRqZhdgGZjIwbq+Sv9dM/OyV2TLxcW4+slR
+myUv9aXHfVdDUiu2Qcc5ipbCvSFNznT/Y7wfR7CX90FkurcSaKdln62xO6Ch/SPh
+JvFiGmXD32cbBs3W5fLgvz91Y5Redjk6BpMpk8XXnNEzFc30V7KUFVimnmTOt7+t
+EjqZDaVp9gd1uO93uvIcXkm9hOhINd3SbMXacvObqPCw7zjtk13kZ1MPr+9x5/Ug
+m1rWdLAD+GEu2C2XPr+02dyneUR0KMAzHb2Ng8Nf4uqz0kDFwke5+vzajrAz1MXb
+hDytrw1u8Hreh1WJ0J+Ieg6wgUNStrMfxe5pDPJmQjRtvMuaAwC8w7q7XM9979Mr
+ot0mDsB4ApJw4lLfwPmabBoPVsAGvrt5sD9fkd1qiZIMpV1Rhp7B9MYEiytaYKYq
+l1v5Z9fih0Wk3Ndb+qySIGnlZJ6wq83VBSQslkNkPWTPb75e6XkH3uzkvEtMtHC+
+Aug1pQWveWd6PM0uB0Gl/oWeQDn2zJEJEHch9jvTi0eWVo8P/2OVSzfPFfPUhJSw
+zmgNX2WsW6WN91wtbf0oUpORK4otjJETUTvurVHPin473mSAeIypzMO1pHS6Q1uy
+Pj5Em8x7BgGza1hBLUTvTIpRfS+J54hoaQL6XGnrE3/QIl/AxGK5aqc9h7EqsTbh
+Pckg6BELWueKg1PpCGWtQ1igCcsTUt/kgJ54TjT7dUyuFCAapVgY6lMlEta4dIYJ
+dbeQWkZR043o6u7R0HvYHl0P13thD41guhdZsPNah6km5hd7IEXuBNo/HReSHniI
+zCKolpIkJyn9X1g+SKJ5aQ6MvFd2L4pkqJKt+nNvkoQXITw9yExDHJSQChX5Qnwe
+eJoU0S2Qc6W9jL9qyOw3U+su2/oPzTk2xRu1CwiYLeNjZSNYhU9Az78CsvNrZUUK
+CmiZrkmN8tRlFFps3TaF/fodwuYfWPC/R9WpKbtaqjjz3PqXHYbh5NyURVw/EqvM
+y1yP26PsQn41tE5Ebndl6P2YzjAZQLKNTc584BXq7Tqj55jeeH/sS2XXv5gF2S+t
+m9+Nwyuavl1mC5CNaL+KbkX6w/OadINUOArQW2HC1SwqP184fN9cJCx3NeB24kKg
+84M42qQPUOIHfiu0R06JKaPWibk9WAU6ssQLcrbRs5NZ0ySqJWU0tpS/W4Zlz1Yj
+Ytnce0VAbz25OAACZ0adKnWgKv8OuQINBFiGv8wBEACtrmK7c12DfxkPAJSD12Va
+nxLLvvjYW0KEWKxN6TMRQCawLhGwFf7FLNpab829DFMhBcNVgJ8aU0YIIu9fHroI
+aGi+bkBkDkSWEhSTlYa6ISfBn6Zk9AGBWB/SIelOncuAcI/Ik6BdDzIXnDN7cXsM
+gV1ql7jIbdbsdX63wZEFwqbaiL1GWd4BUKhj0H46ZTEVBLl0MfHNlYl+X3ib9WpR
+S6iBAGOWs8Kqw5xVE7oJm9DDXXWOdPUE8/FVti+bmOz+ICwQETY9I2EmyNXyUG3i
+aKs07VAf7SPHhgyBEkMngt5ZGcH4gs1m2l/HFQ0StNFNhXuzlHvQhDzd9M1nqpst
+Ee+f8AZMgyNnM+uGHJq9VVtaNnwtMDastvNkUOs+auMXbNwsl5y/O6ZPX5I5IvJm
+UhbSh0UOguGPJKUu/bl65theahz4HGBA0Q5nzgNLXVmU6aic143iixxMk+/qA59I
+6KelgWGj9QBPAHU68//J4dPFtlsRKZ7vI0vD14wnMvaJFv6tyTSgNdWsQOCWi+n1
+6rGfMx1LNZTO1bO6TE6+ZLuvOchGJTYP4LbCeWLL8qDbdfz3oSKHUpyalELJljzi
+n6r3qoA3TqvoGK5OWrFozuhWrWt3tIto53oJ34vJCsRZ0qvKDn9PQX9r3o56hKhn
+8G9z/X5tNlfrzeSYikWQcQARAQABiQRbBBgBCAAmAhsCFiEE60wb/U8EL23dzOyR
+dyH2O9OLR5YFAliGv8wFCQWjmoACKcFdIAQZAQIABgUCWIa/zAAKCRBklMbWmXwh
+XluJD/4mavm5UQ84EczsNesfNL8gY3zzlCnfvnUlJHK+CoYub4wcoDXVUlnCmWgS
+lZHQZgr3/qfW2MM3y/kXcbxhL/FijUzY3WlnCdnIVNjuB+QJt0LHbkP7En/o085Z
+zHuzaXxfZ97qN+KPsRBTjnJ8hd3B64cVjgnXva1+pG51EK4iDF2bXiWPHvUbPiL+
+Og6C9XjpWrwIA1CWyH/4i7dtfTnbViO2aqKQNHfrXJ+xS938Lr8r5+VmUWByHqwe
+BGIASOmwsJeSUHozkZYbmMdaJJ8j458zyfS6LO+HIa3+zhzidOoiEH9c5QvVf54g
+NsYjPTcHj7U0DgkxCVQeiBKBLR+q6M6QHa4qax/X0Z2ZCcSDTZwqGJNaKfcFYd8X
+1B2zgrxkGweeHKjfmpqfXRKrggHumLdVqHU7KS9cz1yeTL+Nw7ne+kzRMEA8sLnm
+4ODRUJwUz12RqS0GG1FYV0rjJVWVzRFMfMUs+7xAptEuMdoddkQSmytkXyOKAqv8
+KQ9XUEbGWikmCxW2cOY9spOpwQa7X2oXe7FlV9RfmHYrG03k+YlIREgFqlvWwsgp
+zURculd+CIFvT3vci7vFm1UiQBb5wC8bHOoRsr7OXW1267lipouZr5OrQhVnRZQV
+a64cdUIKjLXEt4790uxh8ggNwktZRILIn2JHjgEQICdYWeQb1AkQdyH2O9OLR5b3
+MA/8DRZi0s7SLQwaQiJrT7GrACsIMjYo6SapUVxDMF28QfANW809ANpq2Let+yAD
+mEibSgpiDiO7rq6PvYnHmPyxmTbEwMtm1bDi0j55/TybnNN6hnUo8F+o0ywCJjfo
+T8GDuBX50ODoOYUMmIoYwyMz/UtNi8iHtxTBPR5b7l1Vt8EfUb3wrwGa4i22mjgL
+KU49h7Oyi1VYZRrM+0hlrmaLF79tT9msDnn83mgq9qefkJuU4nBqUXui/CY5b8vJ
+XC+8tD+q1wCiUM8uv2LJs/5JyK80zFJbkBXA/ZCYtU0LJEpUf7HjbIAdCMDWjpc4
+j+IyjU+Axv+NkMLgYRhaadnPRVzqY8f2T2Bs+EQWk2i61BVQMqakGtwBWIMCp2fn
+GDCxIL/FCN1kIA0J0h9ommhMgZdOJaAktsddr/LwVh/hcYX8Mfy94vPs+E3Kb6Oi
+iwPkkN6umQvdFa9Rhh9SUNvmtXzMo3WELLobtvVKC+fdFVatDsJurTRKLDKEvPjS
+xFlJ/T8t9yItTBAZ7+ab4nJhWoEbzkVTgNizLCJNmdAEtiKa9dEZOZl0DVmxBhB1
+aqMfHA3S5UhZXmGBHwCF6PcpnM3C4XY2MjQ/sRxdFa7/HFBKOO176h6HyujQ/AyO
+llmvJCCg9Hz0Wk0tjTMFsnAbh7dB2GTNQwBNZ60gUCWR+mG5Ag0EXTX8rgEQAKyR
+kvTxyusp9fZoPbDw5RLeNUZJbsrXQmv92CXpkHtfH/Ldz2WEGKbuhEiyXq2lH8ME
+/nRSdMiAFu/Kdsnq1tYam23rgDOcjt6X2kfSTrcM4px+pFSAkpMzg5RlKRy6pDaq
+eS+f6DSiIndWFpVg4l0l8kX+kuPk6LdQQvZp+gR3Tjz+VkRoBNG8SouP6HalJ8RM
+SXnAJbJGe4xK7prL02ZXNHGImE8MZbamlBPEm5oqP7pWrDlYhK72exHFM8TUNbx/
+stjI8HCC6W25JgpmgJ1+hgTx9/jvWhki4IpwZJIEdBtHowFMPoom2rMHOl8nzNkm
+ZU7iWDQImCn3FfZBnyE+SloFuerYkIxLXOuIIw3yIaFbpkdiZlAm1a65u5m3nVUv
+1CYRRSEIXW37eV3XVJqjBjg0UogtR1hsLbMA5AgQQmRZEgcqV65zbNhI1KheXTqg
+aDAIpBvmX4uVxgfHj78Xf4rPICrQ2oELWsyeFufe1xyR1nKEsSmfH3/LffKmjpln
+Szp0sauZKkml50TPrOvyyIFri5Pci9UXjGN+nNK3dwwP8vOFueTmidR+SagKZD+m
+S4qkyvfmEe10PGyEtws8WROdwyMRUA4FOgcNsoNKmW57ImbjwQs+L1ma7I27tawH
+xNZUQCRRKHF14cAtWljUP4yNcr5nlqnr+2mmP5+bABEBAAGJBFsEGAEIACYCGwIW
+IQTrTBv9TwQvbd3M7JF3IfY704tHlgUCXTX8rgUJBaOagAIpwV0gBBkBCAAGBQJd
+NfyuAAoJEHi9ZUc8s70TzUAP/1Qq69M1CMd302TMnp1Yh1O06wkCPFGnMFMVwYRX
+H5ggoYUb3IoCOmIAHOEn6v9fho0rYImS+oRDFeE08dOxeI+Co0xVisVHJ1JJvdnu
+216BaXEsztZ0KGyUlFidXROrwndlpE3qlz4t1wh/EEaUH2TaQjRJ+O1mXJtF6vLB
+1+YvMTMz3+/3aeX/elDz9aatHSpjBVS2NzbHurb9g7mqD45nB80yTBsPYT7439O9
+m70OqsxjoDqe0bL/XlIXsM9w3ei/Us7rSfSY5zgIKf7/iu+aJcMAQC9Zir7XASUV
+sbBZywfpo2v4/ACWCHJ63lFST2Qrlf4Rjj1PhF0ifvB2XMR6SewNkDgVlQV+YRPO
+1XwTOmloFU8qepkt8nm0QM1lhdOQdKVe0QyNn6btyUCKI7p4pKc8/yfZm5j6EboX
+iGAb3XCcSFhR6pFrad12YMcKBhFYvLCaCN6g1q5sSDxvxqfRETvEFVwqOzlfiUH9
+KVY3WJcOZ3Cpbeu3QCpPkTiVZgbnR+WU9JSGQFEi7iZTrT8tct4hIg1Pa35B1lGZ
+IlpYmzvdN5YoV9ohJoa1Bxj7qialTT/Su1Eb/toOOkOlqQ7B+1NBXzv9FmiBntC4
+afykHIeEIESNX9LdmvB+kQMW7d1d7Bs0aW2okPDt02vgwH2VEtQTtfq5B98jbwNW
+9mbXCRB3IfY704tHliw+EAC5FNOwkABxZZ1C8K4wUDl2Oe7mewVRhVNqvTWS4uib
+vFax78HDyLNqKmfi+yRHSQsDAkKr9GzmBc1DOabp4V+IRwj0vADHbcpwoGM7EJ2G
+o/0RtdZiTP98B8DMACu17NwjM1l5EUExqjGEeXp3jEZGMSE8vqjq8djkvl8s5mUM
+j09Wpj3Gl464NNQ/gnB0P/2sp11T0BVb2u32zNLJKh0ZP9QxXT3z93UBOeiT9BzR
+hqFMyl04xpt5rqYDUdiL7y+tZDR28INZZ7aYsCs4NkA22Fh6nI3v43Us38+Kroru
+09ipLE8A5fx3G5LxMwtWJA+zZisrrky86JYEFOULGpFuKrklP2bRyaHePjMeqOzD
+Y5/n5unqk4+EZAPWIM4LFOwDtTD1BWmuDdpP/RjPuPZUhoMSW0p/Vv/FuBAnpgVQ
+9D/kXI3xaAxKgaPp+AzQN50dCosmn643zAGrZTiIDIp1VtXVRFAVinN/mbJkqQJv
+8zM/x0bc6EUNb/K8BP/JJp+x5D13DjtXYUEG8TFHz6YKZe9QzlhK5rZY/Fttwqvy
+KvIKanXEjOf5/azkdOGlSN6Z74G4l22tui3y3CM+vmRrlMiBbLkCTuPfw8rS6uzi
+B5No8PYBwovbqNvpm+dGNHySFTvNyJhzWmvCVt8FZ+c4tqOmwd/D+fhon0Pg42bu
++bkCDQRheAyfARAApNhsGrvrP6Spjk5xizJwd8m0LIlRi0YbMNkqkk70sgbYQMlt
+VAKnUajQPPxXTJb1bqaRvPrwi1z5qT+twvvTNrckHjkdmlUKfrtRCMDeJT7uMK4e
+r3bYEkYpvLsQXSyBxtes9McVYRNqzPzrf4LnH5KaBMNvPVWke7D5iMX1U5tUHKgh
+ohUJd62Z5mugc/FDlyaBPMDviyuVpHHZhc+vmdwS0m+SC/ZYbAKxU6DauXTdkkk2
+wk3R0c60bqAnXn2B3caCwjOJCX4IEUYFoSqBCa6PmYqREqtU+ch1f4gCcvtw7gvC
+22C77I7fVWWAEcPMSBm/dFY904VrjKFa/yFZik+36AuVoXtD0yP29n6zWlgscQuH
+EVcTLrIgV+upnJUODL88I+dBtVisoFC2HLz0PNU4NKb4EyqoMcC/ZbjfTIg1bZJ/
+QmcezRZbM1a/onO51SYwDZyXmxRwhGXyW0KOLiMCn2G4aKVJAmuNYl6XrG1cwCqj
+cHj4MjUwDBcmJ4wFBPBVVJse2SVW9eYhGzLN/ICSif1m/MLSUX5QH5IaxM4dTP+N
+1lAFN0Xz5l06xnsgwmCkx4l054++PLh+lONLAfavqnhIWXU49Crn44LVmhVrGU5F
+a7RjmiOsX1+qcv5N4Y1N3rPu3XRJcYTwXKjRN6ZD0am/cM/nsUnTO4YlMzcAEQEA
+AYkEWwQYAQgAJgIbAhYhBOtMG/1PBC9t3czskXch9jvTi0eWBQJheAyfBQkFo5qA
+AinBXSAEGQEIAAYFAmF4DJ8ACgkQTrJ9sqO4i4uCCQ//Ug1HJFOguZjWaz0NNYxD
+SXBsEvwnfG7+d4og4pUY53D3NxaUa6BSg62FJtPxuO+7JsfVWPHjAUz5ye4xV+MP
+nxe7pmmAIc3XBdgy7NjB4EUpoyDihLBMq4AkEnYiF8Sb9wCvJW8pjbNj67LOCLPH
+e8CDeyOQA8NytIIk/aeS4dwnefNRso0COZ0yydYOuqplXA/32e7IyTxsC255nRIq
+8ikK/bAh5g7vOSPrW+5A4U4aGX3w4G6LnBSG2BDD/96xNZiIY0pKYPd16t3YkdUD
+TW0GYJZXgowsNuDcJwwxDXHdXWZ7oQbeCLAEvUj3FOwFRsRrp4Q31TTN0q+gxtKi
+A43nAK7EDM78JcYyt4m0FS6kcRzr2hO7B7jboiGLcBtGs8CDe2cYYUK3XUehAU2d
+E9Zve6cXxSUDatLK2/AXJCLenMFi3lWxMgDs0Qca4mz786ivoA4ifOG3VynsB+YM
+Z8bLY3mjD7gYjoU97ZSoiDb6cWIav2FFk69dGAtAvx2UOcUKHKaV3Gb8n9QV0kZJ
+ZGV0QOw+vMdARIq+xX0SOclBHmnnORArqPHTOpKUOCI0bYZPf8JK/Ah0KKHoKX0d
+OEe1g2bdlg3RtT1baN6guHcAg01NyunS0Adm5AsXG6RuPno7l4H6d+Trv9faI2KL
+jpl0lA3BtP1g3oKy1DP4KeoJEHch9jvTi0eWxrwP/0zlWCYOsNH5Id4SZsPKe8im
+evCbj3lvboTYPc4u6HvbbwbYqLerzP2ajWSCdUAK4CMrAuvFildo4k6COh6VaZdi
+DOwsKoJfs6Vd5oud5a+jRnv8+oktRBf5OAVc3RLfBG1RC9qI891JTOjGrTU7dBJr
+RjRWdy9YQd/epN2I0RVtUaJlxKELoFj57FPERZgg+yomiheBARK+fLYY/oFTwJK3
++Kt3rdnBtUeVpEiL6VjU6bqvIpUG+P0u27AspcacgDewg59+thcbY4tnsdo6DSZB
+Q92bBPVGzpXPEhpQ/vZM63CG8qsZfQ1jw82ovmSnkKPLnBQRabFYVl0DCl1uYHg2
+4Up66w6Lj/tT2XbCeBf2n54K9HoUMV9f7/pLoTa0dE3UYI1K4GLZdp+yxMveUEjG
+nh0YOTBmoBtpdy6Udejujil6xbH2gLwbICFm+boKVWwzrYCyfl51ASiq5dmqQwd3
+tPAg9Hc6qtvZ8cswyWyNOQpZo0myvfPaKrHWa9u2GqQmeGBwhckXJxFM/zau0yx6
+NMkSFI49kTglw0A77rcmlJUAQQeoXmTKMl6NM/3AUfvL8Qfu9/74kgoFI9pmQFky
+BtcQMCeB2/JQ9K9ywPhi/gIebjftfMgKQsTW+/6Nl1yZ8q38y2n1J4p/acVlFc2K
+PhbmKL4CvcSdlQS4CbvFuQINBGPs+VgBEADKbgLL+vAabKV2rGSDgY+IttTAtg9w
+9Uor1+Q/CIWGxi/JQy7l7XTKjmS0wvdwU+9f/eGsjxigbvAcSsV1szyKfVQQFT2m
+9KhDrBqNCAvQ5Tg6ZQdNe51oHwjiIQ1i7z8QoT22VucdTYqcMLAHe+g0aNqLLSSW
+LAiW4z+nerclinjiTRCw/aWZJR1ozQd2eKwAw6rk19bHcihXo2E0K1EDmdHcNA8y
+typxwWWXBftCYRWXi5J02GeZazxmx/DULnFgy2J4G0ULTqGWsbf/tCt22jqgyX+v
+Fj/sJPn+l3IJqpyNY5yBG6GcejeP9vRoQrapGqHkcx+37f2vjwmpj5548JI52KEC
+1yZeFwp8HjGLp+zGajpnokrKd4XJHniW9+bPLq7Yp7PNn65MaYvZUjv5enKd45fF
+K6vJ3Ys/fx6PBXKKBs9flRIgdXOKSvtV+bGIG0I/p/JEZ/wPxRgxHPDK5jbcI6KB
+Vm3Uk+CHFC4IBAtzdSh6H4Zfw1EH3dQZMLVBB/Sj34UQhlwAOlAXtZH3vks/Kpcl
+WK8gnqz3i8HN0ezvcnQlRiRO8IqlN9/PmFqZeNTerklT7Tt0jXqiopLHL0FXR2Ls
+ndeORfxDE1rhVOUxloeuIsY8x6gO8h2bGg41YapROjYxZZEcakg9Nch4XAlxeqB4
+ISttfbiVxeL2DQARAQABiQRbBBgBCAAmAhsCFiEE60wb/U8EL23dzOyRdyH2O9OL
+R5YFAmPs+VgFCQWjmoACKcFdIAQZAQgABgUCY+z5WAAKCRDoiXn7mzCs8kblD/48
+yE3Wpi6Cw8RBzq2uzLdkuqXh691zG6VhHUZQNb85ewGjGDu/D25u2JFrhAcmlzOr
+xggvL4a8WatPXQaPqDZaSh41elM1Ya0C7cNQq7xNVA0pcN5bQ+KXXZMuQaA89BCl
+TSXITz6j4O4pvhAG8y8Q2E9Mv7UYas0OhDgzVIry2s1o2Pml1qjlb9jctO9crRUi
+F6v9Ru9aQkgGHYt4uyP3HzKDfoNuzX/WX3O0Fm8NNpnJk6qZsLKwg7ukUdJOIEIb
+LLNLU9ZYmys3wNtDKMfm4T79abSNwNIn4dd5hapH9BAuDJnk4WnFOap9AQZPgJX2
+WXKC2DXQZeSX1VXpI3rr7FSbSec8d5bitw7s20XWyQB2+ZoetRxNgR104GIh/Laj
+tatLKFc9NnP9Smhey8nrxVZFx6HuXsnGOPkbjsiFYMsxtPVYnO72nBDTDP4ZejLO
+aay2KtCb8pJkCH8U0guquDGVd+S02Xx947evyvHqGt5V0yVFPD7uAu7A5QBYXvtc
+tzq93S1jZDIoMP93Oe8VpUrXBBfizzHVxP6VUmxM97IE+gjVRqN9PuMrp2D9yEBU
+Gk44fQW5zyuuomYac7Mpx2fnWgGA/Al9ug2uvS4oIzUyLEJxpc6M8RYluacSIjFg
+CigucRsvTBy6lobG1FMvnQyze6+fAeKbbrK85OuA1AkQdyH2O9OLR5bPGRAAmgSi
+hpu4US/JoWnR/aeiFf9upobXVDnBnqOAXiMUaFeS+hUuh5EWUhDLIWYvXXhPacvb
+pUOlxwLsLIdPRQGGSp1/rqhVRnmWsJ34DoAKxG7Elq8EArK/pF+v4wSUMegjAPJQ
+evIcLvm83z+jHmbk1AEeioBYTq45RbzlHmyLmGK/zT13KnBUWE3sFkECoco+vMli
+8oPeL+JMfiMgPb2vDs+58YlHq5W26pe08BwGzY5LQM7Jt52oxsqgXEX/N95QqgSc
+sc625wCIE8/Qo5pXT0TKk+5ViFojs2Ei3mgXHBXFgISdAtWBEmqN9TESqPPrHzfn
+Fk9t6mPg1r5Nt37IKO7oTzu7/SXrJlXPIQ99Nlq6HO/mMVdYjbWFBPw8+NGVGemQ
+chOODZsksvHJGV4gjMpW1FC37MRNsiai1UMraVxzsrCte4/oqpa7bY8VdWw6p5mv
+fdroLkwHW2cS2lgC8ft7e4npiHXXLAIib+sFHcrIkZu0uJxGCJOkUwkaDrAFKWzZ
+YHc2YUrW5XN7CNBo/fe90r1W9/4esn59SM2mTMarrUn1fiExwFiUci4U+3/7U4Ii
+ViNeNoZ2J1+hqxudlx1OT7Ae2Wg4dLASoEHaMKby4+JVVicA8jdlocrCbpEv1hVV
+47hwiKc+VTQGvCZqs8eT+pbnw1Recd13J9Ny7bO5Ag0EZbladgEQAMSm1QPtyjAr
+XdM1i2Y6439Jc/AJy3ykVjxTaDi6n5z7lgQipaQBSpWbwun4Op0W5fs1t8rYE2iP
+A/KKoqVoEA3o3Hts71uNK+VttkGtUneYv6TvGsV1MYt4NJJOUQF6yPsVcrXMrtJb
+0BXefjmWY4sBdMLXdVDcrRIRdv7r0XBevfX+Lng2BN8z/UtwlmEihHoy60ckJJgq
+47pkfFho51+PjwEZJaPtEgRsXn2sgTMNHukGTrV8ub/aKWVNBPF0wYYF5LA2NHgV
+p148nS11F4OgiNpCkAZmJQCPlyp4emYfxkihjh+TZKw6KcrxwOCx7YeceKK6wWvr
+HHrwjJxl2nhatDIYNIlnVkqTlBp4A9gTdCxmciZ1xXb+QllLycBYMWgu2lo1Kk40
+NOfVljIKLatY88XwmJUySYLGyX5kePI29kc+yVGycYHsSgoOlyM/Vw+GXfuj/BRi
+nKItjITxb6YM25wfhgctUer/NAao7dXprFMDUOz6C720dX/f7ISsiqmi7X1U588o
+mNgLvJ/O8gPnyMtk1gWrwhFZDlVYI5AlYxx3MwoHntLZlvm8iEmR+X9LkhIwZcNd
+vfafIpV+8LlOaIxt+uzNzcMsDHCGomUAf/GYXbI8/x1iHoopZIh99UZObfyxyz2S
+SbVtUEBHXyKXHp0bFWM1Iz2LfQwxeNRRABEBAAGJBHIEGAEKACYWIQTrTBv9TwQv
+bd3M7JF3IfY704tHlgUCZbladgIbAgUJBaOagAJACRB3IfY704tHlsF0IAQZAQoA
+HRYhBA8G/4a+6vTnGGbuUjLuU1WmvG5CBQJluVp2AAoJEDLuU1WmvG5CmB4P/1Rn
+XKHryp3UlaOAq/UAF2YKFS9NAggVwH8PhsFc6nZpruc+CFU1s5jwCuW9aiWgQ+Tj
+BFvQ0h/bHLbujlTSmfyyyo/Ij+4vSxRzlmUa8lHPqyqv7fIsQ82AAs8WE/mV8Dif
+24hsxJSZEH130DTkRqtnXS0FB6sOQPGj5EKAFt3v0vN/Z1QRX2eLmZc2jO7QfkdR
+strvF3borb7xdt26/PM8g8RgYaG+fqIJ/NtGQF0XI+WUxuQ+mtRGEyVpL4qnwwno
+kyxjsMxsJvvGIaPULKR1CahGJD4tAlyE3DvNikMRI2SDojaGyh5cw24mJJVZmx46
+7Q3tE4dwmAu8pCGCldUQBG6eprTL/WauyJcmkJr1qsSK7gyx+Uy8mwXESY/s5bwD
+kzhlzaJ0WjBxqXfoHFIElHJfhLS0efqIr6NFmPUu4cBKJKoZoFBwTPTTEmWz7tE2
+mDgVO9Z6Q9fq7CwZS6J/GchieQgAy3Rxm5BizBZsWisY3BQ4JX1w6wH0Cae4rYCe
+bkutFFWBg7JA3j2nkgfzsD3kYHYf5BllL2yV589dEocNjPios56vPi5kg9UQOFO1
+SaX4Efu1eArNcNteBxKf5pH8okDcgjqj9yXZRs6fI2Uk9zzz0UL63+iRSqSj8Kv6
+iepLCzOph1DHnY2tFghpSFYqlayhdprMJVk7GmLFoiYP/1nT6wq8k/RDS3/W7HEB
+J8Rtxs1vL51nU0e5K7jgbUT9kaG2KBmlnRbgkELjvu0lX6zLFiyPcc5JkvE2AyfZ
+7t5cIfanOS4hc0W9C66RQo2cvUxkn2gtCrM7KCTc16Iwe/uMC2RNEneNLiCetwc5
+DhpjYExR59szzQ9Npx31pefsmkSwKdutEz8W96l29yHYgIDoLYW3b6nuBRBfp4nA
+XQ1gWqfEmFNFlKZBa2pPsKNlFgpchC+EiMQ/db1ElVNyW38K7IOx6hNGpEBJwbPu
+HNef9WU3n2DIIgMBHTHPvbNHiCNTfuOM1+/BMbmK59RmW66TS0UaxZsswHHLZt7v
+NN7SKzXsveT9+A1d6wZlVoy8Y3gykBKnBHGRaGO0zaXczHt4YsUA4L3is6lAjbIo
+pU5M3j2F1RFKRr95+HZT/NXNeGbFvsdKmvP4ELtDAuYVMgYR8GqjI5yP/ccVMsi/
+mhT+cUxO/F7+7nixw1Go637Jqr/NF5kjjrBD8EiGy8QrGm6uBR3NGad0BnMWKa2Y
+oYKF1m3Fs/evBkcymR+hSwFzkXm6WSOb8hzJIayFa6kAc7uSKyR5iG00p/neibbq
+M1aUAQDBwV7g9wPmcdRIjJS2MtK1JXHZCR1gVKb+EObct6RJOVw8s58ES5O9wGZm
+bVtIZ+JHTbuH+tg0EoRNcCbz
+=JIbr
 -----END PGP PUBLIC KEY BLOCK-----
 KEYDATA
     rpm --import "$TMPKEY"
diff --git a/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc b/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc
index de2fdac..9b895dd 100644
--- a/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc
+++ b/chrome/renderer/accessibility/read_anything_app_controller_browsertest.cc
@@ -2301,6 +2301,127 @@
   EXPECT_EQ(GetCurrentTextStartIndex(next_node_ids[0]), 0);
   EXPECT_EQ(GetCurrentTextEndIndex(next_node_ids[0]), (int)sentence3.length());
 
+  // Nodes are empty at the end of the tree.
+  next_node_ids = MoveToNextGranularityAndGetText();
+  EXPECT_EQ((int)next_node_ids.size(), 0);
+}
+
+TEST_F(ReadAnythingAppControllerTest,
+       GetCurrentText_OpeningPunctuationIgnored) {
+  std::u16string sentence1 = u"And I am almost there.";
+  std::u16string sentence2 = u"[2]";
+  ui::AXTreeUpdate update;
+  SetUpdateTreeID(&update);
+  ui::AXNodeData static_text1;
+  static_text1.id = 2;
+  static_text1.role = ax::mojom::Role::kStaticText;
+  static_text1.SetNameChecked(sentence1);
+
+  ui::AXNodeData static_text2;
+  static_text2.id = 3;
+  static_text2.role = ax::mojom::Role::kStaticText;
+  static_text2.SetNameChecked(sentence2);
+
+  update.nodes = {static_text1, static_text2};
+  AccessibilityEventReceived({update});
+  OnAXTreeDistilled({static_text1.id, static_text2.id});
+  InitAXPosition(update.nodes[0].id);
+
+  std::vector<ui::AXNodeID> next_node_ids = GetCurrentText();
+  EXPECT_EQ((int)next_node_ids.size(), 1);
+
+  // The first segment was returned correctly.
+  EXPECT_EQ(next_node_ids[0], static_text1.id);
+  EXPECT_EQ(GetCurrentTextStartIndex(next_node_ids[0]), 0);
+  EXPECT_EQ(GetCurrentTextEndIndex(next_node_ids[0]), (int)sentence1.length());
+
+  next_node_ids = MoveToNextGranularityAndGetText();
+  EXPECT_EQ((int)next_node_ids.size(), 1);
+
+  // The second segment was returned correctly.
+  EXPECT_EQ(next_node_ids[0], static_text2.id);
+  EXPECT_EQ(GetCurrentTextStartIndex(next_node_ids[0]), 0);
+  EXPECT_EQ(GetCurrentTextEndIndex(next_node_ids[0]), (int)sentence2.length());
+
+  // Nodes are empty at the end of the new tree.
+  next_node_ids = MoveToNextGranularityAndGetText();
+  EXPECT_EQ((int)next_node_ids.size(), 0);
+}
+
+TEST_F(ReadAnythingAppControllerTest,
+       GetCurrentText_OpeningPunctuationIncludedWhenEntireNode) {
+  // Simulate breaking up the brackets across a link.
+  std::u16string sentence1 = u"And I am almost there.";
+  std::u16string sentence2 = u"[";
+  std::u16string sentence3 = u"2";
+  std::u16string sentence4 = u"]";
+  ui::AXTreeUpdate update;
+  ui::AXTreeID id_1 = ui::AXTreeID::CreateNewAXTreeID();
+  SetUpdateTreeID(&update, id_1);
+
+  ui::AXNodeData static_text1;
+  static_text1.id = 2;
+  static_text1.role = ax::mojom::Role::kStaticText;
+  static_text1.SetNameChecked(sentence1);
+
+  ui::AXNodeData static_text2;
+  static_text2.id = 3;
+  static_text2.role = ax::mojom::Role::kStaticText;
+  static_text2.SetNameChecked(sentence2);
+
+  ui::AXNodeData static_text3;
+  static_text3.id = 4;
+  static_text3.role = ax::mojom::Role::kStaticText;
+  static_text3.SetNameChecked(sentence3);
+
+  ui::AXNodeData static_text4;
+  static_text4.id = 12;
+  static_text4.role = ax::mojom::Role::kStaticText;
+  static_text4.SetNameChecked(sentence4);
+
+  ui::AXNodeData superscript;
+  superscript.id = 13;
+  superscript.role = ax::mojom::Role::kSuperscript;
+  superscript.child_ids = {static_text2.id, static_text3.id, static_text4.id};
+
+  ui::AXNodeData root;
+  root.id = 10;
+  root.child_ids = {static_text1.id, superscript.id};
+  update.root_id = root.id;
+
+  update.nodes = {root,         static_text1, superscript,
+                  static_text2, static_text3, static_text4};
+  OnActiveAXTreeIDChanged(id_1);
+  AccessibilityEventReceived({update});
+  OnAXTreeDistilled(id_1, {root.id, static_text1.id, superscript.id,
+                           static_text2.id, static_text3.id, static_text4.id});
+  InitAXPosition(static_text1.id);
+
+  std::vector<ui::AXNodeID> next_node_ids = GetCurrentText();
+  EXPECT_EQ((int)next_node_ids.size(), 1);
+
+  // The first segment was returned correctly.
+  EXPECT_EQ(next_node_ids[0], static_text1.id);
+  EXPECT_EQ(GetCurrentTextStartIndex(next_node_ids[0]), 0);
+  EXPECT_EQ(GetCurrentTextEndIndex(next_node_ids[0]), (int)sentence1.length());
+
+  // The next segment contains the entire superscript '[2]' with both opening
+  // and closing brackets so neither bracket is read out-of-context.
+  next_node_ids = MoveToNextGranularityAndGetText();
+  EXPECT_EQ((int)next_node_ids.size(), 3);
+
+  EXPECT_EQ(next_node_ids[0], static_text2.id);
+  EXPECT_EQ(GetCurrentTextStartIndex(next_node_ids[0]), 0);
+  EXPECT_EQ(GetCurrentTextEndIndex(next_node_ids[0]), (int)sentence2.length());
+
+  EXPECT_EQ(next_node_ids[1], static_text3.id);
+  EXPECT_EQ(GetCurrentTextStartIndex(next_node_ids[1]), 0);
+  EXPECT_EQ(GetCurrentTextEndIndex(next_node_ids[1]), (int)sentence3.length());
+
+  EXPECT_EQ(next_node_ids[2], static_text4.id);
+  EXPECT_EQ(GetCurrentTextStartIndex(next_node_ids[2]), 0);
+  EXPECT_EQ(GetCurrentTextEndIndex(next_node_ids[2]), (int)sentence4.length());
+
   // Nodes are empty at the end of the new tree.
   next_node_ids = MoveToNextGranularityAndGetText();
   EXPECT_EQ((int)next_node_ids.size(), 0);
diff --git a/chrome/renderer/accessibility/read_anything_app_model.cc b/chrome/renderer/accessibility/read_anything_app_model.cc
index b972a8e..d714f20 100644
--- a/chrome/renderer/accessibility/read_anything_app_model.cc
+++ b/chrome/renderer/accessibility/read_anything_app_model.cc
@@ -1186,6 +1186,30 @@
       // Get the index of the next sentence if we're looking at the combined
       // previous and current node text.
       int combined_sentence_index = GetNextSentence(combined_text);
+
+      bool is_opening_punctuation = false;
+      // The code that checks for accessible text boundaries sometimes
+      // incorrectly includes opening punctuation (i.e. '(', '<', etc.) as part
+      // of the prior sentence.
+      // e.g. "This is a sentence.[2]" will return a sentence boundary for
+      // "This is a sentence.[", splitting the opening and closing punctuation.
+      // When opening punctuation is split like this in Read Aloud, text will
+      // be read out for the punctuation e.g. "opening square bracket," which
+      // we want to avoid.
+      // Therefore, this is a workaround that prevents adding text from the
+      // next node to the current segment if that text is a single character
+      // and also opening punctuation. The opening punctuation will then be
+      // read out as part of the next segment. If the opening punctuation is
+      // followed by text and closing punctuation, the punctuation will not be
+      // read out directly- just the text content.
+      // TODO(crbug.com/1474951): See if it's possible to fix the code
+      // in FindAccessibleTextBoundary instead so that this workaround isn't
+      // needed.
+      if (combined_sentence_index == (int)current_text.length() + 1) {
+        char c = combined_text[combined_sentence_index - 1];
+        is_opening_punctuation = IsOpeningPunctuation(c);
+      }
+
       // If the combined_sentence_index is the same as the current_text length,
       // the new node should not be considered part of the current sentence.
       // If these values differ, add the current node's text to the list of
@@ -1203,7 +1227,8 @@
       //    The current text length is 6, and the next sentence index of
       //    "Hello. Goodbye." is still 6, so the current node's text shouldn't
       //    be added to the current sentence.
-      if ((int)current_text.length() < combined_sentence_index) {
+      if (((int)current_text.length() < combined_sentence_index) &&
+          !is_opening_punctuation) {
         anchor_node = GetNodeFromCurrentPosition();
         // Calculate the new sentence index.
         int index_in_new_node = combined_sentence_index - current_text.length();
@@ -1404,3 +1429,6 @@
   // TODO(crbug.com/1474951): Can this be updated to IsText() instead?
   return (GetHtmlTag(ax_node_id).length() == 0);
 }
+bool ReadAnythingAppModel::IsOpeningPunctuation(char c) {
+  return (c == '(' || c == '{' || c == '[' || c == '<');
+}
diff --git a/chrome/renderer/accessibility/read_anything_app_model.h b/chrome/renderer/accessibility/read_anything_app_model.h
index 86aadbf..19de79d 100644
--- a/chrome/renderer/accessibility/read_anything_app_model.h
+++ b/chrome/renderer/accessibility/read_anything_app_model.h
@@ -351,6 +351,8 @@
       ReadAnythingAppModel::ReadAloudCurrentGranularity current_granularity,
       ui::AXNodeID id);
 
+  bool IsOpeningPunctuation(char c);
+
   // State.
   // Store AXTrees of web contents in the browser's tab strip as AXTreeManagers.
   std::map<ui::AXTreeID, std::unique_ptr<ui::AXTreeManager>> tree_managers_;
diff --git a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
index e55f844..f0fa459 100644
--- a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
+++ b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
@@ -505,6 +505,16 @@
     GetMainFrame()->AutofillClient()->DidCompleteFocusChangeInFrame();
   }
 
+  void ConfigurePasswordSuggestionFiltering(bool enabled) {
+    if (enabled) {
+      scoped_feature_list_.InitAndEnableFeature(
+          password_manager::features::kNoPasswordSuggestionFiltering);
+    } else {
+      scoped_feature_list_.InitAndDisableFeature(
+          password_manager::features::kNoPasswordSuggestionFiltering);
+    }
+  }
+
   void EnableOverwritingPlaceholderUsernames() {
     scoped_feature_list_.InitAndEnableFeature(
         password_manager::features::kEnableOverwritingPlaceholderUsernames);
@@ -2325,6 +2335,45 @@
   CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true);
 }
 
+// Tests that password suggestions are prefix matched against typed username.
+// TODO(b:322923603): Clean up when the feature is launched.
+TEST_F(PasswordAutofillAgentTest, SuggestionsPrefixMatchedByTypedUsername) {
+  ConfigurePasswordSuggestionFiltering(/*enabled=*/false);
+  SimulateClosingKeyboardReplacingSurfaceIfAndroid(kUsernameName);
+
+  ClearUsernameAndPasswordFieldValues();
+  // Make sure there's password data to fill in the field.
+  SimulateOnFillPasswordForm(fill_data_);
+  // Enter the value manually.
+  SimulateUsernameTyping("ali");
+
+  // Simulate a user clicking on the username element. This should produce a
+  // message with all the usernames.
+  autofill_agent_->FormControlElementClicked(username_element_);
+  CheckSuggestions(u"ali", /*show_all=*/false);
+  base::RunLoop().RunUntilIdle();
+}
+
+// Tests that all password suggestiona are shown when suggestion filtering is
+// disabled.
+TEST_F(PasswordAutofillAgentTest,
+       SuggestionsNotPrefixMatchedWhenFeatureEnabled) {
+  ConfigurePasswordSuggestionFiltering(/*enabled=*/true);
+  SimulateClosingKeyboardReplacingSurfaceIfAndroid(kUsernameName);
+
+  ClearUsernameAndPasswordFieldValues();
+  // Make sure there's password data to fill in the field.
+  SimulateOnFillPasswordForm(fill_data_);
+  // Enter the value manually.
+  SimulateUsernameTyping("ali");
+
+  // Simulate a user clicking on the username element. This should produce a
+  // message with all the usernames.
+  autofill_agent_->FormControlElementClicked(username_element_);
+  CheckSuggestions(u"ali", /*show_all=*/true);
+  base::RunLoop().RunUntilIdle();
+}
+
 TEST_F(PasswordAutofillAgentTest,
        NoPopupOnPasswordFieldWithoutSuggestionsByDefault) {
   ClearUsernameAndPasswordFieldValues();
diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index 16d9a63..9c82ba7 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -448,6 +448,8 @@
   chrome_extensions_renderer_client->RenderThreadStarted();
   WebSecurityPolicy::RegisterURLSchemeAsExtension(
       WebString::FromASCII(extensions::kExtensionScheme));
+  WebSecurityPolicy::RegisterURLSchemeAsCodeCacheWithHashing(
+      WebString::FromASCII(extensions::kExtensionScheme));
 #endif
 
 #if BUILDFLAG(ENABLE_SPELLCHECK)
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 04b2e1ea..84edf522 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3640,6 +3640,7 @@
         "../browser/extensions/background_script_executor_browsertest.cc",
         "../browser/extensions/background_scripts_apitest.cc",
         "../browser/extensions/background_xhr_browsertest.cc",
+        "../browser/extensions/cache_wasm_extension_browsertest.cc",
         "../browser/extensions/calculator_app_browsertest.cc",
         "../browser/extensions/chrome_app_api_browsertest.cc",
         "../browser/extensions/chrome_test_extension_loader_browsertest.cc",
@@ -3984,6 +3985,7 @@
       data += [
         "//chrome/test/data/extensions/",
         "//extensions/test/data/",
+        "//third_party/blink/web_tests/http/tests/wasm/resources/large.wasm",
       ]
 
       if (enable_plugins) {
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/webapps/WebApkIntentDataProviderBuilder.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/webapps/WebApkIntentDataProviderBuilder.java
index 65fe344..2415e96 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/webapps/WebApkIntentDataProviderBuilder.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/webapps/WebApkIntentDataProviderBuilder.java
@@ -7,6 +7,7 @@
 import android.content.Intent;
 import android.graphics.Color;
 
+import org.chromium.base.TimeUtils;
 import org.chromium.blink.mojom.DisplayMode;
 import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
 import org.chromium.chrome.browser.webapps.WebApkIntentDataProviderFactory;
@@ -108,6 +109,7 @@
                 /* isSplashProvidedByWebApk= */ false,
                 null,
                 /* shortcutItems= */ new ArrayList<>(),
-                mWebApkVersionCode);
+                mWebApkVersionCode,
+                /* lastUpdateTime= */ TimeUtils.currentTimeMillis());
     }
 }
diff --git a/chrome/test/base/chromeos/crosier/upstart_test.cc b/chrome/test/base/chromeos/crosier/upstart_test.cc
index 24b85e4..aad9adc 100644
--- a/chrome/test/base/chromeos/crosier/upstart_test.cc
+++ b/chrome/test/base/chromeos/crosier/upstart_test.cc
@@ -16,7 +16,7 @@
          std::tie(b.is_valid, b.goal, b.state, b.pid);
 }
 
-TEST(Upstart, ParseStatus) {
+TEST(UpstartTest, ParseStatus) {
   JobStatus result = internal::ParseStatus("foo", "");
   EXPECT_FALSE(result.is_valid);
 
@@ -55,7 +55,7 @@
   EXPECT_EQ(result, ui);
 }
 
-TEST(Upstart, GetJobStatus) {
+TEST(UpstartTest, GetJobStatus) {
   // Random nonexistent job.
   JobStatus result = GetJobStatus("nonexistent-job");
   EXPECT_FALSE(result.is_valid);
@@ -68,12 +68,12 @@
   EXPECT_GT(result.pid, 0);
 }
 
-TEST(Upstart, JobExists) {
+TEST(UpstartTest, JobExists) {
   EXPECT_FALSE(JobExists("nonexistent-job"));
   EXPECT_TRUE(JobExists("dbus"));
 }
 
-TEST(Upstart, WaitForJobStatus) {
+TEST(UpstartTest, WaitForJobStatus) {
   // This is hard to test without making a lot of assumptions or messing with
   // the system. Instead, we just validate some simple cases that don't change
   // anything.
diff --git a/chrome/test/data/banners/manifest_svg_icon_any.json b/chrome/test/data/banners/manifest_svg_icon_any.json
new file mode 100644
index 0000000..a1184d5
--- /dev/null
+++ b/chrome/test/data/banners/manifest_svg_icon_any.json
@@ -0,0 +1,62 @@
+{
+  "short_name": "Manifest",
+  "name": "Test app SVG icon with no size",
+  "icons": [
+    {
+      "src": "red.svg",
+      "sizes": "any 64x64",
+      "type": "image/svg+xml"
+    }
+  ],
+  "start_url": "manifest_test_page.html",
+  "scope": "/",
+  "display": "standalone",
+  "description": "SVG Icon with size any specified",
+  "shortcuts": [
+    {
+      "name": "shortcut_menu_item1",
+      "url": "/",
+      "icons": [
+        {
+          "src": "red.svg",
+          "sizes": "any",
+          "type": "image/svg+xml",
+          "purpose": "any monochrome"
+        }
+      ]
+    }
+  ],
+  "file_handlers": [
+    {
+      "action": "/",
+      "name": "Plain Text",
+      "icons": [
+        {
+          "src": "red.svg",
+          "sizes": "any"
+        }
+      ],
+      "accept": {
+        "image/txt": [".txt", ".md", ".csv"]
+      }
+    }
+  ],
+  "tab_strip": {
+    "home_tab": {
+      "url": "/",
+      "icons": [
+        {
+          "src": "red.svg",
+          "sizes": "any"
+        }
+      ],
+      "scope_patterns": [
+        {"pathname": "/[^/]*/.*"}
+      ]
+    },
+    "new_tab_button": {
+      "url": "one.html"
+    }
+  }
+}
+  
\ No newline at end of file
diff --git a/chrome/test/data/banners/manifest_svg_icon_no_intrinsic_size.json b/chrome/test/data/banners/manifest_svg_icon_no_intrinsic_size.json
new file mode 100644
index 0000000..3d16b96
--- /dev/null
+++ b/chrome/test/data/banners/manifest_svg_icon_no_intrinsic_size.json
@@ -0,0 +1,62 @@
+{
+  "short_name": "Manifest",
+  "name": "Test app SVG icon with no size",
+  "icons": [
+    {
+      "src": "red_no_size.svg",
+      "sizes": "any",
+      "type": "image/svg+xml"
+    }
+  ],
+  "start_url": "manifest_test_page.html",
+  "scope": "/",
+  "display": "standalone",
+  "description": "SVG Icon with size any specified",
+  "shortcuts": [
+    {
+      "name": "shortcut_menu_item1",
+      "url": "/",
+      "icons": [
+        {
+          "src": "red_no_size.svg",
+          "sizes": "any",
+          "type": "image/svg+xml",
+          "purpose": "any monochrome"
+        }
+      ]
+    }
+  ],
+  "file_handlers": [
+    {
+      "action": "/",
+      "name": "Plain Text",
+      "icons": [
+        {
+          "src": "red_no_size.svg",
+          "sizes": "any"
+        }
+      ],
+      "accept": {
+        "image/txt": [".txt", ".md", ".csv"]
+      }
+    }
+  ],
+  "tab_strip": {
+    "home_tab": {
+      "url": "/",
+      "icons": [
+        {
+          "src": "red_no_size.svg",
+          "sizes": "any"
+        }
+      ],
+      "scope_patterns": [
+        {"pathname": "/[^/]*/.*"}
+      ]
+    },
+    "new_tab_button": {
+      "url": "one.html"
+    }
+  }
+}
+  
\ No newline at end of file
diff --git a/chrome/test/data/banners/red.svg b/chrome/test/data/banners/red.svg
new file mode 100644
index 0000000..2c97935
--- /dev/null
+++ b/chrome/test/data/banners/red.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="144" height="144" viewBox="0 0 144 144" version="1.1">
+<path d="M -0 112.504 L -0 225.009 112.750 224.754 L 225.500 224.500 225.754 112.250 L 226.009 0 113.004 0 L 0 0 -0 112.504 M -0 112.750 L -0 224.500 112 224.241 L 224 223.983 224 112.491 L 224 1 112 1 L 0 1 -0 112.750" stroke="none" fill="#fcaaaa" fill-rule="evenodd"/>
+<path d="M 1 112.500 L 1 224 112.500 224 L 224 224 224 112.500 L 224 1 112.500 1 L 1 1 1 112.500" stroke="none" fill="#fc0404" fill-rule="evenodd"/>
+</svg>
\ No newline at end of file
diff --git a/chrome/test/data/banners/red_no_size.svg b/chrome/test/data/banners/red_no_size.svg
new file mode 100644
index 0000000..b4010c0
--- /dev/null
+++ b/chrome/test/data/banners/red_no_size.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 144 144" version="1.1">
+<path d="M -0 112.504 L -0 225.009 112.750 224.754 L 225.500 224.500 225.754 112.250 L 226.009 0 113.004 0 L 0 0 -0 112.504 M -0 112.750 L -0 224.500 112 224.241 L 224 223.983 224 112.491 L 224 1 112 1 L 0 1 -0 112.750" stroke="none" fill="#fcaaaa" fill-rule="evenodd"/>
+<path d="M 1 112.500 L 1 224 112.500 224 L 224 224 224 112.500 L 224 1 112.500 1 L 1 1 1 112.500" stroke="none" fill="#fc0404" fill-rule="evenodd"/>
+</svg>
\ No newline at end of file
diff --git a/chrome/test/data/extensions/api_test/tts/enqueue/test.js b/chrome/test/data/extensions/api_test/tts/enqueue/test.js
index a8de8a3..0ffaad7b 100644
--- a/chrome/test/data/extensions/api_test/tts/enqueue/test.js
+++ b/chrome/test/data/extensions/api_test/tts/enqueue/test.js
@@ -5,39 +5,36 @@
 // TTS api test for Chrome on ChromeOS.
 // browser_tests.exe --gtest_filter="TtsApiTest.*"
 
-chrome.test.runTests([
-  function testEnqueue() {
-    var callbacks = 0;
-    chrome.tts.speak(
-        'text 1',
-        {
-         'enqueue': true,
-         'onEvent': function(event) {
-           chrome.test.assertEq('end', event.type);
-           callbacks++;
-         }
-        },
-        function() {
-          chrome.test.assertNoLastError();
+chrome.test.runTests([function testEnqueue() {
+  let callbacks = 0;
+  chrome.tts.speak(
+      'text 1', {
+        'enqueue': true,
+        'onEvent': (event) => {
+          chrome.test.assertEq('end', event.type);
+          chrome.test.assertEq(2, callbacks);
           callbacks++;
-        });
-    chrome.tts.speak(
-        'text 2',
-        {
-         'enqueue': true,
-         'onEvent': function(event) {
-           chrome.test.assertEq('end', event.type);
-           callbacks++;
-           if (callbacks == 4) {
-             chrome.test.succeed();
-           } else {
-             chrome.test.fail();
-           }
-         }
-        },
-        function() {
-          chrome.test.assertNoLastError();
-          callbacks++;
-        });
-  }
-]);
+        }
+      },
+      () => {
+        // This happens immediately.
+        chrome.test.assertNoLastError();
+        chrome.test.assertEq(0, callbacks);
+        callbacks++;
+      });
+  chrome.tts.speak(
+      'text 2', {
+        'enqueue': true,
+        'onEvent': (event) => {
+          chrome.test.assertEq('end', event.type);
+          chrome.test.assertEq(3, callbacks);
+          chrome.test.succeed();
+        }
+      },
+      () => {
+        // This happens immediately.
+        chrome.test.assertNoLastError();
+        chrome.test.assertEq(1, callbacks);
+        callbacks++;
+      });
+}]);
diff --git a/chrome/test/data/extensions/api_test/tts/service_worker_enqueue/test.js b/chrome/test/data/extensions/api_test/tts/service_worker_enqueue/test.js
index 7c3f78f..16f0a3f 100644
--- a/chrome/test/data/extensions/api_test/tts/service_worker_enqueue/test.js
+++ b/chrome/test/data/extensions/api_test/tts/service_worker_enqueue/test.js
@@ -10,11 +10,13 @@
         'enqueue': true,
         'onEvent': event => {
           chrome.test.assertEq('end', event.type);
+          chrome.test.assertEq(2, callbacks);
           callbacks++;
         }
       },
       () => {
         chrome.test.assertNoLastError();
+        chrome.test.assertEq(0, callbacks);
         callbacks++;
       });
   // Try async promise-style API.
@@ -22,13 +24,10 @@
     'enqueue': true,
     'onEvent': event => {
       chrome.test.assertEq('end', event.type);
-      callbacks++;
-      if (callbacks == 4) {
-        chrome.test.succeed();
-      } else {
-        chrome.test.fail();
-      }
+      chrome.test.assertEq(3, callbacks);
+      chrome.test.succeed();
     }
   });
+  chrome.test.assertEq(1, callbacks);
   callbacks++;
 }]);
diff --git a/chrome/test/data/webui/chromeos/firmware_update/firmware_update_dialog_test.ts b/chrome/test/data/webui/chromeos/firmware_update/firmware_update_dialog_test.ts
index 45dca726..8cbd964 100644
--- a/chrome/test/data/webui/chromeos/firmware_update/firmware_update_dialog_test.ts
+++ b/chrome/test/data/webui/chromeos/firmware_update/firmware_update_dialog_test.ts
@@ -122,6 +122,12 @@
     assertEquals(
         getTextContent('#updateDialogBody'),
         loadTimeData.getString('restartingBodyText'));
+    // Body text should not have an aria-live value for non-requests.
+    assertEquals(
+        strictQuery(
+            '#updateDialogBody', updateDialogElement.shadowRoot, HTMLDivElement)
+            .ariaLive,
+        '');
     assertEquals(
         getTextContent('#progress'),
         loadTimeData.getString('restartingFooterText'));
@@ -221,6 +227,12 @@
     assertEquals(
         getTextContent('#updateDialogBody'),
         loadTimeData.getString('restartingBodyText'));
+    // Body text should not have an aria-live value for non-requests.
+    assertEquals(
+        strictQuery(
+            '#updateDialogBody', updateDialogElement.shadowRoot, HTMLDivElement)
+            .ariaLive,
+        '');
     assertEquals(
         getTextContent('#progress'),
         loadTimeData.getString('restartingFooterText'));
@@ -375,6 +387,15 @@
       assertEquals(
           getTextContent('#updateDialogBody'),
           loadTimeData.getString(expectedString));
+      assert(updateDialogElement?.shadowRoot);
+      // For user requests, the dialog body should be an assertive aria-live
+      // region.
+      assertEquals(
+          strictQuery(
+              '#updateDialogBody', updateDialogElement.shadowRoot,
+              HTMLDivElement)
+              .ariaLive,
+          'assertive');
       assertEquals(
           getTextContent('#progress'),
           loadTimeData.getStringF('waitingFooterText', 70));
diff --git a/chrome/test/data/webui/cr_components/settings_prefs_test.ts b/chrome/test/data/webui/cr_components/settings_prefs_test.ts
index 4b2b991..7c6be696 100644
--- a/chrome/test/data/webui/cr_components/settings_prefs_test.ts
+++ b/chrome/test/data/webui/cr_components/settings_prefs_test.ts
@@ -80,7 +80,7 @@
 
     prefs = document.createElement('settings-prefs');
     document.body.appendChild(prefs);
-    prefs.initialize(fakeApi as unknown as typeof chrome.settingsPrivate);
+    prefs.initialize(fakeApi);
 
     // getAllPrefs is asynchronous, so return the prefs promise.
     return CrSettingsPrefs.initialized;
diff --git a/chrome/test/data/webui/fake_settings_private.ts b/chrome/test/data/webui/fake_settings_private.ts
index f506f0f0..3131c56c 100644
--- a/chrome/test/data/webui/fake_settings_private.ts
+++ b/chrome/test/data/webui/fake_settings_private.ts
@@ -10,18 +10,30 @@
 
 /** @fileoverview Fake implementation of chrome.settingsPrivate for testing. */
 
+type SettingsPrivateApi = typeof chrome.settingsPrivate;
+type PrefObject = chrome.settingsPrivate.PrefObject;
+type PrefType = chrome.settingsPrivate.PrefType;
+
 /**
  * Fake of chrome.settingsPrivate API. Use by setting
  * CrSettingsPrefs.deferInitialization to true, then passing a
  * FakeSettingsPrivate to settings-prefs#initialize().
  */
-export class FakeSettingsPrivate extends TestBrowserProxy {
+export class FakeSettingsPrivate extends TestBrowserProxy implements
+    SettingsPrivateApi {
+  // Mirroring chrome.settingsPrivate API members.
+  /* eslint-disable @typescript-eslint/naming-convention */
+  PrefType = chrome.settingsPrivate.PrefType;
+  ControlledBy = chrome.settingsPrivate.ControlledBy;
+  Enforcement = chrome.settingsPrivate.Enforcement;
+  /* eslint-enable @typescript-eslint/naming-convention */
+
+  prefs: Record<string, PrefObject> = {};
+  onPrefsChanged: FakeChromeEvent = new FakeChromeEvent();
   private disallowSetPref_: boolean = false;
   private failNextSetPref_: boolean = false;
-  prefs: {[key: string]: chrome.settingsPrivate.PrefObject} = {};
-  onPrefsChanged: FakeChromeEvent = new FakeChromeEvent();
 
-  constructor(initialPrefs?: chrome.settingsPrivate.PrefObject[]) {
+  constructor(initialPrefs?: PrefObject[]) {
     super([
       'setPref',
       'getPref',
@@ -36,18 +48,16 @@
   }
 
   // chrome.settingsPrivate overrides.
-  getAllPrefs(): Promise<chrome.settingsPrivate.PrefObject[]> {
+  getAllPrefs(): Promise<PrefObject[]> {
     // Send a copy of prefs to keep our internal state private.
     const prefs = [];
     for (const key in this.prefs) {
-      prefs.push(
-          structuredClone(this.prefs[key]!) as
-          chrome.settingsPrivate.PrefObject);
+      prefs.push(structuredClone(this.prefs[key]!));
     }
     return Promise.resolve(prefs);
   }
 
-  setPref(key: string, value: any, _pageId: string): Promise<boolean> {
+  setPref(key: string, value: any, _pageId?: string): Promise<boolean> {
     this.methodCalled('setPref', {key, value});
     const pref = this.prefs[key];
     assertNotEquals(undefined, pref);
@@ -69,12 +79,11 @@
     return Promise.resolve(true);
   }
 
-  getPref(key: string): Promise<chrome.settingsPrivate.PrefObject> {
+  getPref(key: string): Promise<PrefObject> {
     this.methodCalled('getPref', key);
     const pref = this.prefs[key];
     assertNotEquals(undefined, pref);
-    return Promise.resolve(
-        structuredClone(pref!) as chrome.settingsPrivate.PrefObject);
+    return Promise.resolve(structuredClone(pref!));
   }
 
   // Functions used by tests.
@@ -102,19 +111,20 @@
       const pref = this.prefs[change.key];
       assertNotEquals(undefined, pref);
       pref!.value = change.value;
-      prefs.push(structuredClone(pref!) as chrome.settingsPrivate.PrefObject);
+      prefs.push(structuredClone(pref!) as PrefObject);
     }
     this.onPrefsChanged.callListeners(prefs);
   }
 
-  getDefaultZoom() {}
+  getDefaultZoom(): Promise<number> {
+    return Promise.resolve(100);
+  }
 
-  setDefaultZoom() {}
+  setDefaultZoom(): void {}
 
   // Private methods for use by the fake API.
 
-  private addPref_(
-      type: chrome.settingsPrivate.PrefType, key: string, value: any) {
+  private addPref_(type: PrefType, key: string, value: any) {
     this.prefs[key] = {
       type: type,
       key: key,
diff --git a/chrome/test/data/webui/settings/autofill_page_test.ts b/chrome/test/data/webui/settings/autofill_page_test.ts
index 22314ba..503f304 100644
--- a/chrome/test/data/webui/settings/autofill_page_test.ts
+++ b/chrome/test/data/webui/settings/autofill_page_test.ts
@@ -51,43 +51,43 @@
       CrSettingsPrefs.deferInitialization = true;
       const prefs = document.createElement('settings-prefs');
       prefs.initialize(new FakeSettingsPrivate([
-                         {
-                           key: 'autofill.enabled',
-                           type: chrome.settingsPrivate.PrefType.BOOLEAN,
-                           value: autofill,
-                         },
-                         {
-                           key: 'autofill.profile_enabled',
-                           type: chrome.settingsPrivate.PrefType.BOOLEAN,
-                           value: true,
-                         },
-                         {
-                           key: 'autofill.credit_card_enabled',
-                           type: chrome.settingsPrivate.PrefType.BOOLEAN,
-                           value: true,
-                         },
-                         {
-                           key: 'credentials_enable_service',
-                           type: chrome.settingsPrivate.PrefType.BOOLEAN,
-                           value: passwords,
-                         },
-                         {
-                           key: 'credentials_enable_autosignin',
-                           type: chrome.settingsPrivate.PrefType.BOOLEAN,
-                           value: true,
-                         },
-                         {
-                           key: 'payments.can_make_payment_enabled',
-                           type: chrome.settingsPrivate.PrefType.BOOLEAN,
-                           value: true,
-                         },
-                         {
-                           key: 'autofill.payment_methods_mandatory_reauth',
-                           type: chrome.settingsPrivate.PrefType.BOOLEAN,
-                           value: true,
+        {
+          key: 'autofill.enabled',
+          type: chrome.settingsPrivate.PrefType.BOOLEAN,
+          value: autofill,
+        },
+        {
+          key: 'autofill.profile_enabled',
+          type: chrome.settingsPrivate.PrefType.BOOLEAN,
+          value: true,
+        },
+        {
+          key: 'autofill.credit_card_enabled',
+          type: chrome.settingsPrivate.PrefType.BOOLEAN,
+          value: true,
+        },
+        {
+          key: 'credentials_enable_service',
+          type: chrome.settingsPrivate.PrefType.BOOLEAN,
+          value: passwords,
+        },
+        {
+          key: 'credentials_enable_autosignin',
+          type: chrome.settingsPrivate.PrefType.BOOLEAN,
+          value: true,
+        },
+        {
+          key: 'payments.can_make_payment_enabled',
+          type: chrome.settingsPrivate.PrefType.BOOLEAN,
+          value: true,
+        },
+        {
+          key: 'autofill.payment_methods_mandatory_reauth',
+          type: chrome.settingsPrivate.PrefType.BOOLEAN,
+          value: true,
 
-                         },
-                       ]) as unknown as typeof chrome.settingsPrivate);
+        },
+      ]));
 
       CrSettingsPrefs.initialized.then(function() {
         resolve(prefs);
diff --git a/chrome/test/data/webui/settings/chromeos/BUILD.gn b/chrome/test/data/webui/settings/chromeos/BUILD.gn
index 5ca202c..21ca2e9 100644
--- a/chrome/test/data/webui/settings/chromeos/BUILD.gn
+++ b/chrome/test/data/webui/settings/chromeos/BUILD.gn
@@ -32,7 +32,6 @@
     "fake_personalization_search_handler.ts",
     "fake_quick_unlock_private.ts",
     "fake_receive_manager.ts",
-    "fake_settings_private.ts",
     "fake_settings_search_handler.ts",
     "fake_system_display.ts",
     "fake_user_action_recorder.ts",
@@ -41,9 +40,7 @@
     "lacros_extension_controlled_indicator_test.ts",
     "lock_screen_subpage_test.ts",
     "onc_mojo_test.ts",
-    "os_about_page_tests.js",
     "os_page_availability_test.ts",
-    "test_about_page_browser_proxy.ts",
     "test_api.ts",
     "test_extension_control_browser_proxy.ts",
     "test_lacros_extension_control_browser_proxy.ts",
@@ -52,7 +49,6 @@
     "utils.ts",
 
     # Subfolder files.
-    "app_management/app_test.js",
     "app_management/fake_page_handler.ts",
     "app_management/file_handling_item_test.ts",
     "app_management/managed_apps_test.ts",
@@ -205,6 +201,8 @@
     "os_about_page/consumer_auto_update_toggle_dialog_test.ts",
     "os_about_page/detailed_build_info_subpage_test.ts",
     "os_about_page/edit_hostname_dialog_test.ts",
+    "os_about_page/os_about_page_test.ts",
+    "os_about_page/test_about_page_browser_proxy.ts",
     "os_about_page/test_device_name_browser_proxy.ts",
 
     "os_apps_page/app_management_page/app_detail_view_test.ts",
diff --git a/chrome/test/data/webui/settings/chromeos/app_management/app_test.js b/chrome/test/data/webui/settings/chromeos/app_management/app_test.js
deleted file mode 100644
index 68ca9f6..0000000
--- a/chrome/test/data/webui/settings/chromeos/app_management/app_test.js
+++ /dev/null
@@ -1,121 +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.
-
-'use strict';
-
-suite('<app-management-app>', () => {
-  let app;
-  let fakeHandler;
-  let store;
-
-  /** @return {ExpandableAppList} */
-  function getAppList() {
-    return app.shadowRoot.querySelector('app-management-main-view')
-        .shadowRoot.querySelector('app-management-expandable-app-list');
-  }
-
-  /** @param {String} term  */
-  async function searchApps(term) {
-    app.dispatch(app_management.actions.setSearchTerm(term));
-    await test_util.flushTasks();
-  }
-
-  /** @return {boolean} */
-  function isSearchViewShown() {
-    return !!app.shadowRoot.querySelector('app-management-search-view');
-  }
-
-  /** @return {boolean} */
-  function isMainViewShown() {
-    return !!app.shadowRoot.querySelector('app-management-main-view');
-  }
-
-  /** @return {boolean} */
-  function isDetailViewShown() {
-    return !!app.shadowRoot.querySelector('app-management-pwa-detail-view');
-  }
-
-  setup(async () => {
-    fakeHandler = setupFakeHandler();
-    store = replaceStore();
-    app = document.createElement('app-management-app');
-    replaceBody(app);
-    await app.shadowRoot.querySelector('app-management-dom-switch')
-        .firstRenderForTesting_.promise;
-    await test_util.flushTasks();
-  });
-
-  test('loads', async () => {
-    // Check that the browser responds to the getApps() message.
-    const {apps: initialApps} =
-        await app_management.BrowserProxy.getInstance().handler.getApps();
-  });
-
-  test('Searching switches to search page', async () => {
-    app.shadowRoot.querySelector('cr-toolbar')
-        .fire('search-changed', 'SearchTest');
-    assert(app.shadowRoot.querySelector('app-management-search-view'));
-  });
-
-  test('App list renders on page change', (done) => {
-    const appList = getAppList();
-    let numApps = 0;
-
-    fakeHandler.addApp()
-        .then(() => {
-          numApps = 1;
-          assertEquals(numApps, appList.numChildrenForTesting_);
-
-          // Click app to go to detail page.
-          appList.querySelector('app-management-app-item').click();
-          return test_util.flushTasks();
-        })
-        .then(() => {
-          return fakeHandler.addApp();
-        })
-        .then(() => {
-          numApps++;
-
-          appList.addEventListener('num-children-for-testing_-changed', () => {
-            assertEquals(numApps, appList.numChildrenForTesting_);
-            done();
-          });
-
-          // Click back button to go to main page.
-          app.shadowRoot.querySelector('app-management-pwa-detail-view')
-              .shadowRoot.querySelector('app-management-detail-view-header')
-              .shadowRoot.querySelector('#backButton')
-              .click();
-          test_util.flushTasks();
-        });
-  });
-
-  test('Search from main page', async () => {
-    await navigateTo('/');
-    assertTrue(isMainViewShown());
-
-    await searchApps('o');
-    assertTrue(isSearchViewShown());
-    assertEquals('/?q=o', getCurrentUrlSuffix());
-
-    await searchApps('');
-    assertTrue(isMainViewShown());
-    assertEquals('/', getCurrentUrlSuffix());
-  });
-
-  test('Search from detail page', async () => {
-    await fakeHandler.addApp();
-
-    await navigateTo('/detail?id=0');
-    assertTrue(isDetailViewShown());
-
-    await searchApps('o');
-    assertTrue(isSearchViewShown());
-    assertEquals('/detail?id=0&q=o', getCurrentUrlSuffix());
-
-    await searchApps('');
-    assertTrue(isDetailViewShown());
-    assertEquals('/detail?id=0', getCurrentUrlSuffix());
-  });
-});
diff --git a/chrome/test/data/webui/settings/chromeos/fake_settings_private.ts b/chrome/test/data/webui/settings/chromeos/fake_settings_private.ts
deleted file mode 100644
index 27fcbc6..0000000
--- a/chrome/test/data/webui/settings/chromeos/fake_settings_private.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2015 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Fake implementation of chrome.settingsPrivate for testing.
- */
-
-import {assertEquals, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {FakeChromeEvent} from 'chrome://webui-test/fake_chrome_event.js';
-
-type SettingsPrivateApi = typeof chrome.settingsPrivate;
-type PrefObject = chrome.settingsPrivate.PrefObject;
-type PrefType = chrome.settingsPrivate.PrefType;
-
-const deepCopy = structuredClone;
-
-/**
- * Fake of chrome.settingsPrivate API. Use by setting
- * CrSettingsPrefs.deferInitialization to true, then passing a
- * FakeSettingsPrivate to settings-prefs#initialize().
- */
-export class FakeSettingsPrivate implements SettingsPrivateApi {
-  // Mirroring chrome.settingsPrivate API members.
-  /* eslint-disable @typescript-eslint/naming-convention */
-  PrefType = chrome.settingsPrivate.PrefType;
-  ControlledBy = chrome.settingsPrivate.ControlledBy;
-  Enforcement = chrome.settingsPrivate.Enforcement;
-  /* eslint-enable @typescript-eslint/naming-convention */
-
-  prefs: Record<string, PrefObject> = {};
-  onPrefsChanged = new FakeChromeEvent();
-  private disallowSetPref_ = false;
-  private failNextSetPref_ = false;
-
-
-  constructor(initialPrefs?: PrefObject[]) {
-    if (initialPrefs) {
-      for (const pref of initialPrefs) {
-        this.addPref_(pref.type, pref.key, pref.value);
-      }
-    }
-  }
-
-  // chrome.settingsPrivate overrides.
-  getAllPrefs(): Promise<PrefObject[]> {
-    // Send a copy of prefs to keep our internal state private.
-    const prefs: PrefObject[] = [];
-    for (const key in this.prefs) {
-      prefs.push(deepCopy(this.prefs[key]!));
-    }
-    return Promise.resolve(prefs);
-  }
-
-  setPref(key: string, value: unknown, _pageId?: string): Promise<boolean> {
-    const pref = this.prefs[key];
-    assertTrue(!!pref);
-    assertEquals(typeof value, typeof pref.value);
-    assertEquals(Array.isArray(value), Array.isArray(pref.value));
-
-    if (this.failNextSetPref_) {
-      this.failNextSetPref_ = false;
-      return Promise.resolve(false);
-    }
-    assertNotEquals(true, this.disallowSetPref_);
-
-    const changed = JSON.stringify(pref.value) !== JSON.stringify(value);
-    pref.value = deepCopy(value);
-
-    // Like chrome.settingsPrivate, send a notification when prefs change.
-    if (changed) {
-      this.sendPrefChanges([{key, value: deepCopy(value)}]);
-    }
-    return Promise.resolve(true);
-  }
-
-  getPref(key: string): Promise<PrefObject> {
-    const pref = this.prefs[key];
-    assertTrue(!!pref);
-    return Promise.resolve(deepCopy(pref));
-  }
-
-  // Functions used by tests.
-
-  /** Instructs the API to return a failure when setPref is next called. */
-  failNextSetPref(): void {
-    this.failNextSetPref_ = true;
-  }
-
-  /** Instructs the API to assert (fail the test) if setPref is called. */
-  disallowSetPref(): void {
-    this.disallowSetPref_ = true;
-  }
-
-  allowSetPref(): void {
-    this.disallowSetPref_ = false;
-  }
-
-  /**
-   * Notifies the listeners of pref changes.
-   */
-  sendPrefChanges(changes: Array<{key: string, value: unknown}>): void {
-    const prefs = [];
-    for (const change of changes) {
-      const pref = this.prefs[change.key];
-      assertTrue(!!pref);
-      pref.value = change.value;
-      prefs.push(deepCopy(pref));
-    }
-    this.onPrefsChanged.callListeners(prefs);
-  }
-
-  getDefaultZoom(): Promise<number> {
-    return Promise.resolve(1);
-  }
-
-  setDefaultZoom(): void {}
-
-  // Private methods for use by the fake API.
-  private addPref_(type: PrefType, key: string, value: unknown): void {
-    this.prefs[key] = {
-      type,
-      key,
-      value,
-    };
-  }
-}
diff --git a/chrome/test/data/webui/settings/chromeos/os_about_page/channel_switcher_dialog_test.ts b/chrome/test/data/webui/settings/chromeos/os_about_page/channel_switcher_dialog_test.ts
index 2c970a9..86b56ffe 100644
--- a/chrome/test/data/webui/settings/chromeos/os_about_page/channel_switcher_dialog_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_about_page/channel_switcher_dialog_test.ts
@@ -10,9 +10,10 @@
 import {assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';
 
-import {TestAboutPageBrowserProxy} from '../test_about_page_browser_proxy.js';
 import {clearBody} from '../utils.js';
 
+import {TestAboutPageBrowserProxy} from './test_about_page_browser_proxy.js';
+
 suite('<settings-channel-switcher-dialog>', () => {
   let dialog: SettingsChannelSwitcherDialogElement;
   let browserProxy: TestAboutPageBrowserProxy;
diff --git a/chrome/test/data/webui/settings/chromeos/os_about_page/consumer_auto_update_toggle_dialog_test.ts b/chrome/test/data/webui/settings/chromeos/os_about_page/consumer_auto_update_toggle_dialog_test.ts
index c653cf8..553a8334 100644
--- a/chrome/test/data/webui/settings/chromeos/os_about_page/consumer_auto_update_toggle_dialog_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_about_page/consumer_auto_update_toggle_dialog_test.ts
@@ -10,9 +10,10 @@
 import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
 import {eventToPromise} from 'chrome://webui-test/test_util.js';
 
-import {TestAboutPageBrowserProxy} from '../test_about_page_browser_proxy.js';
 import {clearBody} from '../utils.js';
 
+import {TestAboutPageBrowserProxy} from './test_about_page_browser_proxy.js';
+
 suite('<settings-consumer-auto-update-toggle-dialog>', () => {
   let dialog: SettingsConsumerAutoUpdateToggleDialogElement;
   let browserProxy: TestAboutPageBrowserProxy;
diff --git a/chrome/test/data/webui/settings/chromeos/os_about_page/detailed_build_info_subpage_test.ts b/chrome/test/data/webui/settings/chromeos/os_about_page/detailed_build_info_subpage_test.ts
index 9433fe8..45ad479 100644
--- a/chrome/test/data/webui/settings/chromeos/os_about_page/detailed_build_info_subpage_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_about_page/detailed_build_info_subpage_test.ts
@@ -14,9 +14,9 @@
 import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 import {isVisible} from 'chrome://webui-test/test_util.js';
 
-import {TestAboutPageBrowserProxy} from '../test_about_page_browser_proxy.js';
 import {clearBody} from '../utils.js';
 
+import {TestAboutPageBrowserProxy} from './test_about_page_browser_proxy.js';
 import {TestDeviceNameBrowserProxy} from './test_device_name_browser_proxy.js';
 
 suite('<detailed-build-info-subpage>', () => {
diff --git a/chrome/test/data/webui/settings/chromeos/os_about_page/os_about_page_test.ts b/chrome/test/data/webui/settings/chromeos/os_about_page/os_about_page_test.ts
new file mode 100644
index 0000000..ae46293
--- /dev/null
+++ b/chrome/test/data/webui/settings/chromeos/os_about_page/os_about_page_test.ts
@@ -0,0 +1,844 @@
+// Copyright 2019 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://os-settings/os_settings.js';
+import 'chrome://os-settings/lazy_load.js';
+
+import {AboutPageBrowserProxyImpl, BrowserChannel, IronIconElement, LifetimeBrowserProxyImpl, OsAboutPageElement, Router, routes, settingMojom, setUserActionRecorderForTesting, UpdateStatus, userActionRecorderMojom} from 'chrome://os-settings/os_settings.js';
+import {webUIListenerCallback} from 'chrome://resources/js/cr.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
+import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {assertEquals, assertFalse, assertNotEquals, assertNull, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
+import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';
+
+import {FakeUserActionRecorder} from '../fake_user_action_recorder.js';
+import {TestLifetimeBrowserProxy} from '../test_os_lifetime_browser_proxy.js';
+import {clearBody} from '../utils.js';
+
+import {TestAboutPageBrowserProxy} from './test_about_page_browser_proxy.js';
+
+type UserActionRecorderInterface =
+    userActionRecorderMojom.UserActionRecorderInterface;
+
+suite('<os-about-page> AllBuilds', () => {
+  const isRevampWayfindingEnabled =
+      loadTimeData.getBoolean('isRevampWayfindingEnabled');
+  let page: OsAboutPageElement;
+  let aboutBrowserProxy: TestAboutPageBrowserProxy;
+  let lifetimeBrowserProxy: TestLifetimeBrowserProxy;
+  let userActionRecorder: UserActionRecorderInterface;
+
+  const SPINNER_ICON_LIGHT_MODE =
+      'chrome://resources/images/throbber_small.svg';
+  const SPINNER_ICON_DARK_MODE =
+      'chrome://resources/images/throbber_small_dark.svg';
+
+  setup(async () => {
+    loadTimeData.overrideValues({
+      isManaged: false,
+    });
+
+    userActionRecorder = new FakeUserActionRecorder();
+    setUserActionRecorderForTesting(userActionRecorder);
+
+    lifetimeBrowserProxy = new TestLifetimeBrowserProxy();
+    LifetimeBrowserProxyImpl.setInstance(lifetimeBrowserProxy);
+
+    aboutBrowserProxy = new TestAboutPageBrowserProxy();
+    AboutPageBrowserProxyImpl.setInstanceForTesting(aboutBrowserProxy);
+
+    Router.getInstance().navigateTo(routes.ABOUT);
+  });
+
+  teardown(() => {
+    Router.getInstance().resetRouteForTesting();
+  });
+
+  interface StatusChangeEventOptions {
+    progress?: number;
+    message?: string;
+    rollback?: boolean;
+    powerwash?: boolean;
+    version?: string;
+    size?: string;
+  }
+
+  function fireStatusChanged(
+      status: UpdateStatus, options: StatusChangeEventOptions = {}): void {
+    webUIListenerCallback('update-status-changed', {
+      progress: options.progress === undefined ? 1 : options.progress,
+      message: options.message,
+      status,
+      rollback: options.rollback,
+      powerwash: options.powerwash,
+      version: options.version,
+      size: options.size,
+    });
+  }
+
+  async function initPage(): Promise<void> {
+    clearBody();
+    page = document.createElement('os-about-page');
+    document.body.appendChild(page);
+    await flushTasks();
+    await Promise.all([
+      aboutBrowserProxy.whenCalled('getChannelInfo'),
+      aboutBrowserProxy.whenCalled('refreshUpdateStatus'),
+      aboutBrowserProxy.whenCalled('refreshTpmFirmwareUpdateStatus'),
+      aboutBrowserProxy.whenCalled('checkInternetConnection'),
+    ]);
+  }
+
+  function deepLinkToSetting(setting: settingMojom.Setting): void {
+    const params = new URLSearchParams();
+    params.append('settingId', setting.toString());
+    Router.getInstance().navigateTo(routes.ABOUT, params);
+    flush();
+  }
+
+  if (isRevampWayfindingEnabled) {
+    test('Crostini settings card is visible', async () => {
+      await initPage();
+      const crostiniSettingsCard =
+          page.shadowRoot!.querySelector('crostini-settings-card');
+      assertTrue(isVisible(crostiniSettingsCard));
+    });
+  }
+
+  ['light', 'dark'].forEach((mode) => {
+    suite(`with ${mode} mode active`, () => {
+      const isDarkMode = mode === 'dark';
+
+      function setDarkMode(isActive: boolean): void {
+        page.set('isDarkModeActive_', isActive);
+      }
+
+      /**
+       * Test that the OS update status message and icon update according to
+       * incoming 'update-status-changed' events, for light and dark mode
+       * respectively.
+       */
+      test('status message and icon update', async () => {
+        await initPage();
+        setDarkMode(isDarkMode);
+        const expectedIcon =
+            isDarkMode ? SPINNER_ICON_DARK_MODE : SPINNER_ICON_LIGHT_MODE;
+
+        const icon = page.shadowRoot!.querySelector('iron-icon');
+        assertTrue(!!icon);
+        const statusMessageEl = page.$.updateStatusMessageInner;
+        let previousMessageText = statusMessageEl.innerText;
+
+        fireStatusChanged(UpdateStatus.CHECKING);
+        assertEquals(expectedIcon, icon.src);
+        assertNull(icon.getAttribute('icon'));
+        assertNotEquals(previousMessageText, statusMessageEl.innerText);
+        previousMessageText = statusMessageEl.innerText;
+
+        fireStatusChanged(UpdateStatus.UPDATING, {progress: 0});
+        assertEquals(expectedIcon, icon.src);
+        assertNull(icon.getAttribute('icon'));
+        assertFalse(statusMessageEl.innerText.includes('%'));
+        assertNotEquals(previousMessageText, statusMessageEl.innerText);
+        previousMessageText = statusMessageEl.innerText;
+
+        fireStatusChanged(UpdateStatus.UPDATING, {progress: 1});
+        assertNotEquals(previousMessageText, statusMessageEl.innerText);
+        assertTrue(statusMessageEl.innerText.includes('%'));
+        previousMessageText = statusMessageEl.innerText;
+
+        fireStatusChanged(UpdateStatus.NEARLY_UPDATED);
+        assertNull(icon.src);
+        assertEquals(
+            isRevampWayfindingEnabled ? 'os-settings:about-update-complete' :
+                                        'settings:check-circle',
+            icon.icon);
+        assertNotEquals(previousMessageText, statusMessageEl.innerText);
+        previousMessageText = statusMessageEl.innerText;
+
+        fireStatusChanged(UpdateStatus.DISABLED_BY_ADMIN);
+        assertNull(icon.src);
+        assertEquals('cr20:domain', icon.icon);
+        assertNotEquals(previousMessageText, statusMessageEl.innerText);
+
+        fireStatusChanged(UpdateStatus.FAILED);
+        assertNull(icon.src);
+        assertEquals(
+            isRevampWayfindingEnabled ? 'os-settings:about-update-error' :
+                                        'cr:error-outline',
+            icon.icon);
+        assertEquals(0, statusMessageEl.innerText.trim().length);
+
+        fireStatusChanged(UpdateStatus.DISABLED);
+        assertNull(icon.src);
+        assertNull(icon.getAttribute('icon'));
+        assertEquals(0, statusMessageEl.innerText.trim().length);
+      });
+    });
+  });
+
+  test('Error HTML is reflected in the update status message', async () => {
+    await initPage();
+    const htmlError = 'hello<br>there<br>was<pre>an</pre>error';
+    fireStatusChanged(UpdateStatus.FAILED, {message: htmlError});
+    assertEquals(htmlError, page.$.updateStatusMessageInner.innerHTML);
+  });
+
+  test('Learn more link is shown when update fails', async () => {
+    await initPage();
+
+    // Check that link is shown when update failed.
+    fireStatusChanged(UpdateStatus.FAILED, {message: 'foo'});
+    assertTrue(!!page.shadowRoot!.querySelector(
+        '#updateStatusMessage a:not([hidden])'));
+
+    // Check that link is hidden when update hasn't failed.
+    fireStatusChanged(UpdateStatus.UPDATED, {message: ''});
+    assertTrue(
+        !!page.shadowRoot!.querySelector('#updateStatusMessage a[hidden]'));
+  });
+
+  test('Relaunch', async () => {
+    await initPage();
+
+    const relaunchButton = page.$.relaunchButton;
+    assertTrue(!!relaunchButton);
+    assertFalse(isVisible(relaunchButton));
+
+    fireStatusChanged(UpdateStatus.NEARLY_UPDATED);
+    assertTrue(isVisible(relaunchButton));
+    relaunchButton.click();
+    await lifetimeBrowserProxy.whenCalled('relaunch');
+  });
+
+  test('Rollback', async () => {
+    const deviceManager = 'google.com';
+    loadTimeData.overrideValues({
+      deviceManager,
+      isManaged: true,
+    });
+    await initPage();
+    const statusMessageEl = page.$.updateStatusMessageInner;
+
+    const progress = 90;
+    fireStatusChanged(
+        UpdateStatus.UPDATING, {progress, powerwash: true, rollback: true});
+    let expectedMessage = page.i18nAdvanced('aboutRollbackInProgress', {
+                                substitutions: [deviceManager, `${progress}%`],
+                              })
+                              .toString();
+    assertEquals(expectedMessage, statusMessageEl.innerText);
+
+    fireStatusChanged(
+        UpdateStatus.NEARLY_UPDATED, {powerwash: true, rollback: true});
+    expectedMessage =
+        page.i18nAdvanced(
+                'aboutRollbackSuccess', {substitutions: [deviceManager]})
+            .toString();
+    assertEquals(expectedMessage, statusMessageEl.innerText);
+
+    // Simulate update disallowed to previously installed version after a
+    // consumer rollback.
+    fireStatusChanged(UpdateStatus.UPDATE_TO_ROLLBACK_VERSION_DISALLOWED);
+    expectedMessage = page.i18n('aboutUpdateToRollbackVersionDisallowed');
+    assertEquals(expectedMessage, statusMessageEl.innerText);
+  });
+
+  test(
+      'Warning dialog is shown when attempting to update over metered network',
+      async () => {
+        await initPage();
+
+        fireStatusChanged(
+            UpdateStatus.NEED_PERMISSION_TO_UPDATE,
+            {version: '9001.0.0', size: '9999'});
+        flush();
+
+        const warningDialog =
+            page.shadowRoot!.querySelector('settings-update-warning-dialog');
+        assertTrue(!!warningDialog);
+        assertTrue(
+            warningDialog.$.dialog.open, 'Warning dialog should be open');
+      });
+
+  test('Message is shown when there is no internet', async () => {
+    await initPage();
+
+    const statusMessageEl = page.$.updateStatusMessageInner;
+    assertFalse(isVisible(statusMessageEl));
+
+    aboutBrowserProxy.sendStatusNoInternet();
+    flush();
+    assertTrue(isVisible(statusMessageEl));
+    assertTrue(statusMessageEl.innerText.includes('no internet'));
+  });
+
+  suite('Update/Relaunch button', () => {
+    /**
+     * Regression test for crbug.com/1220294
+     */
+    test('Update button is shown initially', async () => {
+      aboutBrowserProxy.blockRefreshUpdateStatus();
+      await initPage();
+
+      assertTrue(isVisible(page.$.checkForUpdatesButton));
+    });
+
+    test(
+        'Clicking the update button moves focus to status message',
+        async () => {
+          await initPage();
+
+          const {checkForUpdatesButton, updateStatusMessageInner} = page.$;
+          checkForUpdatesButton.click();
+          await aboutBrowserProxy.whenCalled('requestUpdate');
+          assertEquals(
+              updateStatusMessageInner, page.shadowRoot!.activeElement,
+              'Update status message should be focused.');
+        });
+
+    /**
+     * Test that all buttons update according to incoming
+     * 'update-status-changed' events for the case where target and current
+     * channel are the same.
+     */
+    test('Button visibility based on update status', async () => {
+      await initPage();
+      const {checkForUpdatesButton, relaunchButton} = page.$;
+
+      function assertAllHidden() {
+        assertTrue(checkForUpdatesButton.hidden);
+        assertTrue(relaunchButton.hidden);
+        // Ensure that when all buttons are hidden, the container is also
+        // hidden.
+        assertTrue(page.$.buttonContainer.hidden);
+      }
+
+      // Check that |UPDATED| status is ignored if the user has not
+      // explicitly checked for updates yet.
+      fireStatusChanged(UpdateStatus.UPDATED);
+      assertFalse(checkForUpdatesButton.hidden);
+      assertTrue(relaunchButton.hidden);
+
+      // Check that the "Check for updates" button gets hidden for certain
+      // UpdateStatus values, even if the CHECKING state was never
+      // encountered (for example triggering update from crosh command
+      // line).
+      fireStatusChanged(UpdateStatus.UPDATING);
+      assertAllHidden();
+      fireStatusChanged(UpdateStatus.NEARLY_UPDATED);
+      assertTrue(checkForUpdatesButton.hidden);
+      assertFalse(relaunchButton.hidden);
+
+      fireStatusChanged(UpdateStatus.CHECKING);
+      assertAllHidden();
+
+      fireStatusChanged(UpdateStatus.UPDATING);
+      assertAllHidden();
+
+      fireStatusChanged(UpdateStatus.NEARLY_UPDATED);
+      assertTrue(checkForUpdatesButton.hidden);
+      assertFalse(relaunchButton.hidden);
+
+      fireStatusChanged(UpdateStatus.UPDATED);
+      assertAllHidden();
+
+      fireStatusChanged(UpdateStatus.FAILED);
+      assertFalse(checkForUpdatesButton.hidden);
+      assertTrue(relaunchButton.hidden);
+
+      fireStatusChanged(UpdateStatus.DISABLED);
+      assertAllHidden();
+
+      fireStatusChanged(UpdateStatus.DISABLED_BY_ADMIN);
+      assertFalse(checkForUpdatesButton.hidden);
+      assertTrue(relaunchButton.hidden);
+    });
+
+    /**
+     * Test that buttons update according to incoming
+     * 'update-status-changed' events for the case where the target channel
+     * is more stable than current channel and update will powerwash.
+     */
+    test('Relaunch button when updating from beta to stable', async () => {
+      aboutBrowserProxy.setChannels(BrowserChannel.BETA, BrowserChannel.STABLE);
+      await initPage();
+
+      fireStatusChanged(UpdateStatus.NEARLY_UPDATED, {powerwash: true});
+      const relaunchButton = page.$.relaunchButton;
+      assertTrue(isVisible(relaunchButton));
+
+      assertEquals(
+          page.i18n('aboutRelaunchAndPowerwash'), relaunchButton.innerText);
+
+      relaunchButton.click();
+      await lifetimeBrowserProxy.whenCalled('relaunch');
+    });
+
+    /**
+     * Test that buttons update according to incoming
+     * 'update-status-changed' events for the case where the target channel
+     * is less stable than current channel.
+     */
+    test('Relaunch button when updating from stable to beta', async () => {
+      aboutBrowserProxy.setChannels(BrowserChannel.STABLE, BrowserChannel.BETA);
+      await initPage();
+
+      fireStatusChanged(UpdateStatus.NEARLY_UPDATED, {powerwash: false});
+      const relaunchButton = page.$.relaunchButton;
+      assertTrue(isVisible(relaunchButton));
+
+      assertEquals(page.i18n('aboutRelaunch'), relaunchButton.innerText);
+
+      relaunchButton.click();
+      await lifetimeBrowserProxy.whenCalled('relaunch');
+    });
+
+    /**
+     * The relaunch and powerwash button is shown if the powerwash flag is set
+     * in the update status.
+     */
+    test('Relaunch button when powerwash flag is set', async () => {
+      await initPage();
+
+      fireStatusChanged(UpdateStatus.NEARLY_UPDATED, {powerwash: true});
+      const relaunchButton = page.$.relaunchButton;
+      assertTrue(isVisible(relaunchButton));
+
+      assertEquals(
+          page.i18n('aboutRelaunchAndPowerwash'), relaunchButton.innerText);
+
+      relaunchButton.click();
+      await lifetimeBrowserProxy.whenCalled('relaunch');
+    });
+  });
+
+  suite('Release notes', () => {
+    function queryReleaseNotesOnline(): HTMLElement|null {
+      return page.shadowRoot!.querySelector<HTMLElement>('#releaseNotesOnline');
+    }
+
+    function queryReleaseNotesOffline(): HTMLElement|null {
+      return page.shadowRoot!.querySelector<HTMLElement>(
+          '#releaseNotesOffline');
+    }
+
+    suite('when online', () => {
+      setup(async () => {
+        aboutBrowserProxy.setInternetConnection(true);
+        await initPage();
+      });
+
+      test('Online release notes are visible', async () => {
+        const releaseNotesOnlineButton = queryReleaseNotesOnline();
+        assertTrue(!!releaseNotesOnlineButton);
+        assertTrue(isVisible(releaseNotesOnlineButton));
+        assertFalse(isVisible(queryReleaseNotesOffline()));
+
+        releaseNotesOnlineButton.click();
+        await aboutBrowserProxy.whenCalled('launchReleaseNotes');
+      });
+
+      test('Deep link to release notes button', async () => {
+        const setting = settingMojom.Setting.kSeeWhatsNew;
+        deepLinkToSetting(setting);
+
+        const deepLinkElement = queryReleaseNotesOnline();
+        assertTrue(!!deepLinkElement);
+        await waitAfterNextRender(deepLinkElement);
+        assertEquals(
+            deepLinkElement, page.shadowRoot!.activeElement,
+            `Release notes should be focused for settingId=${setting}.`);
+      });
+    });
+
+    suite('when offline', () => {
+      setup(async () => {
+        aboutBrowserProxy.setInternetConnection(false);
+        await initPage();
+      });
+
+      test('Offline release notes are visible', () => {
+        assertFalse(isVisible(queryReleaseNotesOnline()));
+        assertTrue(isVisible(queryReleaseNotesOffline()));
+      });
+
+      test('Deep link to release notes button', async () => {
+        const setting = settingMojom.Setting.kSeeWhatsNew;
+        deepLinkToSetting(setting);
+
+        const deepLinkElement = queryReleaseNotesOffline();
+        assertTrue(!!deepLinkElement);
+        await waitAfterNextRender(deepLinkElement);
+        assertEquals(
+            deepLinkElement, page.shadowRoot!.activeElement,
+            `Release notes should be focused for settingId=${setting}.`);
+      });
+    });
+  });
+
+  suite('Regulatory info', () => {
+    const regulatoryInfo = {text: 'foo', url: 'bar'};
+
+    async function checkRegulatoryInfo(isShowing: boolean): Promise<void> {
+      await aboutBrowserProxy.whenCalled('getRegulatoryInfo');
+      const regulatoryInfoEl = page.$.regulatoryInfo;
+      assertEquals(isShowing, isVisible(regulatoryInfoEl));
+
+      if (isShowing) {
+        const img = regulatoryInfoEl.querySelector('img');
+        assertTrue(!!img);
+        assertEquals(regulatoryInfo.text, img.getAttribute('alt'));
+        assertEquals(regulatoryInfo.url, img.getAttribute('src'));
+      }
+    }
+
+    test('Regulatory info is not shown', async () => {
+      aboutBrowserProxy.setRegulatoryInfo(null);
+      await initPage();
+      await checkRegulatoryInfo(false);
+    });
+
+    test('Regulatory info is shown', async () => {
+      aboutBrowserProxy.setRegulatoryInfo(regulatoryInfo);
+      await initPage();
+      await checkRegulatoryInfo(true);
+    });
+  });
+
+  test('TPM firmware update', async () => {
+    await initPage();
+
+    const tpmFirmwareUpdateRow =
+        page.shadowRoot!.querySelector<HTMLElement>('#aboutTPMFirmwareUpdate');
+    assertFalse(isVisible(tpmFirmwareUpdateRow));
+
+    aboutBrowserProxy.setTpmFirmwareUpdateStatus({updateAvailable: true});
+    aboutBrowserProxy.refreshTpmFirmwareUpdateStatus();
+    assertTrue(!!tpmFirmwareUpdateRow);
+    assertTrue(isVisible(tpmFirmwareUpdateRow));
+
+    tpmFirmwareUpdateRow.click();
+    await flushTasks();
+    const dialog =
+        page.shadowRoot!.querySelector('os-settings-powerwash-dialog');
+    assertTrue(!!dialog);
+    assertTrue(dialog.$.dialog.open);
+
+    dialog.shadowRoot!.querySelector<HTMLElement>('#powerwash')!.click();
+    const requestTpmFirmwareUpdate =
+        await lifetimeBrowserProxy.whenCalled('factoryReset');
+    assertTrue(requestTpmFirmwareUpdate);
+  });
+
+  suite('End of life', () => {
+    /**
+     * Checks the visibility of the end of life message and icon.
+     */
+    async function assertHasEndOfLife(isShowing: boolean): Promise<void> {
+      await aboutBrowserProxy.whenCalled('getEndOfLifeInfo');
+
+      const statusMessageEl = page.$.updateStatusMessageInner;
+      const endOfLifeMessage =
+          page.shadowRoot!.querySelector<HTMLElement>('#endOfLifeMessage');
+      assertTrue(!!endOfLifeMessage);
+      assertEquals(isShowing, isVisible(endOfLifeMessage));
+
+      // Update status message should be hidden before user has
+      // checked for updates.
+      assertFalse(isVisible(statusMessageEl));
+
+      fireStatusChanged(UpdateStatus.CHECKING);
+      assertEquals(isShowing, !isVisible(statusMessageEl));
+
+      if (isShowing) {
+        const icon = page.shadowRoot!.querySelector('iron-icon');
+        assertTrue(!!icon);
+        assertNull(icon.src);
+        assertEquals('os-settings:end-of-life', icon.icon);
+
+        const {checkForUpdatesButton} = page.$;
+        assertTrue(!!checkForUpdatesButton);
+        assertTrue(checkForUpdatesButton.hidden);
+      }
+    }
+
+    test('End of life message is shown', async () => {
+      aboutBrowserProxy.setEndOfLifeInfo({
+        hasEndOfLife: true,
+        aboutPageEndOfLifeMessage: '',
+        shouldShowEndOfLifeIncentive: false,
+        shouldShowOfferText: false,
+      });
+      await initPage();
+      await assertHasEndOfLife(true);
+    });
+
+    test('End of life message is not shown', async () => {
+      aboutBrowserProxy.setEndOfLifeInfo({
+        hasEndOfLife: false,
+        aboutPageEndOfLifeMessage: '',
+        shouldShowEndOfLifeIncentive: false,
+        shouldShowOfferText: false,
+      });
+      await initPage();
+      await assertHasEndOfLife(false);
+    });
+
+    async function assertEndOfLifeIncentive(isShowing: boolean): Promise<void> {
+      await aboutBrowserProxy.whenCalled('getEndOfLifeInfo');
+      const eolOfferSection =
+          page.shadowRoot!.querySelector('eol-offer-section');
+      assertEquals(isShowing, isVisible(eolOfferSection));
+
+      if (isShowing) {
+        assertTrue(!!eolOfferSection);
+        const eolIncentiveButton =
+            eolOfferSection.shadowRoot!.querySelector<HTMLElement>(
+                '#eolIncentiveButton');
+        assertTrue(!!eolIncentiveButton);
+        eolIncentiveButton.click();
+        await aboutBrowserProxy.whenCalled('endOfLifeIncentiveButtonClicked');
+      }
+    }
+
+    test('End of life incentive is not shown', async () => {
+      aboutBrowserProxy.setEndOfLifeInfo({
+        hasEndOfLife: false,
+        aboutPageEndOfLifeMessage: '',
+        shouldShowEndOfLifeIncentive: false,
+        shouldShowOfferText: false,
+      });
+      await initPage();
+      await assertEndOfLifeIncentive(false);
+    });
+
+    test('End of life incentive is shown', async () => {
+      aboutBrowserProxy.setEndOfLifeInfo({
+        hasEndOfLife: false,
+        aboutPageEndOfLifeMessage: '',
+        shouldShowEndOfLifeIncentive: true,
+        shouldShowOfferText: false,
+      });
+      await initPage();
+      await assertEndOfLifeIncentive(true);
+    });
+  });
+
+  test(
+      'Detailed build info row is focused when returning from subpage',
+      async () => {
+        const triggerSelector = '#detailedBuildInfoTrigger';
+        const subpageTrigger =
+            page.shadowRoot!.querySelector<HTMLElement>(triggerSelector);
+        assertTrue(!!subpageTrigger);
+
+        // Sub-page trigger navigates to Detailed build info subpage
+        subpageTrigger.click();
+        assertEquals(
+            routes.ABOUT_DETAILED_BUILD_INFO,
+            Router.getInstance().currentRoute);
+
+        // Navigate back
+        const popStateEventPromise = eventToPromise('popstate', window);
+        Router.getInstance().navigateToPreviousRoute();
+        await popStateEventPromise;
+        await waitAfterNextRender(page);
+
+        assertEquals(
+            subpageTrigger, page.shadowRoot!.activeElement,
+            `${triggerSelector} should be focused.`);
+      });
+
+  suite('Get help', () => {
+    function getHelpRow(): HTMLElement {
+      const row = page.shadowRoot!.querySelector<HTMLElement>('#help');
+      assertTrue(!!row);
+      return row;
+    }
+
+    test('Clicking row opens explore app', async () => {
+      await initPage();
+
+      getHelpRow().click();
+      await aboutBrowserProxy.whenCalled('openOsHelpPage');
+    });
+
+    test('Deep link to get help row', async () => {
+      await initPage();
+
+      const setting = settingMojom.Setting.kGetHelpWithChromeOs;
+      deepLinkToSetting(setting);
+
+      const deepLinkElement = getHelpRow();
+      await waitAfterNextRender(deepLinkElement);
+      assertEquals(
+          deepLinkElement, page.shadowRoot!.activeElement,
+          `Get help row should be focused for settingId=${setting}.`);
+    });
+  });
+
+  suite('Diagnostics', () => {
+    function getDiagnosticsRow(): HTMLElement {
+      const row = page.shadowRoot!.querySelector<HTMLElement>('#diagnostics');
+      assertTrue(!!row);
+      return row;
+    }
+
+    test('Clicking row opens diagnostics app', async () => {
+      await initPage();
+
+      getDiagnosticsRow().click();
+      await aboutBrowserProxy.whenCalled('openDiagnostics');
+    });
+
+    test('Deep link to diagnostics', async () => {
+      await initPage();
+
+      const setting = settingMojom.Setting.kDiagnostics;
+      deepLinkToSetting(setting);
+
+      const deepLinkElement = getDiagnosticsRow();
+      await waitAfterNextRender(deepLinkElement);
+      assertEquals(
+          deepLinkElement, page.shadowRoot!.activeElement,
+          `Diagnostics row should be focused for settingId=${setting}.`);
+    });
+  });
+
+  suite('Firmware updates', () => {
+    function getFirmwareUpdateBadge(): IronIconElement {
+      const badge = page.shadowRoot!.querySelector<IronIconElement>(
+          '#firmwareUpdateBadge');
+      assertTrue(!!badge);
+      return badge;
+    }
+
+    function getFirmwareUpdateBadgeSeparator(): HTMLElement {
+      const separator = page.shadowRoot!.querySelector<HTMLElement>(
+          '#firmwareUpdateBadgeSeparator');
+      assertTrue(!!separator);
+      return separator;
+    }
+
+    test('Badge is not shown when there are no updates', async () => {
+      aboutBrowserProxy.setFirmwareUpdatesCount(0);
+      await initPage();
+      await aboutBrowserProxy.whenCalled('getFirmwareUpdateCount');
+
+      assertFalse(isVisible(getFirmwareUpdateBadge()));
+      assertFalse(isVisible(getFirmwareUpdateBadgeSeparator()));
+    });
+
+    test('Badge shows the number of updates', async () => {
+      for (let i = 1; i < 10; i++) {
+        aboutBrowserProxy.setFirmwareUpdatesCount(i);
+        await initPage();
+        await aboutBrowserProxy.whenCalled('getFirmwareUpdateCount');
+
+        const badge = getFirmwareUpdateBadge();
+        assertTrue(isVisible(badge));
+        assertTrue(isVisible(getFirmwareUpdateBadgeSeparator()));
+        assertEquals(`os-settings:counter-${i}`, badge.icon);
+      }
+    });
+
+    test('Badge uses the counter-9 icon for 10 updates', async () => {
+      aboutBrowserProxy.setFirmwareUpdatesCount(10);
+      await initPage();
+      await aboutBrowserProxy.whenCalled('getFirmwareUpdateCount');
+
+      const badge = getFirmwareUpdateBadge();
+      assertTrue(isVisible(badge));
+      assertTrue(isVisible(getFirmwareUpdateBadgeSeparator()));
+      assertEquals('os-settings:counter-9', badge.icon);
+    });
+
+    test('Clicking link opens firmware updates page', async () => {
+      await initPage();
+
+      const firmwareUpdatesRow =
+          page.shadowRoot!.querySelector<HTMLElement>('#firmwareUpdates');
+      assertTrue(!!firmwareUpdatesRow);
+      firmwareUpdatesRow.click();
+      await aboutBrowserProxy.whenCalled('openFirmwareUpdatesPage');
+    });
+
+    test('Deep link to firmware updates', async () => {
+      await initPage();
+
+      const setting = settingMojom.Setting.kFirmwareUpdates;
+      deepLinkToSetting(setting);
+
+      const deepLinkElement =
+          page.shadowRoot!.querySelector<HTMLElement>('#firmwareUpdates');
+      assertTrue(!!deepLinkElement);
+      await waitAfterNextRender(deepLinkElement);
+      assertEquals(
+          deepLinkElement, page.shadowRoot!.activeElement,
+          `Firmware updates should be focused for settingId=${setting}.`);
+    });
+  });
+});
+
+suite('<os-about-page> OfficialBuild', () => {
+  let page: OsAboutPageElement;
+  let browserProxy: TestAboutPageBrowserProxy;
+
+  function deepLinkToSetting(setting: settingMojom.Setting): void {
+    const params = new URLSearchParams();
+    params.append('settingId', setting.toString());
+    Router.getInstance().navigateTo(routes.ABOUT, params);
+    flush();
+  }
+
+  async function assertElementIsDeepLinked(element: HTMLElement):
+      Promise<void> {
+    await waitAfterNextRender(element);
+    assertEquals(element, page.shadowRoot!.activeElement);
+  }
+
+  setup(async () => {
+    browserProxy = new TestAboutPageBrowserProxy();
+    AboutPageBrowserProxyImpl.setInstanceForTesting(browserProxy);
+
+    clearBody();
+    page = document.createElement('os-about-page');
+    document.body.appendChild(page);
+    await flushTasks();
+  });
+
+  teardown(() => {
+    Router.getInstance().resetRouteForTesting();
+  });
+
+  test('Report an issue click opens feedback dialog', async () => {
+    const reportIssueRow =
+        page.shadowRoot!.querySelector<HTMLElement>('#reportIssue');
+    assertTrue(!!reportIssueRow);
+    reportIssueRow.click();
+    await browserProxy.whenCalled('openFeedbackDialog');
+  });
+
+  test('Deep link to report an issue', async () => {
+    const setting = settingMojom.Setting.kReportAnIssue;
+    deepLinkToSetting(setting);
+
+    const reportIssueRow =
+        page.shadowRoot!.querySelector<HTMLElement>('#reportIssue');
+    assertTrue(!!reportIssueRow);
+    await assertElementIsDeepLinked(reportIssueRow);
+  });
+
+  test('Deep link to terms of service', async () => {
+    const setting = settingMojom.Setting.kTermsOfService;
+    deepLinkToSetting(setting);
+
+    const productTosRow =
+        page.shadowRoot!.querySelector<HTMLElement>('#aboutProductTos');
+    assertTrue(!!productTosRow);
+    await assertElementIsDeepLinked(productTosRow);
+  });
+});
diff --git a/chrome/test/data/webui/settings/chromeos/test_about_page_browser_proxy.ts b/chrome/test/data/webui/settings/chromeos/os_about_page/test_about_page_browser_proxy.ts
similarity index 100%
rename from chrome/test/data/webui/settings/chromeos/test_about_page_browser_proxy.ts
rename to chrome/test/data/webui/settings/chromeos/os_about_page/test_about_page_browser_proxy.ts
diff --git a/chrome/test/data/webui/settings/chromeos/os_about_page_tests.js b/chrome/test/data/webui/settings/chromeos/os_about_page_tests.js
deleted file mode 100644
index 190e7036..0000000
--- a/chrome/test/data/webui/settings/chromeos/os_about_page_tests.js
+++ /dev/null
@@ -1,1036 +0,0 @@
-// Copyright 2019 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'chrome://os-settings/os_settings.js';
-import 'chrome://os-settings/lazy_load.js';
-
-import {AboutPageBrowserProxyImpl, BrowserChannel, LifetimeBrowserProxyImpl, Router, routes, setUserActionRecorderForTesting, UpdateStatus, userActionRecorderMojom} from 'chrome://os-settings/os_settings.js';
-import {webUIListenerCallback} from 'chrome://resources/ash/common/cr.m.js';
-import {getDeepActiveElement} from 'chrome://resources/ash/common/util.js';
-import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
-import {flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
-import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';
-
-import {FakeUserActionRecorder} from './fake_user_action_recorder.js';
-import {TestAboutPageBrowserProxy} from './test_about_page_browser_proxy.js';
-import {TestLifetimeBrowserProxy} from './test_os_lifetime_browser_proxy.js';
-import {clearBody} from './utils.js';
-
-suite('<os-about-page> AllBuilds AboutPageTest', function() {
-  let page = null;
-
-  /** @type {?TestAboutPageBrowserProxy} */
-  let aboutBrowserProxy = null;
-
-  /** @type {?TestLifetimeBrowserProxy} */
-  let lifetimeBrowserProxy = null;
-
-  /** @type {?userActionRecorderMojom.UserActionRecorderInterface} */
-  let userActionRecorder = null;
-
-  const SPINNER_ICON_LIGHT_MODE =
-      'chrome://resources/images/throbber_small.svg';
-  const SPINNER_ICON_DARK_MODE =
-      'chrome://resources/images/throbber_small_dark.svg';
-
-  setup(function() {
-    loadTimeData.overrideValues({isRevampWayfindingEnabled: false});
-    userActionRecorder = new FakeUserActionRecorder();
-    setUserActionRecorderForTesting(userActionRecorder);
-
-    lifetimeBrowserProxy = new TestLifetimeBrowserProxy();
-    LifetimeBrowserProxyImpl.setInstance(lifetimeBrowserProxy);
-
-    aboutBrowserProxy = new TestAboutPageBrowserProxy();
-    AboutPageBrowserProxyImpl.setInstanceForTesting(aboutBrowserProxy);
-    return initNewPage();
-  });
-
-  teardown(function() {
-    page.remove();
-    page = null;
-    Router.getInstance().resetRouteForTesting();
-    setUserActionRecorderForTesting(null);
-  });
-
-  /**
-   * @param {!UpdateStatus} status
-   * @param {{
-   *   progress: number|undefined,
-   *   message: string|undefined,
-   *   rollback: bool|undefined,
-   *   powerwash: bool|undefined,
-   *   version: string|undefined,
-   *   size: string|undefined,
-   * }} opt_options
-   */
-  function fireStatusChanged(status, opt_options) {
-    const options = opt_options || {};
-    webUIListenerCallback('update-status-changed', {
-      progress: options.progress === undefined ? 1 : options.progress,
-      message: options.message,
-      status,
-      rollback: options.rollback,
-      powerwash: options.powerwash,
-      version: options.version,
-      size: options.size,
-    });
-  }
-
-  /** @return {!Promise} */
-  function initNewPage() {
-    aboutBrowserProxy.reset();
-    lifetimeBrowserProxy.reset();
-    clearBody();
-    page = document.createElement('os-about-page');
-    Router.getInstance().navigateTo(routes.ABOUT);
-    document.body.appendChild(page);
-    return Promise.all([
-      aboutBrowserProxy.whenCalled('getChannelInfo'),
-      aboutBrowserProxy.whenCalled('refreshUpdateStatus'),
-      aboutBrowserProxy.whenCalled('refreshTpmFirmwareUpdateStatus'),
-      aboutBrowserProxy.whenCalled('checkInternetConnection'),
-    ]);
-  }
-
-  /**
-   * @param {string} id
-   */
-  function navigateToSettingsPageWithId(id) {
-    const params = new URLSearchParams();
-    params.append('settingId', id);
-    Router.getInstance().navigateTo(routes.ABOUT, params);
-
-    flush();
-  }
-
-  /**
-   * @param {string} id
-   * @return {!HTMLButtonElement}
-   */
-  function getDeepLinkButtonElementById(id) {
-    return page.shadowRoot.querySelector(`#${id}`).shadowRoot.querySelector(
-        'cr-icon-button');
-  }
-
-  /**
-   * @param {boolean} active
-   */
-  function setDarkMode(active) {
-    assertTrue(!!page);
-    page.isDarkModeActive_ = active;
-  }
-
-  suite('When OsSettingsRevampWayfinding feature is enabled', () => {
-    setup(() => {
-      loadTimeData.overrideValues({isRevampWayfindingEnabled: true});
-    });
-
-    test('Crostini settings card is visible', async () => {
-      await initNewPage();
-      const crostiniSettingsCard =
-          page.shadowRoot.querySelector('crostini-settings-card');
-      assertTrue(isVisible(crostiniSettingsCard));
-    });
-  });
-
-  ['light', 'dark'].forEach((mode) => {
-    suite(`with ${mode} mode active`, () => {
-      const isDarkMode = mode === 'dark';
-
-      /**
-       * Test that the OS update status message and icon update according to
-       * incoming 'update-status-changed' events, for light and dark mode
-       * respectively.
-       */
-      test('status message and icon update', () => {
-        setDarkMode(isDarkMode);
-        const icon = page.shadowRoot.querySelector('iron-icon');
-        assertTrue(!!icon);
-        const statusMessageEl =
-            page.shadowRoot.querySelector('#updateStatusMessage div');
-        let previousMessageText = statusMessageEl.textContent;
-
-        fireStatusChanged(UpdateStatus.CHECKING);
-        if (isDarkMode) {
-          assertEquals(SPINNER_ICON_DARK_MODE, icon.src);
-        } else {
-          assertEquals(SPINNER_ICON_LIGHT_MODE, icon.src);
-        }
-        assertEquals(null, icon.getAttribute('icon'));
-        assertNotEquals(previousMessageText, statusMessageEl.textContent);
-        previousMessageText = statusMessageEl.textContent;
-
-        fireStatusChanged(UpdateStatus.UPDATING, {progress: 0});
-        if (isDarkMode) {
-          assertEquals(SPINNER_ICON_DARK_MODE, icon.src);
-        } else {
-          assertEquals(SPINNER_ICON_LIGHT_MODE, icon.src);
-        }
-        assertEquals(null, icon.getAttribute('icon'));
-        assertFalse(statusMessageEl.textContent.includes('%'));
-        assertNotEquals(previousMessageText, statusMessageEl.textContent);
-        previousMessageText = statusMessageEl.textContent;
-
-        fireStatusChanged(UpdateStatus.UPDATING, {progress: 1});
-        assertNotEquals(previousMessageText, statusMessageEl.textContent);
-        assertTrue(statusMessageEl.textContent.includes('%'));
-        previousMessageText = statusMessageEl.textContent;
-
-        fireStatusChanged(UpdateStatus.NEARLY_UPDATED);
-        assertEquals(null, icon.src);
-        assertEquals('settings:check-circle', icon.icon);
-        assertNotEquals(previousMessageText, statusMessageEl.textContent);
-        previousMessageText = statusMessageEl.textContent;
-
-        fireStatusChanged(UpdateStatus.DISABLED_BY_ADMIN);
-        assertEquals(null, icon.src);
-        assertEquals('cr20:domain', icon.icon);
-        assertNotEquals(previousMessageText, statusMessageEl.textContent);
-
-        fireStatusChanged(UpdateStatus.FAILED);
-        assertEquals(null, icon.src);
-        assertEquals('cr:error-outline', icon.icon);
-        assertEquals(0, statusMessageEl.textContent.trim().length);
-
-        fireStatusChanged(UpdateStatus.DISABLED);
-        assertEquals(null, icon.src);
-        assertEquals(null, icon.getAttribute('icon'));
-        assertEquals(0, statusMessageEl.textContent.trim().length);
-      });
-    });
-  });
-
-  test('ErrorMessageWithHtml', function() {
-    const htmlError = 'hello<br>there<br>was<pre>an</pre>error';
-    fireStatusChanged(UpdateStatus.FAILED, {message: htmlError});
-    const statusMessageEl =
-        page.shadowRoot.querySelector('#updateStatusMessage div');
-    assertEquals(htmlError, statusMessageEl.innerHTML);
-  });
-
-  test('FailedLearnMoreLink', function() {
-    // Check that link is shown when update failed.
-    fireStatusChanged(UpdateStatus.FAILED, {message: 'foo'});
-    assertTrue(!!page.shadowRoot.querySelector(
-        '#updateStatusMessage a:not([hidden])'));
-
-    // Check that link is hidden when update hasn't failed.
-    fireStatusChanged(UpdateStatus.UPDATED, {message: ''});
-    assertTrue(
-        !!page.shadowRoot.querySelector('#updateStatusMessage a[hidden]'));
-  });
-
-  test('Relaunch', function() {
-    const {relaunch} = page.$;
-    assertTrue(!!relaunch);
-    assertTrue(relaunch.hidden);
-
-    fireStatusChanged(UpdateStatus.NEARLY_UPDATED);
-    assertFalse(relaunch.hidden);
-    relaunch.click();
-    return lifetimeBrowserProxy.whenCalled('relaunch');
-  });
-
-  test('Rollback', async () => {
-    loadTimeData.overrideValues({
-      deviceManager: 'google.com',
-      isManaged: true,
-    });
-    await initNewPage();
-    const statusMessageEl =
-        page.shadowRoot.querySelector('#updateStatusMessage div');
-
-    const progress = 90;
-    fireStatusChanged(
-        UpdateStatus.UPDATING,
-        {progress: progress, powerwash: true, rollback: true});
-
-    assertEquals(
-        page.i18nAdvanced(
-                'aboutRollbackInProgress',
-                {substitutions: [page.deviceManager_, progress + '%']})
-            .toString(),
-        statusMessageEl.textContent);
-
-    fireStatusChanged(
-        UpdateStatus.NEARLY_UPDATED, {powerwash: true, rollback: true});
-
-    assertEquals(
-        page.i18nAdvanced(
-                'aboutRollbackSuccess', {substitutions: [page.deviceManager_]})
-            .toString(),
-        statusMessageEl.textContent);
-
-    // Simulate update disallowed to previously installed version after a
-    // consumer rollback.
-    fireStatusChanged(UpdateStatus.UPDATE_TO_ROLLBACK_VERSION_DISALLOWED);
-    const expectedMessage =
-        page.i18n('aboutUpdateToRollbackVersionDisallowed').toString();
-    assertEquals(expectedMessage, statusMessageEl.textContent);
-  });
-
-  test(
-      'Warning dialog is shown when attempting to update over metered network',
-      async () => {
-        await initNewPage();
-
-        fireStatusChanged(
-            UpdateStatus.NEED_PERMISSION_TO_UPDATE,
-            {version: '9001.0.0', size: '9999'});
-        flush();
-
-        const warningDialog =
-            page.shadowRoot.querySelector('settings-update-warning-dialog');
-        assertTrue(!!warningDialog);
-        assertTrue(
-            warningDialog.$.dialog.open, 'Warning dialog should be open');
-      });
-
-  test('NoInternet', function() {
-    assertTrue(page.$.updateStatusMessage.hidden);
-    aboutBrowserProxy.sendStatusNoInternet();
-    flush();
-    assertFalse(page.$.updateStatusMessage.hidden);
-    assertTrue(page.$.updateStatusMessage.textContent.includes('no internet'));
-  });
-
-  /**
-   * Test that all buttons update according to incoming
-   * 'update-status-changed' events for the case where target and current
-   * channel are the same.
-   */
-  test('ButtonsUpdate_SameChannel', function() {
-    const {checkForUpdates, relaunch} = page.$;
-
-    assertTrue(!!relaunch);
-    assertTrue(!!checkForUpdates);
-
-    function assertAllHidden() {
-      assertTrue(checkForUpdates.hidden);
-      assertTrue(relaunch.hidden);
-      // Ensure that when all buttons are hidden, the container is also
-      // hidden.
-      assertTrue(page.$.buttonContainer.hidden);
-    }
-
-    // Check that |UPDATED| status is ignored if the user has not
-    // explicitly checked for updates yet.
-    fireStatusChanged(UpdateStatus.UPDATED);
-    assertFalse(checkForUpdates.hidden);
-    assertTrue(relaunch.hidden);
-
-    // Check that the "Check for updates" button gets hidden for certain
-    // UpdateStatus values, even if the CHECKING state was never
-    // encountered (for example triggering update from crosh command
-    // line).
-    fireStatusChanged(UpdateStatus.UPDATING);
-    assertAllHidden();
-    fireStatusChanged(UpdateStatus.NEARLY_UPDATED);
-    assertTrue(checkForUpdates.hidden);
-    assertFalse(relaunch.hidden);
-
-    fireStatusChanged(UpdateStatus.CHECKING);
-    assertAllHidden();
-
-    fireStatusChanged(UpdateStatus.UPDATING);
-    assertAllHidden();
-
-    fireStatusChanged(UpdateStatus.NEARLY_UPDATED);
-    assertTrue(checkForUpdates.hidden);
-    assertFalse(relaunch.hidden);
-
-    fireStatusChanged(UpdateStatus.UPDATED);
-    assertAllHidden();
-
-    fireStatusChanged(UpdateStatus.FAILED);
-    assertFalse(checkForUpdates.hidden);
-    assertTrue(relaunch.hidden);
-
-    fireStatusChanged(UpdateStatus.DISABLED);
-    assertAllHidden();
-
-    fireStatusChanged(UpdateStatus.DISABLED_BY_ADMIN);
-    assertFalse(checkForUpdates.hidden);
-    assertTrue(relaunch.hidden);
-  });
-
-  /**
-   * Test that buttons update according to incoming
-   * 'update-status-changed' events for the case where the target channel
-   * is more stable than current channel and update will powerwash.
-   */
-  test('ButtonsUpdate_BetaToStable', async () => {
-    aboutBrowserProxy.setChannels(BrowserChannel.BETA, BrowserChannel.STABLE);
-    await initNewPage();
-
-    fireStatusChanged(UpdateStatus.NEARLY_UPDATED, {powerwash: true});
-
-    assertTrue(!!page.$.relaunch);
-    assertFalse(page.$.relaunch.hidden);
-
-    assertEquals(
-        page.$.relaunch.innerText,
-        loadTimeData.getString('aboutRelaunchAndPowerwash'));
-
-    page.$.relaunch.click();
-    await lifetimeBrowserProxy.whenCalled('relaunch');
-  });
-
-  /**
-   * Test that buttons update according to incoming
-   * 'update-status-changed' events for the case where the target channel
-   * is less stable than current channel.
-   */
-  test('ButtonsUpdate_StableToBeta', async () => {
-    aboutBrowserProxy.setChannels(BrowserChannel.STABLE, BrowserChannel.BETA);
-    await initNewPage();
-
-    fireStatusChanged(UpdateStatus.NEARLY_UPDATED, {powerwash: false});
-
-    assertTrue(!!page.$.relaunch);
-    assertFalse(page.$.relaunch.hidden);
-
-    assertEquals(
-        page.$.relaunch.innerText, loadTimeData.getString('aboutRelaunch'));
-
-    page.$.relaunch.click();
-    await lifetimeBrowserProxy.whenCalled('relaunch');
-  });
-
-  /**
-   * The relaunch and powerwash button is shown if the powerwash flag is set
-   * in the update status.
-   */
-  test('ButtonsUpdate_Powerwash', async () => {
-    await initNewPage();
-
-    fireStatusChanged(UpdateStatus.NEARLY_UPDATED, {powerwash: true});
-
-    assertTrue(!!page.$.relaunch);
-    assertFalse(page.$.relaunch.hidden);
-
-    assertEquals(
-        page.$.relaunch.innerText,
-        loadTimeData.getString('aboutRelaunchAndPowerwash'));
-
-    page.$.relaunch.click();
-    await lifetimeBrowserProxy.whenCalled('relaunch');
-  });
-
-  /**
-   * Test that release notes button can toggled by feature flags.
-   * Test that release notes button handles offline/online mode properly.
-   * page.shadowRoot.querySelector("#") is used to access items inside dom-if.
-   */
-  test('ReleaseNotes', async () => {
-    const releaseNotes = null;
-
-    /**
-     * Checks the visibility of the "release notes" section when online.
-     * @param {boolean} isShowing Whether the section is expected to be
-     *     visible.
-     */
-    function checkReleaseNotesOnline(isShowing) {
-      const releaseNotesOnlineEl =
-          page.shadowRoot.querySelector('#releaseNotesOnline');
-      assertEquals(isShowing, !!releaseNotesOnlineEl);
-    }
-
-    /**
-     * Checks the visibility of the "release notes" for offline mode.
-     * @param {boolean} isShowing Whether the section is expected to be
-     *     visible.
-     */
-    function checkReleaseNotesOffline(isShowing) {
-      const releaseNotesOfflineEl =
-          page.shadowRoot.querySelector('#releaseNotesOffline');
-      // According to
-      // https://polymer-library.polymer-project.org/1.0/api/elements/dom-if
-      // the element will not be removed from the dom if already rendered.
-      // Can be just hidden instead for better performance.
-      assertEquals(
-          isShowing,
-          !!releaseNotesOfflineEl &&
-              window.getComputedStyle(releaseNotesOfflineEl).display !==
-                  'none');
-    }
-
-    aboutBrowserProxy.setInternetConnection(false);
-    await initNewPage();
-    checkReleaseNotesOnline(false);
-    checkReleaseNotesOffline(true);
-
-    aboutBrowserProxy.setInternetConnection(true);
-    await initNewPage();
-    checkReleaseNotesOnline(true);
-    checkReleaseNotesOffline(false);
-
-    page.shadowRoot.querySelector('#releaseNotesOnline').click();
-    return aboutBrowserProxy.whenCalled('launchReleaseNotes');
-  });
-
-  test('Deep link to release notes', async () => {
-    loadTimeData.overrideValues({
-      isDeepLinkingEnabled: true,
-    });
-    aboutBrowserProxy.setInternetConnection(false);
-    await initNewPage();
-
-    const params = new URLSearchParams();
-    params.append('settingId', '1703');
-    Router.getInstance().navigateTo(routes.ABOUT, params);
-
-    flush();
-
-    const deepLinkElement =
-        page.shadowRoot.querySelector('#releaseNotesOffline')
-            .shadowRoot.querySelector('cr-icon-button');
-    await waitAfterNextRender(deepLinkElement);
-    assertEquals(
-        deepLinkElement, getDeepActiveElement(),
-        'Release notes should be focused for settingId=1703.');
-  });
-
-  test('RegulatoryInfo', async () => {
-    const regulatoryInfo = {text: 'foo', url: 'bar'};
-
-    /**
-     * Checks the visibility of the "regulatory info" section.
-     * @param {boolean} isShowing Whether the section is expected to be
-     *     visible.
-     * @return {!Promise}
-     */
-    async function checkRegulatoryInfo(isShowing) {
-      await aboutBrowserProxy.whenCalled('getRegulatoryInfo');
-      const regulatoryInfoEl = page.$.regulatoryInfo;
-      assertTrue(!!regulatoryInfoEl);
-      assertEquals(isShowing, !regulatoryInfoEl.hidden);
-
-      if (isShowing) {
-        const img = regulatoryInfoEl.querySelector('img');
-        assertTrue(!!img);
-        assertEquals(regulatoryInfo.text, img.getAttribute('alt'));
-        assertEquals(regulatoryInfo.url, img.getAttribute('src'));
-      }
-    }
-
-    await checkRegulatoryInfo(false);
-    aboutBrowserProxy.setRegulatoryInfo(regulatoryInfo);
-    await initNewPage();
-    await checkRegulatoryInfo(true);
-  });
-
-  test('TPMFirmwareUpdate', async () => {
-    assertTrue(page.$.aboutTPMFirmwareUpdate.hidden);
-    aboutBrowserProxy.setTpmFirmwareUpdateStatus({updateAvailable: true});
-    aboutBrowserProxy.refreshTpmFirmwareUpdateStatus();
-    assertFalse(page.$.aboutTPMFirmwareUpdate.hidden);
-    page.$.aboutTPMFirmwareUpdate.click();
-    await flushTasks();
-    const dialog =
-        page.shadowRoot.querySelector('os-settings-powerwash-dialog');
-    assertTrue(!!dialog);
-    assertTrue(dialog.$.dialog.open);
-    dialog.shadowRoot.querySelector('#powerwash').click();
-    const requestTpmFirmwareUpdate =
-        await lifetimeBrowserProxy.whenCalled('factoryReset');
-    assertTrue(requestTpmFirmwareUpdate);
-  });
-
-  test('DeviceEndOfLife', async () => {
-    /**
-     * Checks the visibility of the end of life message and icon.
-     * @param {boolean} isShowing Whether the end of life UI is expected
-     *     to be visible.
-     * @return {!Promise}
-     */
-    async function checkHasEndOfLife(isShowing) {
-      await aboutBrowserProxy.whenCalled('getEndOfLifeInfo');
-      const {endOfLifeMessageContainer} = page.$;
-      assertTrue(!!endOfLifeMessageContainer);
-
-      assertEquals(isShowing, !endOfLifeMessageContainer.hidden);
-
-      // Update status message should be hidden before user has
-      // checked for updates.
-      assertTrue(page.$.updateStatusMessage.hidden);
-
-      fireStatusChanged(UpdateStatus.CHECKING);
-      assertEquals(isShowing, page.$.updateStatusMessage.hidden);
-
-      if (isShowing) {
-        const icon = page.shadowRoot.querySelector('iron-icon');
-        assertTrue(!!icon);
-        assertEquals(null, icon.src);
-        assertEquals('os-settings:end-of-life', icon.icon);
-
-        const {checkForUpdates} = page.$;
-        assertTrue(!!checkForUpdates);
-        assertTrue(checkForUpdates.hidden);
-      }
-    }
-
-    // Force test proxy to not respond to JS requests.
-    // End of life message should still be hidden in this case.
-    aboutBrowserProxy.setEndOfLifeInfo(new Promise(function(res, rej) {}));
-    await initNewPage();
-    await checkHasEndOfLife(false);
-    aboutBrowserProxy.setEndOfLifeInfo({
-      hasEndOfLife: true,
-      endOfLifeAboutMessage: '',
-    });
-    await initNewPage();
-    await checkHasEndOfLife(true);
-    aboutBrowserProxy.setEndOfLifeInfo({
-      hasEndOfLife: false,
-      endOfLifeAboutMessage: '',
-    });
-    await initNewPage();
-    await checkHasEndOfLife(false);
-  });
-
-  test('DeviceEndOfLifeIncentive', async () => {
-    async function checkEndOfLifeIncentive(isShowing) {
-      await aboutBrowserProxy.whenCalled('getEndOfLifeInfo');
-      const eolSection = page.shadowRoot.querySelector('eol-offer-section');
-      assertEquals(isShowing, !!eolSection);
-
-      if (isShowing) {
-        eolSection.$.eolIncentiveButton.click();
-        await aboutBrowserProxy.whenCalled('endOfLifeIncentiveButtonClicked');
-      }
-    }
-
-    aboutBrowserProxy.setEndOfLifeInfo({
-      hasEndOfLife: false,
-      endOfLifeAboutMessage: '',
-      shouldShowEndOfLifeIncentive: false,
-      shouldShowOfferText: false,
-    });
-    await initNewPage();
-    await checkEndOfLifeIncentive(false);
-
-    aboutBrowserProxy.setEndOfLifeInfo({
-      hasEndOfLife: false,
-      endOfLifeAboutMessage: '',
-      shouldShowEndOfLifeIncentive: true,
-      shouldShowOfferText: false,
-    });
-    await initNewPage();
-    await checkEndOfLifeIncentive(true);
-  });
-
-  test('managed detailed build info page', async () => {
-    loadTimeData.overrideValues({
-      isManaged: true,
-    });
-
-    // Despite there being a valid end of life, the information is not
-    // shown if the user is managed.
-    aboutBrowserProxy.setEndOfLifeInfo({
-      hasEndOfLife: true,
-      aboutPageEndOfLifeMessage: 'message',
-    });
-    await initNewPage();
-
-    const subpageTrigger =
-        page.shadowRoot.querySelector('#detailedBuildInfoTrigger');
-    assertTrue(!!subpageTrigger);
-    subpageTrigger.click();
-    const buildInfoPage =
-        page.shadowRoot.querySelector('settings-detailed-build-info-subpage');
-    assertTrue(!!buildInfoPage);
-    assertTrue(!!buildInfoPage.$['endOfLifeSectionContainer']);
-    assertTrue(buildInfoPage.$['endOfLifeSectionContainer'].hidden);
-  });
-
-  test('detailed build info page', async () => {
-    loadTimeData.overrideValues({
-      isManaged: false,
-    });
-
-    async function checkEndOfLifeSection() {
-      await aboutBrowserProxy.whenCalled('getEndOfLifeInfo');
-      const buildInfoPage =
-          page.shadowRoot.querySelector('settings-detailed-build-info-subpage');
-      assertTrue(!!buildInfoPage.$['endOfLifeSectionContainer']);
-      assertFalse(buildInfoPage.$['endOfLifeSectionContainer'].hidden);
-    }
-
-    aboutBrowserProxy.setEndOfLifeInfo({
-      hasEndOfLife: true,
-      aboutPageEndOfLifeMessage: '',
-    });
-    await initNewPage();
-    let subpageTrigger =
-        page.shadowRoot.querySelector('#detailedBuildInfoTrigger');
-    assertTrue(!!subpageTrigger);
-    subpageTrigger.click();
-    const buildInfoPage =
-        page.shadowRoot.querySelector('settings-detailed-build-info-subpage');
-    assertTrue(!!buildInfoPage);
-    assertTrue(!!buildInfoPage.$['endOfLifeSectionContainer']);
-    assertTrue(buildInfoPage.$['endOfLifeSectionContainer'].hidden);
-
-    aboutBrowserProxy.setEndOfLifeInfo({
-      hasEndOfLife: true,
-      aboutPageEndOfLifeMessage: 'message',
-    });
-    await initNewPage();
-    subpageTrigger = page.shadowRoot.querySelector('#detailedBuildInfoTrigger');
-    assertTrue(!!subpageTrigger);
-    subpageTrigger.click();
-    checkEndOfLifeSection();
-  });
-
-  test(
-      'Detailed build info subpage trigger is focused when returning ' +
-          'from subpage',
-      async () => {
-        const triggerSelector = '#detailedBuildInfoTrigger';
-        const subpageTrigger = page.shadowRoot.querySelector(triggerSelector);
-        assertTrue(!!subpageTrigger);
-
-        // Sub-page trigger navigates to Detailed build info subpage
-        subpageTrigger.click();
-        assertEquals(
-            routes.ABOUT_DETAILED_BUILD_INFO,
-            Router.getInstance().currentRoute);
-
-        // Navigate back
-        const popStateEventPromise = eventToPromise('popstate', window);
-        Router.getInstance().navigateToPreviousRoute();
-        await popStateEventPromise;
-        await waitAfterNextRender(page);
-
-        assertEquals(
-            subpageTrigger, page.shadowRoot.activeElement,
-            `${triggerSelector} should be focused.`);
-      });
-
-  function getBuildInfoPage() {
-    const subpageTrigger =
-        page.shadowRoot.querySelector('#detailedBuildInfoTrigger');
-    assertTrue(!!subpageTrigger);
-    subpageTrigger.click();
-    const buildInfoPage =
-        page.shadowRoot.querySelector('settings-detailed-build-info-subpage');
-    assertTrue(!!buildInfoPage);
-    return buildInfoPage;
-  }
-
-  test('Managed user auto update toggle in build info page', async () => {
-    loadTimeData.overrideValues({
-      isManaged: true,
-    });
-
-    async function checkManagedAutoUpdateToggle(isToggleEnabled, showToggle) {
-      // Create the page.
-      await initNewPage();
-      // Set overrides + response values.
-      aboutBrowserProxy.setManagedAutoUpdate(isToggleEnabled);
-
-      loadTimeData.overrideValues({showAutoUpdateToggle: showToggle});
-      // Go to the build info page.
-      const buildInfoPage = getBuildInfoPage();
-      // Wait for overrides + response values.
-      await aboutBrowserProxy.whenCalled('isManagedAutoUpdateEnabled');
-
-      const mau_toggle =
-          buildInfoPage.shadowRoot.querySelector('#managedAutoUpdateToggle');
-
-      if (showToggle) {
-        assertTrue(!!mau_toggle);
-        // Managed auto update toggle should always be disabled to toggle.
-        assertTrue(!!mau_toggle.hasAttribute('disabled'));
-        assertEquals(isToggleEnabled, mau_toggle.checked);
-        // Consumer auto update toggle should not exist.
-        assertFalse(!!buildInfoPage.shadowRoot.querySelector(
-            '#consumerAutoUpdateToggle'));
-      } else {
-        assertFalse(!!mau_toggle);
-      }
-    }
-
-    for (let i = 0; i < (1 << 2); i++) {
-      await checkManagedAutoUpdateToggle(
-          /*isToggleEnabled=*/ (i & 1) > 0,
-          /*showToggle=*/ (i & 2) > 0);
-    }
-  });
-
-  test('Consumer user auto update toggle in build info page', async () => {
-    loadTimeData.overrideValues({
-      isManaged: false,
-    });
-
-    async function checkConsumerAutoUpdateToggle(
-        isEnabled, isTogglingAllowed, showToggle) {
-      // Create the page.
-      await initNewPage();
-      // Set overrides + response values.
-      loadTimeData.overrideValues({
-        isConsumerAutoUpdateTogglingAllowed: isTogglingAllowed,
-        showAutoUpdateToggle: showToggle,
-      });
-      aboutBrowserProxy.resetConsumerAutoUpdate(isEnabled);
-      const prefs = {
-        'settings': {
-          'consumer_auto_update_toggle': {
-            key: 'consumer_auto_update_toggle',
-            type: chrome.settingsPrivate.PrefType.BOOLEAN,
-            value: isEnabled,
-          },
-        },
-      };
-      // Go to the build info page.
-      const buildInfoPage = getBuildInfoPage();
-      // Wait for overrides + response values.
-      buildInfoPage.prefs = Object.assign({}, prefs);
-      await Promise.all([
-        aboutBrowserProxy.whenCalled('isConsumerAutoUpdateEnabled'),
-        aboutBrowserProxy.whenCalled('setConsumerAutoUpdate'),
-      ]);
-
-      // Managed auto update toggle should not exist.
-      assertFalse(
-          !!buildInfoPage.shadowRoot.querySelector('#managedAutoUpdateToggle'));
-      const cauToggle =
-          buildInfoPage.shadowRoot.querySelector('#consumerAutoUpdateToggle');
-      if (showToggle) {
-        assertTrue(!!cauToggle);
-        assertEquals(isTogglingAllowed, !cauToggle.disabled);
-        assertEquals(isEnabled, cauToggle.checked);
-      } else {
-        assertFalse(!!cauToggle);
-      }
-
-      // Check dialog popup when toggling off.
-      if (showToggle && isEnabled) {
-        let dialog = buildInfoPage.shadowRoot.querySelector(
-            'settings-consumer-auto-update-toggle-dialog');
-        assertFalse(!!dialog);
-
-        cauToggle.click();
-        flush();
-
-        dialog = buildInfoPage.shadowRoot.querySelector(
-            'settings-consumer-auto-update-toggle-dialog');
-        // Only when toggling is allowed, should the dialog popup.
-        if (isTogglingAllowed) {
-          assertTrue(!!dialog);
-        } else {
-          assertFalse(!!dialog);
-        }
-      }
-    }
-
-    for (let i = 0; i < (1 << 3); i++) {
-      // showToggle should always be true when isTogglingAllowed is true, but
-      // test to catch unintended behaviors from happening.
-      await checkConsumerAutoUpdateToggle(
-          /*isEnabled=*/ (i & 1) > 0,
-          /*isTogglingAllowed=*/ (i & 2) > 0,
-          /*showToggle=*/ (i & 4) > 0);
-    }
-  });
-
-  test('GetHelp', function() {
-    assertTrue(!!page.$.help);
-    page.$.help.click();
-    return aboutBrowserProxy.whenCalled('openOsHelpPage');
-  });
-
-  test('LaunchDiagnostics', async function() {
-    loadTimeData.overrideValues({
-      isDeepLinkingEnabled: true,
-    });
-
-    await initNewPage();
-    flush();
-
-    assertTrue(!!page.$.diagnostics);
-    page.$.diagnostics.click();
-    await aboutBrowserProxy.whenCalled('openDiagnostics');
-  });
-
-  test('Deep link to diagnostics', async () => {
-    loadTimeData.overrideValues({
-      isDeepLinkingEnabled: true,
-    });
-
-    await initNewPage();
-    flush();
-
-    const diagnosticsId = '1707';
-    navigateToSettingsPageWithId(diagnosticsId);
-
-    const deepLinkElement = getDeepLinkButtonElementById('diagnostics');
-    await waitAfterNextRender(deepLinkElement);
-    assertEquals(
-        deepLinkElement, getDeepActiveElement(),
-        `Diagnostics should be focused for settingId=${diagnosticsId}.`);
-  });
-
-  test('FirmwareUpdatesBadge No Updates', async function() {
-    aboutBrowserProxy.setFirmwareUpdatesCount(0);
-    await initNewPage();
-    flush();
-    await aboutBrowserProxy.whenCalled('getFirmwareUpdateCount');
-
-    assertTrue(!!page.$.firmwareUpdateBadge);
-    assertTrue(!!page.$.firmwareUpdateBadgeSeparator);
-
-    assertTrue(page.$.firmwareUpdateBadge.hidden);
-    assertTrue(page.$.firmwareUpdateBadgeSeparator.hidden);
-  });
-
-  test('FirmwareUpdatesBadge N Updates', async function() {
-    for (let i = 1; i < 10; i++) {
-      aboutBrowserProxy.setFirmwareUpdatesCount(i);
-      await initNewPage();
-      flush();
-      await aboutBrowserProxy.whenCalled('getFirmwareUpdateCount');
-
-      assertTrue(!!page.$.firmwareUpdateBadge);
-      assertTrue(!!page.$.firmwareUpdateBadgeSeparator);
-
-      assertFalse(page.$.firmwareUpdateBadge.hidden);
-      assertEquals('os-settings:counter-' + i, page.$.firmwareUpdateBadge.icon);
-
-      assertFalse(page.$.firmwareUpdateBadgeSeparator.hidden);
-    }
-  });
-
-  test('FirmwareUpdatesBadge 10 Updates', async function() {
-    aboutBrowserProxy.setFirmwareUpdatesCount(10);
-    await initNewPage();
-    flush();
-    await aboutBrowserProxy.whenCalled('getFirmwareUpdateCount');
-
-    assertTrue(!!page.$.firmwareUpdateBadge);
-    assertTrue(!!page.$.firmwareUpdateBadgeSeparator);
-
-    assertFalse(page.$.firmwareUpdateBadge.hidden);
-    assertEquals('os-settings:counter-9', page.$.firmwareUpdateBadge.icon);
-
-    assertFalse(page.$.firmwareUpdateBadgeSeparator.hidden);
-  });
-
-  test('LaunchFirmwareUpdates', async function() {
-    loadTimeData.overrideValues({
-      isDeepLinkingEnabled: true,
-    });
-
-    await initNewPage();
-    flush();
-
-    assertTrue(!!page.$.firmwareUpdates);
-    page.$.firmwareUpdates.click();
-    await aboutBrowserProxy.whenCalled('openFirmwareUpdatesPage');
-  });
-
-  test('Deep link to firmware updates', async () => {
-    loadTimeData.overrideValues({
-      isDeepLinkingEnabled: true,
-    });
-
-    await initNewPage();
-    flush();
-
-    const firmwareUpdatesId = '1709';
-    navigateToSettingsPageWithId(firmwareUpdatesId);
-
-    const deepLinkElement = getDeepLinkButtonElementById('firmwareUpdates');
-    await waitAfterNextRender(deepLinkElement);
-    assertEquals(
-        deepLinkElement, getDeepActiveElement(),
-        `Firmware updates should be focused for settingId=${
-            firmwareUpdatesId}.`);
-  });
-
-  // Regression test for crbug.com/1220294
-  test('Update button shown initially', async () => {
-    aboutBrowserProxy.blockRefreshUpdateStatus();
-    await initNewPage();
-
-    const {checkForUpdates} = page.$;
-    assertFalse(checkForUpdates.hidden);
-  });
-
-  test('Update button click moves focus', async () => {
-    await initNewPage();
-
-    const {checkForUpdates, updateStatusMessageInner} = page.$;
-    checkForUpdates.click();
-    await aboutBrowserProxy.whenCalled('requestUpdate');
-    assertEquals(
-        updateStatusMessageInner, getDeepActiveElement(),
-        'Update status message should be focused.');
-  });
-});
-
-suite('<os-about-page> OfficialBuild', function() {
-  let page = null;
-  let browserProxy = null;
-
-  setup(function() {
-    browserProxy = new TestAboutPageBrowserProxy();
-    AboutPageBrowserProxyImpl.setInstanceForTesting(browserProxy);
-    clearBody();
-    page = document.createElement('os-about-page');
-    document.body.appendChild(page);
-  });
-
-  teardown(function() {
-    page.remove();
-    page = null;
-    Router.getInstance().resetRouteForTesting();
-  });
-
-  test('ReportAnIssue', async function() {
-    assertTrue(!!page.$.reportIssue);
-    page.$.reportIssue.click();
-    await browserProxy.whenCalled('openFeedbackDialog');
-  });
-
-  test('Deep link to report an issue', async () => {
-    loadTimeData.overrideValues({
-      isDeepLinkingEnabled: true,
-    });
-
-    const params = new URLSearchParams();
-    params.append('settingId', '1705');
-    Router.getInstance().navigateTo(routes.ABOUT, params);
-
-    flush();
-
-    const deepLinkElement = page.shadowRoot.querySelector('#reportIssue')
-                                .shadowRoot.querySelector('cr-icon-button');
-    await waitAfterNextRender(deepLinkElement);
-    assertEquals(
-        deepLinkElement, getDeepActiveElement(),
-        'Report an issue button should be focused for settingId=1705.');
-  });
-
-  test('Deep link to terms of service', async () => {
-    loadTimeData.overrideValues({
-      isDeepLinkingEnabled: true,
-    });
-
-    const params = new URLSearchParams();
-    params.append('settingId', '1706');
-    Router.getInstance().navigateTo(routes.ABOUT, params);
-
-    flush();
-
-    const deepLinkElement = page.shadowRoot.querySelector('#aboutProductTos');
-    await waitAfterNextRender(deepLinkElement);
-    assertEquals(
-        deepLinkElement, getDeepActiveElement(),
-        'Terms of service link should be focused for settingId=1706.');
-  });
-});
diff --git a/chrome/test/data/webui/settings/chromeos/os_files_page/files_settings_card_test.ts b/chrome/test/data/webui/settings/chromeos/os_files_page/files_settings_card_test.ts
index 12cac802..28a1686 100644
--- a/chrome/test/data/webui/settings/chromeos/os_files_page/files_settings_card_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_files_page/files_settings_card_test.ts
@@ -71,8 +71,7 @@
   async function createFilesSettingsCard() {
     prefElement = document.createElement('settings-prefs');
     const fakeSettingsPrivate = new FakeSettingsPrivate(getFakePrefs());
-    prefElement.initialize(
-        fakeSettingsPrivate as unknown as typeof chrome.settingsPrivate);
+    prefElement.initialize(fakeSettingsPrivate);
     await CrSettingsPrefs.initialized;
 
     filesSettingsCard = document.createElement('files-settings-card');
diff --git a/chrome/test/data/webui/settings/chromeos/os_files_page/os_files_page_test.ts b/chrome/test/data/webui/settings/chromeos/os_files_page/os_files_page_test.ts
index a7df7876..2ad9dde 100644
--- a/chrome/test/data/webui/settings/chromeos/os_files_page/os_files_page_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_files_page/os_files_page_test.ts
@@ -63,8 +63,7 @@
   async function createFilesPage() {
     prefElement = document.createElement('settings-prefs');
     const fakeSettingsPrivate = new FakeSettingsPrivate(getFakePrefs());
-    prefElement.initialize(
-        fakeSettingsPrivate as unknown as typeof chrome.settingsPrivate);
+    prefElement.initialize(fakeSettingsPrivate);
     await CrSettingsPrefs.initialized;
 
     filesPage = document.createElement('os-settings-files-page');
diff --git a/chrome/test/data/webui/settings/chromeos/os_languages_page/input_method_options_page_test.ts b/chrome/test/data/webui/settings/chromeos/os_languages_page/input_method_options_page_test.ts
index b120cf8..da03596 100644
--- a/chrome/test/data/webui/settings/chromeos/os_languages_page/input_method_options_page_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_languages_page/input_method_options_page_test.ts
@@ -62,8 +62,7 @@
     CrSettingsPrefs.deferInitialization = true;
     const settingsPrefs = document.createElement('settings-prefs');
     settingsPrivate = new FakeSettingsPrivate(getFakePrefs());
-    settingsPrefs.initialize(
-        settingsPrivate as unknown as typeof chrome.settingsPrivate);
+    settingsPrefs.initialize(settingsPrivate);
     document.body.appendChild(settingsPrefs);
     await CrSettingsPrefs.initialized;
 
diff --git a/chrome/test/data/webui/settings/chromeos/os_languages_page/input_page_test.ts b/chrome/test/data/webui/settings/chromeos/os_languages_page/input_page_test.ts
index 40ac2e4..758fa7168 100644
--- a/chrome/test/data/webui/settings/chromeos/os_languages_page/input_page_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_languages_page/input_page_test.ts
@@ -11,11 +11,11 @@
 import {keyDownOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {assertDeepEquals, assertEquals, assertFalse, assertGE, assertGT, assertNotEquals, assertNull, assertStringContains, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {FakeSettingsPrivate} from 'chrome://webui-test/fake_settings_private.js';
 import {fakeDataBind, flushTasks, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 import {eventToPromise, isVisible} from 'chrome://webui-test/test_util.js';
 
 import {FakeLanguageSettingsPrivate, getFakeLanguagePrefs} from '../fake_language_settings_private.js';
-import {FakeSettingsPrivate} from '../fake_settings_private.js';
 
 import {TestLanguagesBrowserProxy} from './test_os_languages_browser_proxy.js';
 import {TestLanguagesMetricsProxy} from './test_os_languages_metrics_proxy.js';
@@ -64,8 +64,7 @@
     // settingsPrivate, so prefer to use settingsPrivate getters/setters
     // whenever possible.
     settingsPrivate.onPrefsChanged.addListener(spellCheckServiceListener);
-    prefElement.initialize(
-        settingsPrivate as unknown as typeof chrome.settingsPrivate);
+    prefElement.initialize(settingsPrivate);
     document.body.appendChild(prefElement);
 
     await CrSettingsPrefs.initialized;
diff --git a/chrome/test/data/webui/settings/chromeos/os_languages_page/os_clear_personalization_data_page_test.ts b/chrome/test/data/webui/settings/chromeos/os_languages_page/os_clear_personalization_data_page_test.ts
index 52c7a6fa..524867de 100644
--- a/chrome/test/data/webui/settings/chromeos/os_languages_page/os_clear_personalization_data_page_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_languages_page/os_clear_personalization_data_page_test.ts
@@ -23,8 +23,7 @@
 
   setup(() => {
     settingsPrefs = document.createElement('settings-prefs');
-    const settingsPrivate = new FakeSettingsPrivate(getFakePrefs()) as
-        unknown as typeof chrome.settingsPrivate;
+    const settingsPrivate = new FakeSettingsPrivate(getFakePrefs());
     settingsPrefs.initialize(settingsPrivate);
 
     clearPersonalizedDataPage =
diff --git a/chrome/test/data/webui/settings/chromeos/os_languages_page/os_edit_dictionary_page_test.ts b/chrome/test/data/webui/settings/chromeos/os_languages_page/os_edit_dictionary_page_test.ts
index 220680e..8cd9e7d 100644
--- a/chrome/test/data/webui/settings/chromeos/os_languages_page/os_edit_dictionary_page_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_languages_page/os_edit_dictionary_page_test.ts
@@ -9,9 +9,9 @@
 import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {assertEquals, assertFalse, assertNotEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {FakeSettingsPrivate} from 'chrome://webui-test/fake_settings_private.js';
 
 import {FakeLanguageSettingsPrivate} from '../fake_language_settings_private.js';
-import {FakeSettingsPrivate} from '../fake_settings_private.js';
 
 import {TestLanguagesBrowserProxy} from './test_os_languages_browser_proxy.js';
 
@@ -69,8 +69,7 @@
   setup(() => {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
     settingsPrefs = document.createElement('settings-prefs');
-    const settingsPrivate = new FakeSettingsPrivate(getFakePrefs()) as
-        unknown as typeof chrome.settingsPrivate;
+    const settingsPrivate = new FakeSettingsPrivate(getFakePrefs());
     settingsPrefs.initialize(settingsPrivate);
 
     languageSettingsPrivate = new FakeLanguageSettingsPrivate();
diff --git a/chrome/test/data/webui/settings/chromeos/os_languages_page/os_languages_page_v2_test.ts b/chrome/test/data/webui/settings/chromeos/os_languages_page/os_languages_page_v2_test.ts
index 8bd54fa..c329e397 100644
--- a/chrome/test/data/webui/settings/chromeos/os_languages_page/os_languages_page_v2_test.ts
+++ b/chrome/test/data/webui/settings/chromeos/os_languages_page/os_languages_page_v2_test.ts
@@ -10,11 +10,11 @@
 import {keyDownOn} from 'chrome://resources/polymer/v3_0/iron-test-helpers/mock-interactions.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {assertDeepEquals, assertEquals, assertFalse, assertGE, assertGT, assertLT, assertNull, assertStringContains, assertTrue} from 'chrome://webui-test/chai_assert.js';
+import {FakeSettingsPrivate} from 'chrome://webui-test/fake_settings_private.js';
 import {fakeDataBind, waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
 import {isVisible} from 'chrome://webui-test/test_util.js';
 
 import {FakeLanguageSettingsPrivate, getFakeLanguagePrefs} from '../fake_language_settings_private.js';
-import {FakeSettingsPrivate} from '../fake_settings_private.js';
 import {TestLifetimeBrowserProxy} from '../test_os_lifetime_browser_proxy.js';
 
 import {TestLanguagesBrowserProxy} from './test_os_languages_browser_proxy.js';
@@ -56,8 +56,7 @@
         window.trustedTypes.emptyHTML as unknown as string;
 
     settingsPrefs = document.createElement('settings-prefs');
-    const settingsPrivate = new FakeSettingsPrivate(getFakeLanguagePrefs()) as
-        unknown as typeof chrome.settingsPrivate;
+    const settingsPrivate = new FakeSettingsPrivate(getFakeLanguagePrefs());
     settingsPrefs.initialize(settingsPrivate);
     document.body.appendChild(settingsPrefs);
     await CrSettingsPrefs.initialized;
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.cc b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.cc
index c0bcbbf..8e1e843 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.cc
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.cc
@@ -24,12 +24,21 @@
     set_test_loader_host(chrome::kChromeUIOSSettingsHost);
   }
 
-  void RunSettingsTest(const std::string& current_path) {
+  // Runs the specified test.
+  // - test_path: The path to the test file within the CrOS Settings test root
+  //              directory.
+  // - trigger: A JS string used to trigger the tests, defaults to
+  //            "mocha.run()".
+  void RunSettingsTest(
+      const std::string& test_path,
+      const std::string& trigger = std::string("mocha.run()")) {
     // All OS Settings test files are located in the directory
     // settings/chromeos/.
-    const std::string path_with_parent_directory =
-        base::StrCat({std::string("settings/chromeos/"), current_path});
-    RunTest(path_with_parent_directory, "mocha.run()");
+    const std::string path_with_parent_directory = base::StrCat({
+        std::string("settings/chromeos/"),
+        test_path,
+    });
+    RunTest(path_with_parent_directory, trigger);
   }
 
   base::test::ScopedFeatureList scoped_feature_list_{
@@ -60,20 +69,6 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 };
 
-using OsAboutPageTest = OSSettingsMochaTest;
-
-IN_PROC_BROWSER_TEST_F(OsAboutPageTest, AllBuilds) {
-  RunTest("settings/chromeos/os_about_page_tests.js",
-          "runMochaSuite('<os-about-page> AllBuilds')");
-}
-
-#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
-IN_PROC_BROWSER_TEST_F(OsAboutPageTest, OfficialBuild) {
-  RunTest("settings/chromeos/os_about_page_tests.js",
-          "runMochaSuite('<os-about-page> OfficialBuild')");
-}
-#endif
-
 class OSSettingsMochaTestApnRevampEnabled : public OSSettingsMochaTest {
  protected:
   OSSettingsMochaTestApnRevampEnabled() {
@@ -1011,6 +1006,32 @@
   RunSettingsTest("os_a11y_page/tts_voice_subpage_test.js");
 }
 
+IN_PROC_BROWSER_TEST_F(OSSettingsMochaTestRevampEnabled,
+                       OsAboutPage_AllBuilds) {
+  RunSettingsTest("os_about_page/os_about_page_test.js",
+                  "runMochaSuite('<os-about-page> AllBuilds')");
+}
+
+IN_PROC_BROWSER_TEST_F(OSSettingsMochaTestRevampDisabled,
+                       OsAboutPage_AllBuilds) {
+  RunSettingsTest("os_about_page/os_about_page_test.js",
+                  "runMochaSuite('<os-about-page> AllBuilds')");
+}
+
+#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
+IN_PROC_BROWSER_TEST_F(OSSettingsMochaTestRevampEnabled,
+                       OsAboutPage_OfficialBuild) {
+  RunSettingsTest("os_about_page/os_about_page_test.js",
+                  "runMochaSuite('<os-about-page> OfficialBuild')");
+}
+
+IN_PROC_BROWSER_TEST_F(OSSettingsMochaTestRevampDisabled,
+                       OsAboutPage_OfficialBuild) {
+  RunSettingsTest("os_about_page/os_about_page_test.js",
+                  "runMochaSuite('<os-about-page> OfficialBuild')");
+}
+#endif
+
 IN_PROC_BROWSER_TEST_F(OSSettingsMochaTest, OsAboutPageChannelSwitcherDialog) {
   RunSettingsTest("os_about_page/channel_switcher_dialog_test.js");
 }
diff --git a/chrome/test/data/webui/settings/edit_dictionary_page_test.ts b/chrome/test/data/webui/settings/edit_dictionary_page_test.ts
index bbdefec..8eb98f50 100644
--- a/chrome/test/data/webui/settings/edit_dictionary_page_test.ts
+++ b/chrome/test/data/webui/settings/edit_dictionary_page_test.ts
@@ -54,8 +54,7 @@
   setup(function() {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
     settingsPrefs = document.createElement('settings-prefs');
-    const settingsPrivate = new FakeSettingsPrivate(getFakePrefs()) as
-        unknown as typeof chrome.settingsPrivate;
+    const settingsPrivate = new FakeSettingsPrivate(getFakePrefs());
     settingsPrefs.initialize(settingsPrivate);
 
     languageSettingsPrivate = new FakeLanguageSettingsPrivate();
diff --git a/chrome/test/data/webui/settings/languages_page_metrics_test_browser.ts b/chrome/test/data/webui/settings/languages_page_metrics_test_browser.ts
index 5912cdcf..26f9569 100644
--- a/chrome/test/data/webui/settings/languages_page_metrics_test_browser.ts
+++ b/chrome/test/data/webui/settings/languages_page_metrics_test_browser.ts
@@ -33,8 +33,7 @@
   setup(function() {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
     const settingsPrefs = document.createElement('settings-prefs');
-    const settingsPrivate = new FakeSettingsPrivate(getFakeLanguagePrefs()) as
-        unknown as typeof chrome.settingsPrivate;
+    const settingsPrivate = new FakeSettingsPrivate(getFakeLanguagePrefs());
     settingsPrefs.initialize(settingsPrivate);
     document.body.appendChild(settingsPrefs);
     return CrSettingsPrefs.initialized.then(function() {
diff --git a/chrome/test/data/webui/settings/languages_page_test.ts b/chrome/test/data/webui/settings/languages_page_test.ts
index ff266d6..0b14dc8 100644
--- a/chrome/test/data/webui/settings/languages_page_test.ts
+++ b/chrome/test/data/webui/settings/languages_page_test.ts
@@ -52,8 +52,7 @@
   setup(function() {
     const settingsPrefs = document.createElement('settings-prefs');
     const settingsPrivate = new FakeSettingsPrivate(getFakeLanguagePrefs());
-    settingsPrefs.initialize(
-        settingsPrivate as unknown as typeof chrome.settingsPrivate);
+    settingsPrefs.initialize(settingsPrivate);
     document.body.appendChild(settingsPrefs);
     return CrSettingsPrefs.initialized.then(function() {
       // Set up test browser proxy.
diff --git a/chrome/test/data/webui/settings/languages_test.ts b/chrome/test/data/webui/settings/languages_test.ts
index d4d58dc..c9ef61bf 100644
--- a/chrome/test/data/webui/settings/languages_test.ts
+++ b/chrome/test/data/webui/settings/languages_test.ts
@@ -37,8 +37,7 @@
   setup(async function() {
     const settingsPrefs = document.createElement('settings-prefs');
     const settingsPrivate = new FakeSettingsPrivate(getFakeLanguagePrefs());
-    settingsPrefs.initialize(
-        settingsPrivate as unknown as typeof chrome.settingsPrivate);
+    settingsPrefs.initialize(settingsPrivate);
     document.body.appendChild(settingsPrefs);
 
     // Setup test browser proxy.
diff --git a/chrome/test/data/webui/settings/privacy_sandbox_page_test.ts b/chrome/test/data/webui/settings/privacy_sandbox_page_test.ts
index fe8f105f..25364d85 100644
--- a/chrome/test/data/webui/settings/privacy_sandbox_page_test.ts
+++ b/chrome/test/data/webui/settings/privacy_sandbox_page_test.ts
@@ -1275,7 +1275,8 @@
   });
 
   test('ManageTopicsPageTestLabelsAndSubLabels', async function() {
-    const firstLevelTopics = page.shadowRoot!.querySelectorAll('.topic-toggle');
+    const firstLevelTopics =
+        page.shadowRoot!.querySelectorAll('.topic-toggle-row');
     assertEquals(2, firstLevelTopics.length);
     const labels = Array.from(page.shadowRoot!.querySelectorAll('.label'))
                        .map(label => label.textContent);
@@ -1305,14 +1306,14 @@
       displayString: 'test-topic-3',
       description: '',
     }]);
+    // Unblocking topic 1, toggle should now be checked meaning it's unblocked.
     const toggles = page.shadowRoot!.querySelectorAll('cr-toggle');
     assertEquals(2, toggles.length);
     toggles[0]!.click();
     assertTrue(toggles[0]!.checked);
     // Attempting to block topic 1, causes a dialog to open due to
-    // getChildTopicsCurrentlyAssigned returning a non empty
-    // list of child topics that would be blocked
-    // if they choose to continue.
+    // getChildTopicsCurrentlyAssigned returning a non empty list of
+    // child topics that would be blocked if they chose to continue.
     toggles[0]!.click();
     await flushTasks();
 
@@ -1355,6 +1356,67 @@
     await flushTasks();
     assertFalse(toggles[1]!.checked);
   });
+
+  test('ManageTopicsPageClickOnToggleRow', async function() {
+    testPrivacySandboxBrowserProxy.setChildTopics([{
+      topicId: 3,
+      taxonomyVersion: 1,
+      displayString: 'test-topic-3',
+      description: '',
+    }]);
+    // Unblocking topic 1, toggle should now be checked meaning it's unblocked.
+    const topicToggleRows =
+        page.shadowRoot!.querySelectorAll<HTMLElement>('.topic-toggle-row');
+    const toggles = page.shadowRoot!.querySelectorAll('cr-toggle');
+    assertEquals(2, topicToggleRows.length);
+    assertEquals(2, toggles.length);
+    topicToggleRows[0]!.click();
+    assertTrue(toggles[0]!.checked);
+
+    // Attempting to block topic 1, causes a dialog to open due to
+    // getChildTopicsCurrentlyAssigned returning a non empty list of child
+    // topics that would be blocked if they choose to continue.
+    topicToggleRows[0]!.click();
+    await flushTasks();
+
+    let blockTopicDialog =
+        page.shadowRoot!.querySelector<SettingsSimpleConfirmationDialogElement>(
+            '#blockTopicDialog');
+    assertTrue(!!blockTopicDialog);
+    await (whenAttributeIs(blockTopicDialog.$.dialog, 'open', ''));
+
+    blockTopicDialog.$.cancel.click();
+    await eventToPromise('close', blockTopicDialog);
+    await flushTasks();
+
+    // After closing the dialog and choosing to not block it, the toggle is
+    // turned back ON.
+    assertTrue(toggles[0]!.checked);
+
+    // Attempt to block topic 1 again
+    topicToggleRows[0]!.click();
+    await flushTasks();
+    blockTopicDialog =
+        page.shadowRoot!.querySelector<SettingsSimpleConfirmationDialogElement>(
+            '#blockTopicDialog');
+    assertTrue(!!blockTopicDialog);
+    await (whenAttributeIs(blockTopicDialog.$.dialog, 'open', ''));
+
+    blockTopicDialog.$.confirm.click();
+    await eventToPromise('close', blockTopicDialog);
+    await flushTasks();
+
+    // The block button blocks the topic and changes the toggle to be turned
+    // OFF.
+    assertFalse(toggles[0]!.checked);
+
+    testPrivacySandboxBrowserProxy.setChildTopics([]);
+    // Toggle 2 (topic 4) has no child topics that are currently assigned which
+    // is why the dialog does not appear and the toggle is turned OFF.
+    topicToggleRows[1]!.click();
+    await flushTasks();
+    assertFalse(toggles[1]!.checked);
+  });
 });
 
 suite('ManageTopicsAndAdTopicsPageState', function() {
diff --git a/chrome/test/data/webui/settings/speed_page_test.ts b/chrome/test/data/webui/settings/speed_page_test.ts
index 3b142e8..ffafdcf 100644
--- a/chrome/test/data/webui/settings/speed_page_test.ts
+++ b/chrome/test/data/webui/settings/speed_page_test.ts
@@ -35,8 +35,7 @@
 
   setup(function() {
     settingsPrefs = document.createElement('settings-prefs');
-    const settingsPrivate = new FakeSettingsPrivate(getFakePrefs()) as
-        unknown as typeof chrome.settingsPrivate;
+    const settingsPrivate = new FakeSettingsPrivate(getFakePrefs());
     settingsPrefs.initialize(settingsPrivate);
 
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
diff --git a/chrome/test/data/webui/settings/spell_check_page_metrics_test_browser.ts b/chrome/test/data/webui/settings/spell_check_page_metrics_test_browser.ts
index 592c144..074cfc7 100644
--- a/chrome/test/data/webui/settings/spell_check_page_metrics_test_browser.ts
+++ b/chrome/test/data/webui/settings/spell_check_page_metrics_test_browser.ts
@@ -46,8 +46,7 @@
   setup(function() {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
     const settingsPrefs = document.createElement('settings-prefs');
-    const settingsPrivate = new FakeSettingsPrivate(getFakeLanguagePrefs()) as
-        unknown as typeof chrome.settingsPrivate;
+    const settingsPrivate = new FakeSettingsPrivate(getFakeLanguagePrefs());
     settingsPrefs.initialize(settingsPrivate);
     document.body.appendChild(settingsPrefs);
     return CrSettingsPrefs.initialized.then(function() {
diff --git a/chrome/test/data/webui/settings/spell_check_page_test.ts b/chrome/test/data/webui/settings/spell_check_page_test.ts
index 9ae87f0..9ca82c5 100644
--- a/chrome/test/data/webui/settings/spell_check_page_test.ts
+++ b/chrome/test/data/webui/settings/spell_check_page_test.ts
@@ -44,8 +44,7 @@
   setup(function() {
     const settingsPrefs = document.createElement('settings-prefs');
     const settingsPrivate = new FakeSettingsPrivate(getFakeLanguagePrefs());
-    settingsPrefs.initialize(
-        settingsPrivate as unknown as typeof chrome.settingsPrivate);
+    settingsPrefs.initialize(settingsPrivate);
     document.body.appendChild(settingsPrefs);
     return CrSettingsPrefs.initialized.then(function() {
       // Set up test browser proxy.
diff --git a/chrome/test/data/webui/settings/translate_page_metrics_test_browser.ts b/chrome/test/data/webui/settings/translate_page_metrics_test_browser.ts
index 5bad6f66..403d316 100644
--- a/chrome/test/data/webui/settings/translate_page_metrics_test_browser.ts
+++ b/chrome/test/data/webui/settings/translate_page_metrics_test_browser.ts
@@ -28,8 +28,7 @@
   setup(function() {
     document.body.innerHTML = window.trustedTypes!.emptyHTML;
     const settingsPrefs = document.createElement('settings-prefs');
-    const settingsPrivate = new FakeSettingsPrivate(getFakeLanguagePrefs()) as
-        unknown as typeof chrome.settingsPrivate;
+    const settingsPrivate = new FakeSettingsPrivate(getFakeLanguagePrefs());
     settingsPrefs.initialize(settingsPrivate);
     document.body.appendChild(settingsPrefs);
     return CrSettingsPrefs.initialized.then(function() {
diff --git a/chrome/test/data/webui/settings/translate_page_test.ts b/chrome/test/data/webui/settings/translate_page_test.ts
index aaa24ae..837b5870 100644
--- a/chrome/test/data/webui/settings/translate_page_test.ts
+++ b/chrome/test/data/webui/settings/translate_page_test.ts
@@ -36,8 +36,7 @@
   setup(function() {
     const settingsPrefs = document.createElement('settings-prefs');
     const settingsPrivate = new FakeSettingsPrivate(getFakeLanguagePrefs());
-    settingsPrefs.initialize(
-        settingsPrivate as unknown as typeof chrome.settingsPrivate);
+    settingsPrefs.initialize(settingsPrivate);
     document.body.appendChild(settingsPrefs);
     return CrSettingsPrefs.initialized.then(function() {
       // Set up test browser proxy.
diff --git a/chromeos/ash/components/tether/connect_tethering_operation.cc b/chromeos/ash/components/tether/connect_tethering_operation.cc
index ac36415..799663f1 100644
--- a/chromeos/ash/components/tether/connect_tethering_operation.cc
+++ b/chromeos/ash/components/tether/connect_tethering_operation.cc
@@ -10,7 +10,6 @@
 #include "chromeos/ash/components/multidevice/logging/logging.h"
 #include "chromeos/ash/components/tether/message_wrapper.h"
 #include "chromeos/ash/components/tether/proto/tether.pb.h"
-#include "chromeos/ash/components/tether/tether_host_response_recorder.h"
 #include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
 
 namespace ash {
@@ -41,17 +40,16 @@
     multidevice::RemoteDeviceRef device_to_connect,
     device_sync::DeviceSyncClient* device_sync_client,
     secure_channel::SecureChannelClient* secure_channel_client,
-    TetherHostResponseRecorder* tether_host_response_recorder,
     bool setup_required) {
   if (factory_instance_) {
     return factory_instance_->CreateInstance(
         device_to_connect, device_sync_client, secure_channel_client,
-        tether_host_response_recorder, setup_required);
+        setup_required);
   }
 
-  return base::WrapUnique(new ConnectTetheringOperation(
-      device_to_connect, device_sync_client, secure_channel_client,
-      tether_host_response_recorder, setup_required));
+  return base::WrapUnique(
+      new ConnectTetheringOperation(device_to_connect, device_sync_client,
+                                    secure_channel_client, setup_required));
 }
 
 // static
@@ -66,7 +64,6 @@
     multidevice::RemoteDeviceRef device_to_connect,
     device_sync::DeviceSyncClient* device_sync_client,
     secure_channel::SecureChannelClient* secure_channel_client,
-    TetherHostResponseRecorder* tether_host_response_recorder,
     bool setup_required)
     : MessageTransferOperation(
           multidevice::RemoteDeviceRefList{device_to_connect},
@@ -74,7 +71,6 @@
           device_sync_client,
           secure_channel_client),
       remote_device_(device_to_connect),
-      tether_host_response_recorder_(tether_host_response_recorder),
       clock_(base::DefaultClock::GetInstance()),
       setup_required_(setup_required),
       error_code_to_return_(HostResponseErrorCode::NO_RESPONSE) {}
@@ -124,9 +120,6 @@
           << "response_code == SUCCESS. Config: {ssid: \"" << response->ssid()
           << "\", password: \"" << response->password() << "\"}";
 
-      tether_host_response_recorder_->RecordSuccessfulConnectTetheringResponse(
-          remote_device);
-
       // Save the response values here, but do not notify observers until
       // OnOperationFinished(). Notifying observers at this point can cause this
       // object to be deleted, resulting in a crash.
diff --git a/chromeos/ash/components/tether/connect_tethering_operation.h b/chromeos/ash/components/tether/connect_tethering_operation.h
index e0a634b..3b6153f 100644
--- a/chromeos/ash/components/tether/connect_tethering_operation.h
+++ b/chromeos/ash/components/tether/connect_tethering_operation.h
@@ -29,7 +29,6 @@
 namespace ash::tether {
 
 class MessageWrapper;
-class TetherHostResponseRecorder;
 
 // Operation used to request that a tether host share its Internet connection.
 // Attempts a connection to the RemoteDevice passed to its constructor and
@@ -61,7 +60,6 @@
         multidevice::RemoteDeviceRef device_to_connect,
         device_sync::DeviceSyncClient* device_sync_client,
         secure_channel::SecureChannelClient* secure_channel_client,
-        TetherHostResponseRecorder* tether_host_response_recorder,
         bool setup_required);
 
     static void SetFactoryForTesting(Factory* factory);
@@ -72,7 +70,6 @@
         multidevice::RemoteDeviceRef devices_to_connect,
         device_sync::DeviceSyncClient* device_sync_client,
         secure_channel::SecureChannelClient* secure_channel_client,
-        TetherHostResponseRecorder* tether_host_response_recorder,
         bool setup_required) = 0;
 
    private:
@@ -106,7 +103,6 @@
       multidevice::RemoteDeviceRef device_to_connect,
       device_sync::DeviceSyncClient* device_sync_client,
       secure_channel::SecureChannelClient* secure_channel_client,
-      TetherHostResponseRecorder* tether_host_response_recorder,
       bool setup_required);
 
   // MessageTransferOperation:
@@ -155,7 +151,6 @@
   static const uint32_t kSetupRequiredResponseTimeoutSeconds;
 
   multidevice::RemoteDeviceRef remote_device_;
-  raw_ptr<TetherHostResponseRecorder> tether_host_response_recorder_;
   raw_ptr<base::Clock> clock_;
   int connect_message_sequence_number_ = -1;
   bool setup_required_;
diff --git a/chromeos/ash/components/tether/connect_tethering_operation_unittest.cc b/chromeos/ash/components/tether/connect_tethering_operation_unittest.cc
index 2b68dd7..e8376d4 100644
--- a/chromeos/ash/components/tether/connect_tethering_operation_unittest.cc
+++ b/chromeos/ash/components/tether/connect_tethering_operation_unittest.cc
@@ -17,7 +17,6 @@
 #include "base/timer/mock_timer.h"
 #include "chromeos/ash/components/multidevice/remote_device_test_util.h"
 #include "chromeos/ash/components/tether/message_wrapper.h"
-#include "chromeos/ash/components/tether/mock_tether_host_response_recorder.h"
 #include "chromeos/ash/components/tether/proto/tether.pb.h"
 #include "chromeos/ash/components/tether/proto_test_util.h"
 #include "chromeos/ash/services/device_sync/public/cpp/fake_device_sync_client.h"
@@ -73,8 +72,6 @@
         remote_device_(multidevice::CreateRemoteDeviceRefForTest()) {}
 
   void SetUp() override {
-    mock_tether_host_response_recorder_ =
-        std::make_unique<StrictMock<MockTetherHostResponseRecorder>>();
     fake_device_sync_client_ =
         std::make_unique<device_sync::FakeDeviceSyncClient>();
     fake_device_sync_client_->set_local_device_metadata(test_local_device_);
@@ -98,8 +95,7 @@
 
     operation = base::WrapUnique(new ConnectTetheringOperation(
         remote_device_, fake_device_sync_client_.get(),
-        fake_secure_channel_client_.get(),
-        mock_tether_host_response_recorder_.get(), false /* setup_required */));
+        fake_secure_channel_client_.get(), false /* setup_required */));
     operation->SetTimerFactoryForTest(
         std::make_unique<cross_device::FakeTimerFactory>());
     operation->AddObserver(&mock_observer_);
@@ -133,8 +129,6 @@
   std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
   std::unique_ptr<secure_channel::FakeSecureChannelClient>
       fake_secure_channel_client_;
-  std::unique_ptr<StrictMock<MockTetherHostResponseRecorder>>
-      mock_tether_host_response_recorder_;
   base::SimpleTestClock test_clock_;
   MockOperationObserver mock_observer_;
   base::HistogramTester histogram_tester_;
@@ -145,9 +139,6 @@
   static const std::string kTestSsid = "testSsid";
   static const std::string kTestPassword = "testPassword";
 
-  EXPECT_CALL(*mock_tether_host_response_recorder_,
-              RecordSuccessfulConnectTetheringResponse(remote_device_));
-
   // Verify that the Observer is called with success and the correct parameters.
   EXPECT_CALL(mock_observer_, OnSuccessfulConnectTetheringResponse(
                                   remote_device_, kTestSsid, kTestPassword));
@@ -177,10 +168,6 @@
 // success response code; failure to provide these parameters results in a
 // failed tethering connection.
 TEST_F(ConnectTetheringOperationTest, SuccessButInvalidResponse) {
-  EXPECT_CALL(*mock_tether_host_response_recorder_,
-              RecordSuccessfulConnectTetheringResponse(_))
-      .Times(0);
-
   // Verify that the observer is called with failure and the appropriate error
   // code.
   EXPECT_CALL(
@@ -200,10 +187,6 @@
 }
 
 TEST_F(ConnectTetheringOperationTest, UnknownError) {
-  EXPECT_CALL(*mock_tether_host_response_recorder_,
-              RecordSuccessfulConnectTetheringResponse(_))
-      .Times(0);
-
   // Verify that the observer is called with failure and the appropriate error
   // code.
   EXPECT_CALL(
@@ -222,10 +205,6 @@
 }
 
 TEST_F(ConnectTetheringOperationTest, ProvisioningFailed) {
-  EXPECT_CALL(*mock_tether_host_response_recorder_,
-              RecordSuccessfulConnectTetheringResponse(_))
-      .Times(0);
-
   // Verify that the observer is called with failure and the appropriate error
   // code.
   EXPECT_CALL(
@@ -244,10 +223,6 @@
 }
 
 TEST_F(ConnectTetheringOperationTest, InvalidWifiApConfig) {
-  EXPECT_CALL(*mock_tether_host_response_recorder_,
-              RecordSuccessfulConnectTetheringResponse(_))
-      .Times(0);
-
   // Verify that the observer is called with failure and the appropriate error
   // code.
   EXPECT_CALL(
@@ -266,10 +241,6 @@
 }
 
 TEST_F(ConnectTetheringOperationTest, InvalidActiveExistingSoftApConfig) {
-  EXPECT_CALL(*mock_tether_host_response_recorder_,
-              RecordSuccessfulConnectTetheringResponse(_))
-      .Times(0);
-
   // Verify that the observer is called with failure and the appropriate error
   // code.
   EXPECT_CALL(
@@ -288,10 +259,6 @@
 }
 
 TEST_F(ConnectTetheringOperationTest, InvalidNewSoftApConfig) {
-  EXPECT_CALL(*mock_tether_host_response_recorder_,
-              RecordSuccessfulConnectTetheringResponse(_))
-      .Times(0);
-
   // Verify that the observer is called with failure and the appropriate error
   // code.
   EXPECT_CALL(
@@ -324,7 +291,6 @@
       new ConnectTetheringOperation(remote_device_,
                                     fake_device_sync_client_.get(),
                                     fake_secure_channel_client_.get(),
-                                    mock_tether_host_response_recorder_.get(),
                                     true /* setup_required */));
 
   EXPECT_EQ(ConnectTetheringOperation::kSetupRequiredResponseTimeoutSeconds,
@@ -333,8 +299,7 @@
   // Setup not required case.
   operation.reset(new ConnectTetheringOperation(
       remote_device_, fake_device_sync_client_.get(),
-      fake_secure_channel_client_.get(),
-      mock_tether_host_response_recorder_.get(), false /* setup_required */));
+      fake_secure_channel_client_.get(), false /* setup_required */));
 
   EXPECT_EQ(ConnectTetheringOperation::kSetupNotRequiredResponseTimeoutSeconds,
             operation->GetMessageTimeoutSeconds());
diff --git a/chromeos/ash/components/tether/host_connection_metrics_logger.cc b/chromeos/ash/components/tether/host_connection_metrics_logger.cc
index 95eb92b..717c5be 100644
--- a/chromeos/ash/components/tether/host_connection_metrics_logger.cc
+++ b/chromeos/ash/components/tether/host_connection_metrics_logger.cc
@@ -83,6 +83,11 @@
           ConnectionToHostResult_FailureClientConnectionEventType::
               INTERNAL_ERROR);
       break;
+    case ConnectionToHostInternalError::CLIENT_CONNECTION_WIFI_FAILED_TO_ENABLE:
+      RecordConnectionResultFailureClientConnection(
+          ConnectionToHostResult_FailureClientConnectionEventType::
+              WIFI_FAILED_TO_ENABLED);
+      break;
     case ConnectionToHostInternalError::
         CLIENT_CONNECTION_NETWORK_CONNECTION_HANDLER_FAILED:
       RecordConnectionResultFailureClientConnection(
@@ -143,7 +148,7 @@
       RecordConnectionResultFailure(
           ConnectionToHostResult_FailureEventType::INVALID_WIFI_AP_CONFIG);
       break;
-  }
+  };
 }
 
 void HostConnectionMetricsLogger::OnActiveHostChanged(
diff --git a/chromeos/ash/components/tether/host_connection_metrics_logger.h b/chromeos/ash/components/tether/host_connection_metrics_logger.h
index 4079025..f7cf21af 100644
--- a/chromeos/ash/components/tether/host_connection_metrics_logger.h
+++ b/chromeos/ash/components/tether/host_connection_metrics_logger.h
@@ -43,6 +43,7 @@
     CLIENT_CONNECTION_INTERNAL_ERROR,
     CLIENT_CONNECTION_NETWORK_CONNECTION_HANDLER_FAILED,
     CLIENT_CONNECTION_NETWORK_STATE_WAS_NULL,
+    CLIENT_CONNECTION_WIFI_FAILED_TO_ENABLE,
     TETHERING_TIMED_OUT_FIRST_TIME_SETUP_REQUIRED,
     TETHERING_TIMED_OUT_FIRST_TIME_SETUP_NOT_REQUIRED,
     ENABLING_HOTSPOT_FAILED,
@@ -131,6 +132,9 @@
       RecordConnectionResultFailureShutDownDuringConnectionAttempt);
   FRIEND_TEST_ALL_PREFIXES(
       HostConnectionMetricsLoggerTest,
+      RecordConnectionResultFailureClientConnection_WifiFailedToEnable);
+  FRIEND_TEST_ALL_PREFIXES(
+      HostConnectionMetricsLoggerTest,
       RecordConnectionResultFailureClientConnection_NetworkConnectionHandlerFailed);
   FRIEND_TEST_ALL_PREFIXES(
       HostConnectionMetricsLoggerTest,
@@ -219,6 +223,7 @@
     INTERNAL_ERROR = 2,
     NETWORK_CONNECTION_HANDLER_FAILED = 3,
     NETWORK_STATE_WAS_NULL = 4,
+    WIFI_FAILED_TO_ENABLED = 5,
     FAILURE_CLIENT_CONNECTION_MAX
   };
 
diff --git a/chromeos/ash/components/tether/host_connection_metrics_logger_unittest.cc b/chromeos/ash/components/tether/host_connection_metrics_logger_unittest.cc
index 8895fc725..01a374b 100644
--- a/chromeos/ash/components/tether/host_connection_metrics_logger_unittest.cc
+++ b/chromeos/ash/components/tether/host_connection_metrics_logger_unittest.cc
@@ -399,6 +399,31 @@
 }
 
 TEST_F(HostConnectionMetricsLoggerTest,
+       RecordConnectionResultFailureClientConnection_WifiFailedToEnable) {
+  metrics_logger_->RecordConnectionToHostResult(
+      HostConnectionMetricsLogger::ConnectionToHostResult::INTERNAL_ERROR,
+      test_devices_[0].GetDeviceId(),
+      ConnectionToHostInternalError::CLIENT_CONNECTION_WIFI_FAILED_TO_ENABLE);
+
+  VerifyFailure_ClientConnection(
+      HostConnectionMetricsLogger::
+          ConnectionToHostResult_FailureClientConnectionEventType::
+              WIFI_FAILED_TO_ENABLED);
+  VerifyFailure(
+      HostConnectionMetricsLogger::ConnectionToHostResult_FailureEventType::
+          CLIENT_CONNECTION_ERROR);
+  VerifySuccess(HostConnectionMetricsLogger::
+                    ConnectionToHostResult_SuccessEventType::FAILURE);
+  VerifyProvisioningFailure(
+      HostConnectionMetricsLogger::
+          ConnectionToHostResult_ProvisioningFailureEventType::OTHER);
+  VerifyEndResult(ConnectionToHostResult::INTERNAL_ERROR);
+  VerifyUnavoidableErrorResult(
+      HostConnectionMetricsLogger::
+          ConnectionToHostResult_UnavoidableErrorEventType::OTHER);
+}
+
+TEST_F(HostConnectionMetricsLoggerTest,
        RecordConnectionResultFailureClientConnection_InternalError) {
   SetActiveHostToConnecting(test_devices_[0].GetDeviceId());
 
diff --git a/chromeos/ash/components/tether/tether_connector_impl.cc b/chromeos/ash/components/tether/tether_connector_impl.cc
index e49781c..876e4f8f 100644
--- a/chromeos/ash/components/tether/tether_connector_impl.cc
+++ b/chromeos/ash/components/tether/tether_connector_impl.cc
@@ -20,6 +20,7 @@
 #include "chromeos/ash/components/tether/host_scan_cache.h"
 #include "chromeos/ash/components/tether/notification_presenter.h"
 #include "chromeos/ash/components/tether/tether_host_fetcher.h"
+#include "chromeos/ash/components/tether/tether_host_response_recorder.h"
 #include "chromeos/ash/components/tether/wifi_hotspot_connector.h"
 #include "chromeos/ash/components/tether/wifi_hotspot_disconnector.h"
 #include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
@@ -73,6 +74,10 @@
           ConnectionToHostResult::INTERNAL_ERROR,
           ConnectionToHostInternalError::
               CLIENT_CONNECTION_NETWORK_CONNECTION_HANDLER_FAILED);
+    case WifiHotspotConnector::WifiHotspotConnectionError::kWifiFailedToEnabled:
+      return std::make_pair(ConnectionToHostResult::INTERNAL_ERROR,
+                            ConnectionToHostInternalError::
+                                CLIENT_CONNECTION_WIFI_FAILED_TO_ENABLE);
   }
 }
 
@@ -218,6 +223,8 @@
     multidevice::RemoteDeviceRef remote_device,
     const std::string& ssid,
     const std::string& password) {
+  tether_host_response_recorder_->RecordSuccessfulConnectTetheringResponse(
+      remote_device);
   if (device_id_pending_connection_ != remote_device.GetDeviceId()) {
     // If the success was part of a previous attempt for a different device,
     // ignore it.
@@ -304,7 +311,6 @@
           device_id);
   connect_tethering_operation_ = ConnectTetheringOperation::Factory::Create(
       *tether_host_to_connect, device_sync_client_, secure_channel_client_,
-      tether_host_response_recorder_,
       host_scan_cache_->DoesHostRequireSetup(tether_network_guid));
   connect_tethering_operation_->AddObserver(this);
   connect_tethering_operation_->Initialize();
@@ -374,7 +380,7 @@
       PA_LOG(WARNING)
           << "Failed to connect to Wi-Fi hotspot for device with ID "
           << multidevice::RemoteDeviceRef::TruncateDeviceIdForLogs(device_id)
-          << ", " << "but the connection to that device failed.";
+          << ", " << "but the connection to that device was canceled.";
       return;
     }
 
diff --git a/chromeos/ash/components/tether/tether_connector_impl_unittest.cc b/chromeos/ash/components/tether/tether_connector_impl_unittest.cc
index 97bc299..a052801 100644
--- a/chromeos/ash/components/tether/tether_connector_impl_unittest.cc
+++ b/chromeos/ash/components/tether/tether_connector_impl_unittest.cc
@@ -70,12 +70,10 @@
       multidevice::RemoteDeviceRef device_to_connect,
       device_sync::DeviceSyncClient* device_sync_client,
       secure_channel::SecureChannelClient* secure_channel_client,
-      TetherHostResponseRecorder* tether_host_response_recorder,
       bool setup_required)
       : ConnectTetheringOperation(device_to_connect,
                                   device_sync_client,
                                   secure_channel_client,
-                                  tether_host_response_recorder,
                                   setup_required),
         setup_required_(setup_required) {}
 
@@ -123,12 +121,11 @@
       multidevice::RemoteDeviceRef device_to_connect,
       device_sync::DeviceSyncClient* device_sync_client,
       secure_channel::SecureChannelClient* secure_channel_client,
-      TetherHostResponseRecorder* tether_host_response_recorder,
       bool setup_required) override {
     FakeConnectTetheringOperation* operation =
-        new FakeConnectTetheringOperation(
-            device_to_connect, device_sync_client, secure_channel_client,
-            tether_host_response_recorder, setup_required);
+        new FakeConnectTetheringOperation(device_to_connect, device_sync_client,
+                                          secure_channel_client,
+                                          setup_required);
     created_operations_.push_back(operation);
     return base::WrapUnique(operation);
   }
@@ -266,6 +263,10 @@
       HostConnectionMetricsLogger::ConnectionToHostResult expected_event_type,
       std::optional<HostConnectionMetricsLogger::ConnectionToHostInternalError>
           expected_internal_error) {
+    EXPECT_CALL(*mock_tether_host_response_recorder_,
+                RecordSuccessfulConnectTetheringResponse(testing::_))
+        .Times(0);
+
     EXPECT_CALL(*mock_host_connection_metrics_logger_,
                 RecordConnectionToHostResult(
                     expected_event_type,
@@ -623,12 +624,19 @@
       fake_wifi_hotspot_disconnector_->last_disconnected_wifi_network_guid());
 }
 
+MATCHER_P(HasID, id, "") {
+  return true;
+}
+
 TEST_F(TetherConnectorImplTest, TestSuccessfulConnection) {
   EXPECT_CALL(*mock_host_connection_metrics_logger_,
               RecordConnectionToHostResult(
                   HostConnectionMetricsLogger::ConnectionToHostResult::SUCCESS,
                   test_devices_[0].GetDeviceId(), Eq(std::nullopt)));
 
+  EXPECT_CALL(*mock_tether_host_response_recorder_,
+              RecordSuccessfulConnectTetheringResponse(test_devices_[0]));
+
   CallConnect(GetTetherNetworkGuid(test_devices_[0].GetDeviceId()));
   EXPECT_EQ(ActiveHost::ActiveHostStatus::CONNECTING,
             fake_active_host_->GetActiveHostStatus());
diff --git a/chromeos/ash/components/tether/wifi_hotspot_connector.cc b/chromeos/ash/components/tether/wifi_hotspot_connector.cc
index da6ccb3..8f0633c 100644
--- a/chromeos/ash/components/tether/wifi_hotspot_connector.cc
+++ b/chromeos/ash/components/tether/wifi_hotspot_connector.cc
@@ -119,6 +119,8 @@
 void WifiHotspotConnector::OnEnableWifiError(const std::string& error_name) {
   is_waiting_for_wifi_to_enable_ = false;
   PA_LOG(ERROR) << "Failed to enable Wi-Fi: " << error_name;
+  CompleteActiveConnectionAttempt(
+      WifiHotspotConnectionError::kWifiFailedToEnabled);
 }
 
 void WifiHotspotConnector::DeviceListChanged() {
@@ -426,6 +428,10 @@
     case WifiHotspotConnector::WifiHotspotConnectionError::
         kNetworkConnectionHandlerFailed:
       stream << "[network connection handler failed to connect]";
+      break;
+    case WifiHotspotConnector::WifiHotspotConnectionError::kWifiFailedToEnabled:
+      stream << "[wifi failed to enabled]";
+      break;
   }
 
   return stream;
diff --git a/chromeos/ash/components/tether/wifi_hotspot_connector.h b/chromeos/ash/components/tether/wifi_hotspot_connector.h
index 1c62163..d5edc74 100644
--- a/chromeos/ash/components/tether/wifi_hotspot_connector.h
+++ b/chromeos/ash/components/tether/wifi_hotspot_connector.h
@@ -36,6 +36,7 @@
     kCancelledForNewerConnectionAttempt,
     kNetworkConnectionHandlerFailed,
     kNetworkStateWasNull,
+    kWifiFailedToEnabled,
   };
 
   WifiHotspotConnector(NetworkHandler* network_handler,
diff --git a/chromeos/ash/components/tether/wifi_hotspot_connector_unittest.cc b/chromeos/ash/components/tether/wifi_hotspot_connector_unittest.cc
index aa059d63..9885c620 100644
--- a/chromeos/ash/components/tether/wifi_hotspot_connector_unittest.cc
+++ b/chromeos/ash/components/tether/wifi_hotspot_connector_unittest.cc
@@ -915,6 +915,38 @@
 }
 
 TEST_F(WifiHotspotConnectorTest,
+       TestConnect_WifiDisabled_FailsIfWifiDoesNotTurnOn) {
+  technology_state_controller()->SetTechnologiesEnabled(
+      NetworkTypePattern::WiFi(), false /* enabled */,
+      network_handler::ErrorCallback());
+
+  std::vector<std::string> prohibited_technologies;
+  prohibited_technologies.push_back(shill::kTypeWifi);
+  network_state_handler()->SetProhibitedTechnologies(prohibited_technologies);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(
+      network_state_handler()->IsTechnologyEnabled(NetworkTypePattern::WiFi()));
+
+  wifi_hotspot_connector_->ConnectToWifiHotspot(
+      std::string(kSsid), std::string(kPassword), kTetherNetworkGuid,
+      base::BindOnce(&WifiHotspotConnectorTest::WifiConnectionCallback,
+                     base::Unretained(this)));
+
+  // Allow the asynchronous call to
+  // TechnologyStateHandler::SetTechnologiesEnabled() within
+  // WifiHotspotConnector::ConnectToWifiHotspot() to synchronously run. After
+  // this call, Wi-Fi should be enabled and WifiHotspotConnector will have
+  // called TestNetworkConnect::CreateConfiguration().
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(
+      network_state_handler()->IsTechnologyEnabled(NetworkTypePattern::WiFi()));
+  EXPECT_EQ(1u, connection_callback_responses_.size());
+  EXPECT_EQ(
+      WifiHotspotConnector::WifiHotspotConnectionError::kWifiFailedToEnabled,
+      connection_callback_responses_[0].error());
+}
+
+TEST_F(WifiHotspotConnectorTest,
        TestConnect_WifiDisabled_Success_OtherDeviceStatesChange) {
   technology_state_controller()->SetTechnologiesEnabled(
       NetworkTypePattern::WiFi(), false /* enabled */,
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index bdf1f636..d8f61fd3 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -3142,6 +3142,68 @@
           You can only create wallpapers with AI for personal and non-commercial use. When you get wallpaper help, text is sent to Google AI servers to generate wallpaper suggestions, 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="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>
         </message>
 
+        <!-- Sea Pen Templates -->
+        <message name="IDS_SEA_PEN_TEMPLATE_TITLE_FLOWER" desc="Title of the image generation template IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          Airbrushed
+        </message>
+        <message name="IDS_SEA_PEN_TEMPLATE_FLOWER" desc="Template used to generate an image of a flower. Users can select the variables from a drop-down menu.">
+          A radiant <ph name="FLOWER_COLOR">$1<ex>pink</ex></ph> <ph name="FLOWER_TYPE">$2<ex>garden rose</ex></ph> in bloom
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_TYPE_ROSE" desc="Option to fill in the FLOWER_TYPE placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          garden rose
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_TYPE_CALLA_LILY" desc="Option to fill in the FLOWER_TYPE placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          calla lily
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_TYPE_WINDFLOWER" desc="Option to fill in the FLOWER_TYPE placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          windflower
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_TYPE_TULIP" desc="Option to fill in the FLOWER_TYPE placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          tulip
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_TYPE_LILY_OF_THE_VALLEY" desc="Option to fill in the FLOWER_TYPE placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          lily of the valley
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_TYPE_BIRD_OF_PARADISE" desc="Option to fill in the FLOWER_TYPE placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          bird of paradise
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_TYPE_ORCHID" desc="Option to fill in the FLOWER_TYPE placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          orchid
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_TYPE_RANUNCULUS" desc="Option to fill in the FLOWER_TYPE placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          ranunculus
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_TYPE_DAISY" desc="Option to fill in the FLOWER_TYPE placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          daisy
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_TYPE_HYDRANGEA" desc="Option to fill in the FLOWER_TYPE placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          hydrangea
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_COLOR_PINK" desc="Option to fill in the FLOWER_COLOR placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          pink
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_COLOR_PURPLE" desc="Option to fill in the FLOWER_COLOR placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          light purple
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_COLOR_BLUE" desc="Option to fill in the FLOWER_COLOR placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          light blue
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_COLOR_WHITE" desc="Option to fill in the FLOWER_COLOR placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          white
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_COLOR_CORAL" desc="Option to fill in the FLOWER_COLOR placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          coral
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_COLOR_YELLOW" desc="Option to fill in the FLOWER_COLOR placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          pastel yellow
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_COLOR_GREEN" desc="Option to fill in the FLOWER_COLOR placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          light green
+        </message>
+        <message name="IDS_SEA_PEN_OPTION_FLOWER_COLOR_RED" desc="Option to fill in the FLOWER_COLOR placeholder for IDS_SEA_PEN_TEMPLATE_FLOWER.">
+          dark red
+        </message>
+
         <!-- Traffic Counters UI -->
         <message name="IDS_TRAFFIC_COUNTERS_UNKNOWN" desc="Traffic counters related to an unknown source">
           Unknown
@@ -4022,6 +4084,9 @@
         <message name="IDS_FIRMWARE_CONFIRMATION_DISCLAIMER_TEXT" desc="Information text to users that the chosen update comes from the device manufacturer and hasn't been verified by Google." translateable="false">
           This update is provided by the external device manufacturer and hasn't been verified by Google.
         </message>
+        <message name="IDS_FIRMWARE_CONFIRMATION_DISCLAIMER_ICON_ARIA_LABEL" desc="The alt-text read by the screen reader when the warning disclaimer is selected." translateable="false">
+          Warning
+        </message>
         <message name="IDS_FIRMWARE_REQUEST_ID_REMOVE_REPLUG" desc="Request shown to the user during an update, requesting that the device USB cable be unplugged and reinserted." translateable="false">
           The update will continue when the device USB cable has been unplugged and then re-inserted.
         </message>
@@ -4871,6 +4936,12 @@
         <message name="IDS_INPUT_OVERLAY_EDITING_LIST_ITEM_BUTTON_A11Y_PLURAL_TEMPLATE" 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">
           Selected key 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">
+          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">
+          Delete <ph name="ACTION_NAME">$1<ex>Button w</ex></ph>
+        </message>
         <!-- End of Arc Input Overlay -->
       </if>
 
diff --git a/chromeos/chromeos_strings_grd/IDS_INPUT_OVERLAY_SHORTCUT_DELETE_A11Y_LABEL_TEMPLATE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_INPUT_OVERLAY_SHORTCUT_DELETE_A11Y_LABEL_TEMPLATE.png.sha1
new file mode 100644
index 0000000..586afac
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_INPUT_OVERLAY_SHORTCUT_DELETE_A11Y_LABEL_TEMPLATE.png.sha1
@@ -0,0 +1 @@
+a32286115c5bae9084274f13d662b8af78d3aad3
diff --git a/chromeos/chromeos_strings_grd/IDS_INPUT_OVERLAY_SHORTCUT_EDIT_A11Y_LABEL_TEMPLATE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_INPUT_OVERLAY_SHORTCUT_EDIT_A11Y_LABEL_TEMPLATE.png.sha1
new file mode 100644
index 0000000..3868225
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_INPUT_OVERLAY_SHORTCUT_EDIT_A11Y_LABEL_TEMPLATE.png.sha1
@@ -0,0 +1 @@
+114050480fbc65c37e9dd9d74d5e9672855a5fb8
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_BLUE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_BLUE.png.sha1
new file mode 100644
index 0000000..212d807
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_BLUE.png.sha1
@@ -0,0 +1 @@
+c0256a89b723f165a17352b5d4948592b597815e
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_CORAL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_CORAL.png.sha1
new file mode 100644
index 0000000..212d807
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_CORAL.png.sha1
@@ -0,0 +1 @@
+c0256a89b723f165a17352b5d4948592b597815e
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_GREEN.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_GREEN.png.sha1
new file mode 100644
index 0000000..212d807
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_GREEN.png.sha1
@@ -0,0 +1 @@
+c0256a89b723f165a17352b5d4948592b597815e
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_PINK.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_PINK.png.sha1
new file mode 100644
index 0000000..212d807
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_PINK.png.sha1
@@ -0,0 +1 @@
+c0256a89b723f165a17352b5d4948592b597815e
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_PURPLE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_PURPLE.png.sha1
new file mode 100644
index 0000000..212d807
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_PURPLE.png.sha1
@@ -0,0 +1 @@
+c0256a89b723f165a17352b5d4948592b597815e
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_RED.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_RED.png.sha1
new file mode 100644
index 0000000..212d807
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_RED.png.sha1
@@ -0,0 +1 @@
+c0256a89b723f165a17352b5d4948592b597815e
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_WHITE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_WHITE.png.sha1
new file mode 100644
index 0000000..212d807
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_WHITE.png.sha1
@@ -0,0 +1 @@
+c0256a89b723f165a17352b5d4948592b597815e
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_YELLOW.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_YELLOW.png.sha1
new file mode 100644
index 0000000..212d807
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_COLOR_YELLOW.png.sha1
@@ -0,0 +1 @@
+c0256a89b723f165a17352b5d4948592b597815e
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_BIRD_OF_PARADISE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_BIRD_OF_PARADISE.png.sha1
new file mode 100644
index 0000000..fa2e2136
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_BIRD_OF_PARADISE.png.sha1
@@ -0,0 +1 @@
+766f9cb74b3450c23b63f18810bacbec93355b09
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_CALLA_LILY.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_CALLA_LILY.png.sha1
new file mode 100644
index 0000000..fa2e2136
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_CALLA_LILY.png.sha1
@@ -0,0 +1 @@
+766f9cb74b3450c23b63f18810bacbec93355b09
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_DAISY.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_DAISY.png.sha1
new file mode 100644
index 0000000..fa2e2136
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_DAISY.png.sha1
@@ -0,0 +1 @@
+766f9cb74b3450c23b63f18810bacbec93355b09
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_HYDRANGEA.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_HYDRANGEA.png.sha1
new file mode 100644
index 0000000..fa2e2136
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_HYDRANGEA.png.sha1
@@ -0,0 +1 @@
+766f9cb74b3450c23b63f18810bacbec93355b09
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_LILY_OF_THE_VALLEY.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_LILY_OF_THE_VALLEY.png.sha1
new file mode 100644
index 0000000..fa2e2136
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_LILY_OF_THE_VALLEY.png.sha1
@@ -0,0 +1 @@
+766f9cb74b3450c23b63f18810bacbec93355b09
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_ORCHID.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_ORCHID.png.sha1
new file mode 100644
index 0000000..fa2e2136
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_ORCHID.png.sha1
@@ -0,0 +1 @@
+766f9cb74b3450c23b63f18810bacbec93355b09
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_RANUNCULUS.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_RANUNCULUS.png.sha1
new file mode 100644
index 0000000..fa2e2136
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_RANUNCULUS.png.sha1
@@ -0,0 +1 @@
+766f9cb74b3450c23b63f18810bacbec93355b09
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_ROSE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_ROSE.png.sha1
new file mode 100644
index 0000000..fa2e2136
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_ROSE.png.sha1
@@ -0,0 +1 @@
+766f9cb74b3450c23b63f18810bacbec93355b09
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_TULIP.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_TULIP.png.sha1
new file mode 100644
index 0000000..fa2e2136
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_TULIP.png.sha1
@@ -0,0 +1 @@
+766f9cb74b3450c23b63f18810bacbec93355b09
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_WINDFLOWER.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_WINDFLOWER.png.sha1
new file mode 100644
index 0000000..fa2e2136
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_OPTION_FLOWER_TYPE_WINDFLOWER.png.sha1
@@ -0,0 +1 @@
+766f9cb74b3450c23b63f18810bacbec93355b09
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_TEMPLATE_FLOWER.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_TEMPLATE_FLOWER.png.sha1
new file mode 100644
index 0000000..ed603af
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_TEMPLATE_FLOWER.png.sha1
@@ -0,0 +1 @@
+71133f1f2a6593ac56b6c19249c27b11c80f0ba9
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SEA_PEN_TEMPLATE_TITLE_FLOWER.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_TEMPLATE_TITLE_FLOWER.png.sha1
new file mode 100644
index 0000000..7cfeddc
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SEA_PEN_TEMPLATE_TITLE_FLOWER.png.sha1
@@ -0,0 +1 @@
+6494e235d752fd83a94c9554ad83726ac1eda620
diff --git a/chromeos/ui/frame/non_client_frame_view_base.cc b/chromeos/ui/frame/non_client_frame_view_base.cc
index a495d55..d0af7146 100644
--- a/chromeos/ui/frame/non_client_frame_view_base.cc
+++ b/chromeos/ui/frame/non_client_frame_view_base.cc
@@ -220,4 +220,7 @@
   frame_->non_client_view()->DeprecatedLayoutImmediately();
 }
 
+BEGIN_METADATA(NonClientFrameViewBase)
+END_METADATA
+
 }  // namespace chromeos
diff --git a/chromeos/ui/frame/non_client_frame_view_base.h b/chromeos/ui/frame/non_client_frame_view_base.h
index 3dfe64f..edaaae953 100644
--- a/chromeos/ui/frame/non_client_frame_view_base.h
+++ b/chromeos/ui/frame/non_client_frame_view_base.h
@@ -19,6 +19,8 @@
 namespace chromeos {
 
 class NonClientFrameViewBase : public views::NonClientFrameView {
+  METADATA_HEADER(NonClientFrameViewBase, views::NonClientFrameView)
+
  public:
   explicit NonClientFrameViewBase(views::Widget* frame);
   NonClientFrameViewBase(const NonClientFrameViewBase&) = delete;
@@ -80,8 +82,9 @@
 // when painting the HeaderView to its own layer.
 class NonClientFrameViewBase::OverlayView : public views::View,
                                             public views::ViewTargeterDelegate {
+  METADATA_HEADER(OverlayView, views::View)
+
  public:
-  METADATA_HEADER(OverlayView);
   explicit OverlayView(HeaderView* header_view);
   OverlayView(const OverlayView&) = delete;
   OverlayView& operator=(const OverlayView&) = delete;
diff --git a/clank b/clank
index 21c15c8..f1d1538 160000
--- a/clank
+++ b/clank
@@ -1 +1 @@
-Subproject commit 21c15c8a9fe3638c84faf6515795d2da19042214
+Subproject commit f1d153819784da755404355b06bf9d91d1789b71
diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc
index 248c9937..1197d51 100644
--- a/components/autofill/content/renderer/password_autofill_agent.cc
+++ b/components/autofill/content/renderer/password_autofill_agent.cc
@@ -1167,7 +1167,9 @@
   if (!element.IsPasswordFieldForAutofill()) {
     std::u16string username_prefix;
     if (!ShouldShowFullSuggestionListForPasswordManager(trigger_source,
-                                                        element)) {
+                                                        element) &&
+        !base::FeatureList::IsEnabled(
+            password_manager::features::kNoPasswordSuggestionFiltering)) {
       if (!password_info ||
           !CanShowUsernameSuggestion(password_info->fill_data,
                                      element.Value().Utf16())) {
diff --git a/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.cc b/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.cc
index 796bac0..33266cf0 100644
--- a/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.cc
+++ b/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.cc
@@ -390,7 +390,6 @@
   DCHECK(io_task_runner_->BelongsToCurrentThread());
   if (!db_) {
     db_ = std::make_unique<sql::Database>(sql::DatabaseOptions{
-        .exclusive_locking = true,
         // The entry size should be between 11 and 10 + x bytes, where x is the
         // the length of the host name string in bytes.
         // The total number of entries per host is bounded at 32, and the total
diff --git a/components/compose/core/browser/compose_metrics.cc b/components/compose/core/browser/compose_metrics.cc
index bbe18f7..a0e147b 100644
--- a/components/compose/core/browser/compose_metrics.cc
+++ b/components/compose/core/browser/compose_metrics.cc
@@ -428,4 +428,12 @@
   }
 }
 
+void LogComposeRequestFeedback(EvalLocation eval_location,
+                               ComposeRequestFeedback feedback) {
+  base::UmaHistogramEnumeration(
+      base::StrCat(
+          {"Compose.", EvalLocationString(eval_location), ".Request.Feedback"}),
+      feedback);
+}
+
 }  // namespace compose
diff --git a/components/compose/core/browser/compose_metrics.h b/components/compose/core/browser/compose_metrics.h
index 63a34181..114f2e0 100644
--- a/components/compose/core/browser/compose_metrics.h
+++ b/components/compose/core/browser/compose_metrics.h
@@ -165,6 +165,19 @@
   kMaxValue = kMixed,
 };
 
+// Enum for recording the feedback state of a Compose request.
+// These values are persisted to logs. Entries should not be renumbered and
+// numeric values should never be reused. Keep in sync with
+// ComposeRequestFeedback in
+// src/tools/metrics/histograms/metadata/compose/enums.xml.
+enum class ComposeRequestFeedback {
+  kNoFeedback = 0,
+  kPositiveFeedback = 1,
+  kNegativeFeedback = 2,
+  kRequestError = 3,
+  kMaxValue = kRequestError,
+};
+
 // Struct containing event and logging information for an individual
 // |ComposeSession|.
 struct ComposeSessionEvents {
@@ -345,6 +358,9 @@
 void LogComposeSessionDuration(base::TimeDelta session_duration,
                                std::string session_suffix = "");
 
+void LogComposeRequestFeedback(EvalLocation eval_location,
+                               ComposeRequestFeedback feedback);
+
 }  // namespace compose
 
 #endif  // COMPONENTS_COMPOSE_CORE_BROWSER_COMPOSE_METRICS_H_
diff --git a/components/enterprise/data_controls/verdict.cc b/components/enterprise/data_controls/verdict.cc
index 6fc8476..26d9d39 100644
--- a/components/enterprise/data_controls/verdict.cc
+++ b/components/enterprise/data_controls/verdict.cc
@@ -8,6 +8,29 @@
 
 namespace data_controls {
 
+namespace {
+
+base::OnceClosure MergedReportClosure(
+    base::OnceClosure source_report_closure,
+    base::OnceClosure destination_report_closure) {
+  if (source_report_closure) {
+    if (destination_report_closure) {
+      return base::BindOnce(
+          [](base::OnceClosure source_report_closure,
+             base::OnceClosure destination_report_closure) {
+            std::move(source_report_closure).Run();
+            std::move(destination_report_closure).Run();
+          },
+          std::move(source_report_closure),
+          std::move(destination_report_closure));
+    }
+    return source_report_closure;
+  }
+  return destination_report_closure;
+}
+
+}  // namespace
+
 // static
 Verdict Verdict::NotSet() {
   return Verdict(Rule::Level::kNotSet);
@@ -35,16 +58,29 @@
   return Verdict(Rule::Level::kAllow);
 }
 
+// static
+Verdict Verdict::Merge(Verdict source_profile_verdict,
+                       Verdict destination_profile_verdict) {
+  Rule::Level level = source_profile_verdict.level();
+  if (destination_profile_verdict.level() > level) {
+    level = destination_profile_verdict.level();
+  }
+
+  return Verdict(level,
+                 MergedReportClosure(
+                     source_profile_verdict.TakeInitialReportClosure(),
+                     destination_profile_verdict.TakeInitialReportClosure()),
+                 MergedReportClosure(
+                     source_profile_verdict.TakeBypassReportClosure(),
+                     destination_profile_verdict.TakeBypassReportClosure()));
+}
+
 Verdict::Verdict(Rule::Level level,
                  base::OnceClosure initial_report_closure,
                  base::OnceClosure bypass_report_closure)
     : level_(level),
       initial_report_closure_(std::move(initial_report_closure)),
-      bypass_report_closure_(std::move(bypass_report_closure)) {
-  DCHECK_EQ(level == Rule::Level::kNotSet || level == Rule::Level::kAllow,
-            initial_report_closure_.is_null());
-  DCHECK_EQ(level == Rule::Level::kWarn, !bypass_report_closure_.is_null());
-}
+      bypass_report_closure_(std::move(bypass_report_closure)) {}
 
 Verdict::~Verdict() = default;
 Verdict::Verdict(Verdict&& other) = default;
diff --git a/components/enterprise/data_controls/verdict.h b/components/enterprise/data_controls/verdict.h
index a05a3815..e42b2faf 100644
--- a/components/enterprise/data_controls/verdict.h
+++ b/components/enterprise/data_controls/verdict.h
@@ -24,6 +24,12 @@
   static Verdict Block(base::OnceClosure initial_report_closure);
   static Verdict Allow();
 
+  // When an action has two distinct verdicts due to involving two different
+  // profiles, this helper can be used to simplify the logic to apply to the
+  // action.
+  static Verdict Merge(Verdict source_profile_verdict,
+                       Verdict destination_profile_verdict);
+
   ~Verdict();
   Verdict(Verdict&&);
   Verdict& operator=(Verdict&&);
diff --git a/components/enterprise/data_controls/verdict_unittest.cc b/components/enterprise/data_controls/verdict_unittest.cc
index 9bb92a5..db108ccf 100644
--- a/components/enterprise/data_controls/verdict_unittest.cc
+++ b/components/enterprise/data_controls/verdict_unittest.cc
@@ -4,24 +4,86 @@
 
 #include "components/enterprise/data_controls/verdict.h"
 
+#include <vector>
+
 #include "base/functional/callback_helpers.h"
 #include "base/test/test_future.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace data_controls {
 
+namespace {
+
+// Helpers to make the tests more concise.
+Verdict NotSet() {
+  return Verdict::NotSet();
+}
+Verdict Report() {
+  return Verdict::Report(base::DoNothing());
+}
+Verdict Warn() {
+  return Verdict::Warn(base::DoNothing(), base::DoNothing());
+}
+Verdict Block() {
+  return Verdict::Block(base::DoNothing());
+}
+Verdict Allow() {
+  return Verdict::Allow();
+}
+
+}  // namespace
+
 TEST(DataControlVerdictTest, Level) {
-  ASSERT_EQ(Verdict::NotSet().level(), Rule::Level::kNotSet);
-  ASSERT_EQ(Verdict::Report(base::DoNothing()).level(), Rule::Level::kReport);
-  ASSERT_EQ(Verdict::Warn(base::DoNothing(), base::DoNothing()).level(),
-            Rule::Level::kWarn);
-  ASSERT_EQ(Verdict::Block(base::DoNothing()).level(), Rule::Level::kBlock);
-  ASSERT_EQ(Verdict::Allow().level(), Rule::Level::kAllow);
+  ASSERT_EQ(NotSet().level(), Rule::Level::kNotSet);
+  ASSERT_EQ(Report().level(), Rule::Level::kReport);
+  ASSERT_EQ(Warn().level(), Rule::Level::kWarn);
+  ASSERT_EQ(Block().level(), Rule::Level::kBlock);
+  ASSERT_EQ(Allow().level(), Rule::Level::kAllow);
+}
+
+TEST(DataControlVerdictTest, MergedLevel_NotSet) {
+  ASSERT_EQ(Verdict::Merge(NotSet(), NotSet()).level(), Rule::Level::kNotSet);
+  ASSERT_EQ(Verdict::Merge(NotSet(), Report()).level(), Rule::Level::kReport);
+  ASSERT_EQ(Verdict::Merge(NotSet(), Warn()).level(), Rule::Level::kWarn);
+  ASSERT_EQ(Verdict::Merge(NotSet(), Block()).level(), Rule::Level::kBlock);
+  ASSERT_EQ(Verdict::Merge(NotSet(), Allow()).level(), Rule::Level::kAllow);
+}
+
+TEST(DataControlVerdictTest, MergedLevel_Report) {
+  ASSERT_EQ(Verdict::Merge(Report(), NotSet()).level(), Rule::Level::kReport);
+  ASSERT_EQ(Verdict::Merge(Report(), Report()).level(), Rule::Level::kReport);
+  ASSERT_EQ(Verdict::Merge(Report(), Warn()).level(), Rule::Level::kWarn);
+  ASSERT_EQ(Verdict::Merge(Report(), Block()).level(), Rule::Level::kBlock);
+  ASSERT_EQ(Verdict::Merge(Report(), Allow()).level(), Rule::Level::kAllow);
+}
+
+TEST(DataControlVerdictTest, MergedLevel_Warn) {
+  ASSERT_EQ(Verdict::Merge(Warn(), NotSet()).level(), Rule::Level::kWarn);
+  ASSERT_EQ(Verdict::Merge(Warn(), Report()).level(), Rule::Level::kWarn);
+  ASSERT_EQ(Verdict::Merge(Warn(), Warn()).level(), Rule::Level::kWarn);
+  ASSERT_EQ(Verdict::Merge(Warn(), Block()).level(), Rule::Level::kBlock);
+  ASSERT_EQ(Verdict::Merge(Warn(), Allow()).level(), Rule::Level::kAllow);
+}
+
+TEST(DataControlVerdictTest, MergedLevel_Block) {
+  ASSERT_EQ(Verdict::Merge(Block(), NotSet()).level(), Rule::Level::kBlock);
+  ASSERT_EQ(Verdict::Merge(Block(), Report()).level(), Rule::Level::kBlock);
+  ASSERT_EQ(Verdict::Merge(Block(), Warn()).level(), Rule::Level::kBlock);
+  ASSERT_EQ(Verdict::Merge(Block(), Block()).level(), Rule::Level::kBlock);
+  ASSERT_EQ(Verdict::Merge(Block(), Allow()).level(), Rule::Level::kAllow);
+}
+
+TEST(DataControlVerdictTest, MergedLevel_Allow) {
+  ASSERT_EQ(Verdict::Merge(Allow(), NotSet()).level(), Rule::Level::kAllow);
+  ASSERT_EQ(Verdict::Merge(Allow(), Report()).level(), Rule::Level::kAllow);
+  ASSERT_EQ(Verdict::Merge(Allow(), Warn()).level(), Rule::Level::kAllow);
+  ASSERT_EQ(Verdict::Merge(Allow(), Block()).level(), Rule::Level::kAllow);
+  ASSERT_EQ(Verdict::Merge(Allow(), Allow()).level(), Rule::Level::kAllow);
 }
 
 TEST(DataControlVerdictTest, InitialReport) {
-  ASSERT_TRUE(Verdict::NotSet().TakeInitialReportClosure().is_null());
-  ASSERT_TRUE(Verdict::Allow().TakeInitialReportClosure().is_null());
+  ASSERT_TRUE(NotSet().TakeInitialReportClosure().is_null());
+  ASSERT_TRUE(Allow().TakeInitialReportClosure().is_null());
 
   base::test::TestFuture<void> report_future;
   auto report = Verdict::Report(report_future.GetCallback());
@@ -46,12 +108,10 @@
 }
 
 TEST(DataControlVerdictTest, BypassReport) {
-  ASSERT_TRUE(Verdict::NotSet().TakeBypassReportClosure().is_null());
-  ASSERT_TRUE(
-      Verdict::Block(base::DoNothing()).TakeBypassReportClosure().is_null());
-  ASSERT_TRUE(Verdict::Allow().TakeBypassReportClosure().is_null());
-  ASSERT_TRUE(
-      Verdict::Report(base::DoNothing()).TakeBypassReportClosure().is_null());
+  ASSERT_TRUE(NotSet().TakeBypassReportClosure().is_null());
+  ASSERT_TRUE(Block().TakeBypassReportClosure().is_null());
+  ASSERT_TRUE(Allow().TakeBypassReportClosure().is_null());
+  ASSERT_TRUE(Report().TakeBypassReportClosure().is_null());
 
   base::test::TestFuture<void> warn_future;
   auto warn = Verdict::Warn(base::DoNothing(), warn_future.GetCallback());
@@ -61,4 +121,32 @@
   ASSERT_TRUE(warn_future.Wait());
 }
 
+TEST(DataControlVerdictTest, MergedCallbacks) {
+  base::test::TestFuture<void> source_initial_report_future;
+  base::test::TestFuture<void> source_bypass_report_future;
+  auto source_verdict =
+      Verdict::Warn(source_initial_report_future.GetCallback(),
+                    source_bypass_report_future.GetCallback());
+
+  base::test::TestFuture<void> destination_initial_report_future;
+  base::test::TestFuture<void> destination_bypass_report_future;
+  auto destination_verdict =
+      Verdict::Warn(destination_initial_report_future.GetCallback(),
+                    destination_bypass_report_future.GetCallback());
+
+  auto merged_verdict =
+      Verdict::Merge(std::move(source_verdict), std::move(destination_verdict));
+  auto merged_initial_report = merged_verdict.TakeInitialReportClosure();
+  ASSERT_TRUE(merged_initial_report);
+  std::move(merged_initial_report).Run();
+  ASSERT_TRUE(source_initial_report_future.Wait());
+  ASSERT_TRUE(destination_initial_report_future.Wait());
+
+  auto merged_bypass_report = merged_verdict.TakeBypassReportClosure();
+  ASSERT_TRUE(merged_bypass_report);
+  std::move(merged_bypass_report).Run();
+  ASSERT_TRUE(source_bypass_report_future.Wait());
+  ASSERT_TRUE(destination_bypass_report_future.Wait());
+}
+
 }  // namespace data_controls
diff --git a/components/offline_pages/core/background/request_queue_store.cc b/components/offline_pages/core/background/request_queue_store.cc
index 71dd67c..ba54e33 100644
--- a/components/offline_pages/core/background/request_queue_store.cc
+++ b/components/offline_pages/core/background/request_queue_store.cc
@@ -587,8 +587,8 @@
 
 void RequestQueueStore::Initialize(InitializeCallback callback) {
   DCHECK(!db_);
-  db_ = std::make_unique<sql::Database>(sql::DatabaseOptions{
-      .exclusive_locking = true, .page_size = 4096, .cache_size = 500});
+  db_ = std::make_unique<sql::Database>(
+      sql::DatabaseOptions{.page_size = 4096, .cache_size = 500});
 
   background_task_runner_->PostTaskAndReplyWithResult(
       FROM_HERE, base::BindOnce(&InitDatabaseSync, db_.get(), db_file_path_),
diff --git a/components/offline_pages/task/sql_store_base.cc b/components/offline_pages/task/sql_store_base.cc
index ad832b94..3861730 100644
--- a/components/offline_pages/task/sql_store_base.cc
+++ b/components/offline_pages/task/sql_store_base.cc
@@ -100,7 +100,6 @@
   // This is how we reset a pointer and provide deleter. This is necessary to
   // ensure that we can close the store more than once.
   db_ = DatabaseUniquePtr(new sql::Database({// These values are default.
-                                             .exclusive_locking = true,
                                              .page_size = 4096,
                                              .cache_size = 500}),
                           base::OnTaskRunnerDeleter(background_task_runner_));
diff --git a/components/optimization_guide/core/model_execution/model_execution_features_controller_unittest.cc b/components/optimization_guide/core/model_execution/model_execution_features_controller_unittest.cc
index 47ba6be..fc1c6ea35 100644
--- a/components/optimization_guide/core/model_execution/model_execution_features_controller_unittest.cc
+++ b/components/optimization_guide/core/model_execution/model_execution_features_controller_unittest.cc
@@ -189,4 +189,56 @@
       proto::ModelExecutionFeature::MODEL_EXECUTION_FEATURE_TAB_ORGANIZATION));
 }
 
+TEST_F(ModelExecutionFeaturesControllerTest,
+       MainToggleEnablesAllVisibleFeatures) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatures(
+      {features::internal::kComposeSettingsVisibility,
+       features::internal::kTabOrganizationSettingsVisibility},
+      {});
+  CreateModelExecutionFeaturesController();
+  EnableSignIn();
+  EXPECT_TRUE(model_execution_features_controller()->IsSettingVisible(
+      proto::ModelExecutionFeature::MODEL_EXECUTION_FEATURE_COMPOSE));
+  EXPECT_TRUE(model_execution_features_controller()->IsSettingVisible(
+      proto::ModelExecutionFeature::MODEL_EXECUTION_FEATURE_TAB_ORGANIZATION));
+  EXPECT_FALSE(model_execution_features_controller()->IsSettingVisible(
+      proto::ModelExecutionFeature::MODEL_EXECUTION_FEATURE_WALLPAPER_SEARCH));
+
+  // Enabling the main toggle enables visible features.
+  pref_service()->SetInteger(
+      prefs::kModelExecutionMainToggleSettingState,
+      static_cast<int>(optimization_guide::prefs::FeatureOptInState::kEnabled));
+  EXPECT_TRUE(
+      model_execution_features_controller()
+          ->ShouldFeatureBeCurrentlyEnabledForUser(
+              proto::ModelExecutionFeature::MODEL_EXECUTION_FEATURE_COMPOSE));
+  EXPECT_TRUE(model_execution_features_controller()
+                  ->ShouldFeatureBeCurrentlyEnabledForUser(
+                      proto::ModelExecutionFeature::
+                          MODEL_EXECUTION_FEATURE_TAB_ORGANIZATION));
+  EXPECT_FALSE(model_execution_features_controller()
+                   ->ShouldFeatureBeCurrentlyEnabledForUser(
+                       proto::ModelExecutionFeature::
+                           MODEL_EXECUTION_FEATURE_WALLPAPER_SEARCH));
+
+  // Disabling the main toggle disables all features.
+  pref_service()->SetInteger(
+      prefs::kModelExecutionMainToggleSettingState,
+      static_cast<int>(
+          optimization_guide::prefs::FeatureOptInState::kDisabled));
+  EXPECT_FALSE(
+      model_execution_features_controller()
+          ->ShouldFeatureBeCurrentlyEnabledForUser(
+              proto::ModelExecutionFeature::MODEL_EXECUTION_FEATURE_COMPOSE));
+  EXPECT_FALSE(model_execution_features_controller()
+                   ->ShouldFeatureBeCurrentlyEnabledForUser(
+                       proto::ModelExecutionFeature::
+                           MODEL_EXECUTION_FEATURE_TAB_ORGANIZATION));
+  EXPECT_FALSE(model_execution_features_controller()
+                   ->ShouldFeatureBeCurrentlyEnabledForUser(
+                       proto::ModelExecutionFeature::
+                           MODEL_EXECUTION_FEATURE_WALLPAPER_SEARCH));
+}
+
 }  // namespace optimization_guide
diff --git a/components/optimization_guide/internal b/components/optimization_guide/internal
index ac7f78f..a1431631 160000
--- a/components/optimization_guide/internal
+++ b/components/optimization_guide/internal
@@ -1 +1 @@
-Subproject commit ac7f78fc61731de5281b728b345dbf1bf95f0318
+Subproject commit a1431631ca51a2ed211beb9b2438e260fed5143d
diff --git a/components/password_manager/core/browser/leak_detection_delegate.cc b/components/password_manager/core/browser/leak_detection_delegate.cc
index 054f62a..2285209 100644
--- a/components/password_manager/core/browser/leak_detection_delegate.cc
+++ b/components/password_manager/core/browser/leak_detection_delegate.cc
@@ -17,6 +17,7 @@
 #include "components/password_manager/core/browser/password_manager_client.h"
 #include "components/password_manager/core/browser/password_manager_util.h"
 #include "components/password_manager/core/browser/password_store/password_store_interface.h"
+#include "components/password_manager/core/browser/password_sync_util.h"
 #include "components/password_manager/core/common/password_manager_features.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/pref_service.h"
@@ -115,15 +116,16 @@
   IsSyncing is_syncing{false};
   bool in_account_store = (in_stores & PasswordForm::Store::kAccountStore) ==
                           PasswordForm::Store::kAccountStore;
-  switch (client_->GetPasswordSyncState()) {
-    case SyncState::kNotSyncing:
+  const syncer::SyncService* sync_service = client_->GetSyncService();
+  switch (sync_util::GetPasswordSyncState(sync_service)) {
+    case sync_util::SyncState::kNotActive:
       break;
-    case SyncState::kAccountPasswordsActiveNormalEncryption:
-    case SyncState::kAccountPasswordsActiveWithCustomPassphrase:
+    case sync_util::SyncState::kAccountPasswordsActiveNormalEncryption:
+    case sync_util::SyncState::kAccountPasswordsActiveWithCustomPassphrase:
       is_syncing = IsSyncing(in_account_store);
       break;
-    case SyncState::kSyncingWithCustomPassphrase:
-    case SyncState::kSyncingNormalEncryption:
+    case sync_util::SyncState::kSyncingWithCustomPassphrase:
+    case sync_util::SyncState::kSyncingNormalEncryption:
       is_syncing = IsSyncing(true);
       break;
   }
diff --git a/components/password_manager/core/browser/leak_detection_delegate_unittest.cc b/components/password_manager/core/browser/leak_detection_delegate_unittest.cc
index cab89f74..6a84e27 100644
--- a/components/password_manager/core/browser/leak_detection_delegate_unittest.cc
+++ b/components/password_manager/core/browser/leak_detection_delegate_unittest.cc
@@ -21,6 +21,7 @@
 #include "components/password_manager/core/browser/password_form.h"
 #include "components/password_manager/core/browser/password_store/mock_password_store_interface.h"
 #include "components/password_manager/core/browser/password_store/password_store_consumer.h"
+#include "components/password_manager/core/browser/password_sync_util.h"
 #include "components/password_manager/core/browser/stub_password_manager_client.h"
 #include "components/password_manager/core/common/password_manager_features.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
@@ -29,6 +30,7 @@
 #include "components/prefs/testing_pref_service.h"
 #include "components/safe_browsing/core/common/features.h"
 #include "components/safe_browsing/core/common/safe_browsing_prefs.h"
+#include "components/sync/test/test_sync_service.h"
 #include "components/version_info/channel.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -68,6 +70,7 @@
                const std::u16string&,
                bool in_account_store),
               (override));
+  MOCK_METHOD(const syncer::SyncService*, GetSyncService, (), (const override));
   MOCK_METHOD(PasswordStoreInterface*,
               GetAccountPasswordStore,
               (),
@@ -76,7 +79,6 @@
               GetProfilePasswordStore,
               (),
               (const override));
-  MOCK_METHOD(SyncState, GetPasswordSyncState, (), (const override));
   MOCK_METHOD(version_info::Channel, GetChannel, (), (const override));
 };
 
@@ -118,6 +120,7 @@
   MockPasswordManagerClient& client() { return client_; }
   MockLeakDetectionCheckFactory& factory() { return *mock_factory_; }
   LeakDetectionDelegate& delegate() { return delegate_; }
+  syncer::TestSyncService* sync_service() { return &test_sync_service_; }
   MockPasswordStoreInterface* profile_store() {
     return mock_profile_store_.get();
   }
@@ -181,6 +184,7 @@
   base::test::ScopedFeatureList features_;
   base::test::TaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  testing::NiceMock<syncer::TestSyncService> test_sync_service_;
   testing::NiceMock<MockPasswordManagerClient> client_;
   scoped_refptr<MockPasswordStoreInterface> mock_profile_store_ =
       base::MakeRefCounted<testing::StrictMock<MockPasswordStoreInterface>>();
@@ -354,10 +358,13 @@
   LeakDetectionDelegateInterface* delegate_interface = &delegate();
   const PasswordForm form = CreateTestForm();
 
-  ON_CALL(client(), GetPasswordSyncState)
-      .WillByDefault(Return(SyncState::kSyncingNormalEncryption));
-  EXPECT_CALL(client(), GetProfilePasswordStore())
-      .WillRepeatedly(Return(profile_store()));
+  ON_CALL(client(), GetSyncService()).WillByDefault(Return(sync_service()));
+  ON_CALL(client(), GetProfilePasswordStore())
+      .WillByDefault(Return(profile_store()));
+
+  ASSERT_EQ(sync_util::GetPasswordSyncState(sync_service()),
+            sync_util::SyncState::kSyncingNormalEncryption);
+
   ExpectPasswords({form});
   EXPECT_CALL(factory(), TryCreateLeakCheck)
       .WillOnce(
@@ -381,13 +388,17 @@
   LeakDetectionDelegateInterface* delegate_interface = &delegate();
   const PasswordForm form = CreateTestForm();
 
-  ON_CALL(client(), GetPasswordSyncState)
-      .WillByDefault(
-          Return(SyncState::kAccountPasswordsActiveNormalEncryption));
-  EXPECT_CALL(client(), GetAccountPasswordStore())
-      .WillRepeatedly(Return(account_store()));
-  EXPECT_CALL(client(), GetProfilePasswordStore())
-      .WillRepeatedly(Return(profile_store()));
+  ON_CALL(client(), GetSyncService()).WillByDefault(Return(sync_service()));
+  ON_CALL(client(), GetAccountPasswordStore())
+      .WillByDefault(Return(account_store()));
+  ON_CALL(client(), GetProfilePasswordStore())
+      .WillByDefault(Return(profile_store()));
+
+  sync_service()->GetUserSettings()->ClearInitialSyncFeatureSetupComplete();
+
+  ASSERT_EQ(sync_util::GetPasswordSyncState(sync_service()),
+            sync_util::SyncState::kAccountPasswordsActiveNormalEncryption);
+
   ExpectPasswords({form}, /*store=*/account_store());
   ExpectPasswords({}, /*store=*/profile_store());
   EXPECT_CALL(factory(), TryCreateLeakCheck)
@@ -413,13 +424,17 @@
   LeakDetectionDelegateInterface* delegate_interface = &delegate();
   const PasswordForm form = CreateTestForm();
 
-  ON_CALL(client(), GetPasswordSyncState)
-      .WillByDefault(
-          Return(SyncState::kAccountPasswordsActiveNormalEncryption));
-  EXPECT_CALL(client(), GetAccountPasswordStore())
-      .WillRepeatedly(Return(account_store()));
-  EXPECT_CALL(client(), GetProfilePasswordStore())
-      .WillRepeatedly(Return(profile_store()));
+  ON_CALL(client(), GetSyncService()).WillByDefault(Return(sync_service()));
+  ON_CALL(client(), GetAccountPasswordStore())
+      .WillByDefault(Return(account_store()));
+  ON_CALL(client(), GetProfilePasswordStore())
+      .WillByDefault(Return(profile_store()));
+
+  sync_service()->GetUserSettings()->ClearInitialSyncFeatureSetupComplete();
+
+  ASSERT_EQ(sync_util::GetPasswordSyncState(sync_service()),
+            sync_util::SyncState::kAccountPasswordsActiveNormalEncryption);
+
   ExpectPasswords({}, /*store=*/account_store());
   ExpectPasswords({form}, /*store=*/profile_store());
   EXPECT_CALL(factory(), TryCreateLeakCheck)
diff --git a/components/password_manager/core/browser/password_feature_manager_impl.cc b/components/password_manager/core/browser/password_feature_manager_impl.cc
index 65fc50ed7..ee1d935 100644
--- a/components/password_manager/core/browser/password_feature_manager_impl.cc
+++ b/components/password_manager/core/browser/password_feature_manager_impl.cc
@@ -26,12 +26,12 @@
 
 bool PasswordFeatureManagerImpl::IsGenerationEnabled() const {
   switch (password_manager::sync_util::GetPasswordSyncState(sync_service_)) {
-    case SyncState::kNotSyncing:
+    case sync_util::SyncState::kNotActive:
       return ShouldShowAccountStorageOptIn();
-    case SyncState::kSyncingWithCustomPassphrase:
-    case SyncState::kSyncingNormalEncryption:
-    case SyncState::kAccountPasswordsActiveNormalEncryption:
-    case SyncState::kAccountPasswordsActiveWithCustomPassphrase:
+    case sync_util::SyncState::kSyncingWithCustomPassphrase:
+    case sync_util::SyncState::kSyncingNormalEncryption:
+    case sync_util::SyncState::kAccountPasswordsActiveNormalEncryption:
+    case sync_util::SyncState::kAccountPasswordsActiveWithCustomPassphrase:
       return true;
   }
 }
diff --git a/components/password_manager/core/browser/password_feature_manager_impl_unittest.cc b/components/password_manager/core/browser/password_feature_manager_impl_unittest.cc
index 99ed1b48..7dd64ca 100644
--- a/components/password_manager/core/browser/password_feature_manager_impl_unittest.cc
+++ b/components/password_manager/core/browser/password_feature_manager_impl_unittest.cc
@@ -72,9 +72,9 @@
   sync_service_.GetUserSettings()->SetSelectedType(
       syncer::UserSelectableType::kPasswords, true);
 
-  ASSERT_EQ(
-      password_manager::sync_util::GetPasswordSyncState(&sync_service_),
-      password_manager::SyncState::kAccountPasswordsActiveNormalEncryption);
+  ASSERT_EQ(password_manager::sync_util::GetPasswordSyncState(&sync_service_),
+            password_manager::sync_util::SyncState::
+                kAccountPasswordsActiveNormalEncryption);
 
   EXPECT_TRUE(password_feature_manager_.IsGenerationEnabled());
 }
@@ -88,7 +88,7 @@
       syncer::UserSelectableType::kPasswords, true);
 
   ASSERT_EQ(password_manager::sync_util::GetPasswordSyncState(&sync_service_),
-            password_manager::SyncState::kSyncingNormalEncryption);
+            password_manager::sync_util::SyncState::kSyncingNormalEncryption);
 
   EXPECT_TRUE(password_feature_manager_.IsGenerationEnabled());
 }
@@ -108,7 +108,7 @@
       syncer::UserSelectableType::kPasswords, false);
 
   ASSERT_EQ(password_manager::sync_util::GetPasswordSyncState(&sync_service_),
-            password_manager::SyncState::kNotSyncing);
+            password_manager::sync_util::SyncState::kNotActive);
 
   // The user must be eligible for account storage opt in now.
   ASSERT_TRUE(password_feature_manager_.ShouldShowAccountStorageOptIn());
@@ -134,7 +134,7 @@
       /*types=*/syncer::UserSelectableTypeSet());
 
   ASSERT_EQ(password_manager::sync_util::GetPasswordSyncState(&sync_service_),
-            password_manager::SyncState::kNotSyncing);
+            password_manager::sync_util::SyncState::kNotActive);
 
   EXPECT_FALSE(password_feature_manager_.IsGenerationEnabled());
 }
@@ -154,7 +154,7 @@
       /*types=*/syncer::UserSelectableTypeSet());
 
   ASSERT_EQ(password_manager::sync_util::GetPasswordSyncState(&sync_service_),
-            password_manager::SyncState::kNotSyncing);
+            password_manager::sync_util::SyncState::kNotActive);
 
   EXPECT_FALSE(password_feature_manager_.IsGenerationEnabled());
 }
@@ -168,7 +168,7 @@
   ASSERT_EQ(sync_service_.GetTransportState(),
             syncer::SyncService::TransportState::PAUSED);
   ASSERT_EQ(password_manager::sync_util::GetPasswordSyncState(&sync_service_),
-            password_manager::SyncState::kNotSyncing);
+            password_manager::sync_util::SyncState::kNotActive);
 
   EXPECT_FALSE(password_feature_manager_.IsGenerationEnabled());
 }
diff --git a/components/password_manager/core/browser/password_form_manager_unittest.cc b/components/password_manager/core/browser/password_form_manager_unittest.cc
index d1e262b..383f10a3 100644
--- a/components/password_manager/core/browser/password_form_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_form_manager_unittest.cc
@@ -204,7 +204,6 @@
               AutofillHttpAuth,
               (const PasswordForm&, const PasswordFormManagerForUI*),
               (override));
-  MOCK_METHOD(SyncState, GetPasswordSyncState, (), (const, override));
   MOCK_METHOD(bool, IsCommittedMainFrameSecure, (), (const, override));
   MOCK_METHOD(signin::IdentityManager*, GetIdentityManager, (), (override));
   MOCK_METHOD(PrefService*, GetPrefs, (), (const, override));
diff --git a/components/password_manager/core/browser/password_manager_client.cc b/components/password_manager/core/browser/password_manager_client.cc
index 6217edc..546d7d64 100644
--- a/components/password_manager/core/browser/password_manager_client.cc
+++ b/components/password_manager/core/browser/password_manager_client.cc
@@ -82,10 +82,6 @@
 
 void PasswordManagerClient::TriggerSignIn(signin_metrics::AccessPoint) {}
 
-SyncState PasswordManagerClient::GetPasswordSyncState() const {
-  return SyncState::kNotSyncing;
-}
-
 bool PasswordManagerClient::WasLastNavigationHTTPError() const {
   return false;
 }
diff --git a/components/password_manager/core/browser/password_manager_client.h b/components/password_manager/core/browser/password_manager_client.h
index 6fccc7d..3621f64 100644
--- a/components/password_manager/core/browser/password_manager_client.h
+++ b/components/password_manager/core/browser/password_manager_client.h
@@ -91,17 +91,6 @@
 class WebAuthnCredentialsDelegate;
 struct PasswordForm;
 
-enum class SyncState {
-  kNotSyncing,
-  kSyncingNormalEncryption,
-  kSyncingWithCustomPassphrase,
-  // Sync is disabled but the user is signed in and opted in to passwords
-  // account storage.
-  kAccountPasswordsActiveNormalEncryption,
-  // Same as above but the account has a custom passphrase set.
-  kAccountPasswordsActiveWithCustomPassphrase,
-};
-
 enum class ErrorMessageFlowType { kSaveFlow, kFillFlow };
 
 #if BUILDFLAG(IS_ANDROID)
@@ -342,10 +331,6 @@
   // Returns the PasswordReuseManager associated with this instance.
   virtual PasswordReuseManager* GetPasswordReuseManager() const = 0;
 
-  // Reports whether and how passwords are synced in the embedder. The default
-  // implementation always returns kNotSyncing.
-  virtual SyncState GetPasswordSyncState() const;
-
   // Returns true if last navigation page had HTTP error i.e 5XX or 4XX
   virtual bool WasLastNavigationHTTPError() const;
 
diff --git a/components/password_manager/core/browser/password_manager_unittest.cc b/components/password_manager/core/browser/password_manager_unittest.cc
index ca1955b8..072502d9 100644
--- a/components/password_manager/core/browser/password_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_manager_unittest.cc
@@ -242,7 +242,6 @@
               (),
               (override));
   MOCK_METHOD(bool, IsNewTabPage, (), (const, override));
-  MOCK_METHOD(SyncState, GetPasswordSyncState, (), (const, override));
   MOCK_METHOD(profile_metrics::BrowserProfileType,
               GetProfileType,
               (),
diff --git a/components/password_manager/core/browser/password_sync_util.cc b/components/password_manager/core/browser/password_sync_util.cc
index c39fa07..fd1c7537 100644
--- a/components/password_manager/core/browser/password_sync_util.cc
+++ b/components/password_manager/core/browser/password_sync_util.cc
@@ -107,26 +107,21 @@
   return std::nullopt;
 }
 
-password_manager::SyncState GetPasswordSyncState(
-    const syncer::SyncService* sync_service) {
-  // TODO(crbug.com/1509058): On Android, we might want to return
-  // kAccountPasswordsActive for syncing users. Possibly rename the enum values.
+SyncState GetPasswordSyncState(const syncer::SyncService* sync_service) {
   if (!sync_service ||
       !sync_service->GetActiveDataTypes().Has(syncer::PASSWORDS)) {
-    return password_manager::SyncState::kNotSyncing;
+    return SyncState::kNotActive;
   }
 
   if (sync_service->IsSyncFeatureActive()) {
     return sync_service->GetUserSettings()->IsUsingExplicitPassphrase()
-               ? password_manager::SyncState::kSyncingWithCustomPassphrase
-               : password_manager::SyncState::kSyncingNormalEncryption;
+               ? SyncState::kSyncingWithCustomPassphrase
+               : SyncState::kSyncingNormalEncryption;
   }
 
   return sync_service->GetUserSettings()->IsUsingExplicitPassphrase()
-             ? password_manager::SyncState::
-                   kAccountPasswordsActiveWithCustomPassphrase
-             : password_manager::SyncState::
-                   kAccountPasswordsActiveNormalEncryption;
+             ? SyncState::kAccountPasswordsActiveWithCustomPassphrase
+             : SyncState::kAccountPasswordsActiveNormalEncryption;
 }
 
 }  // namespace sync_util
diff --git a/components/password_manager/core/browser/password_sync_util.h b/components/password_manager/core/browser/password_sync_util.h
index f5371af5..241482c 100644
--- a/components/password_manager/core/browser/password_sync_util.h
+++ b/components/password_manager/core/browser/password_sync_util.h
@@ -17,11 +17,21 @@
 
 namespace password_manager {
 
-enum class SyncState;
 struct PasswordForm;
 
 namespace sync_util {
 
+enum class SyncState {
+  kNotActive,
+  kSyncingNormalEncryption,
+  kSyncingWithCustomPassphrase,
+  // Sync is disabled but the user is signed in and opted in to passwords
+  // account storage.
+  kAccountPasswordsActiveNormalEncryption,
+  // Same as above but the account has a custom passphrase set.
+  kAccountPasswordsActiveWithCustomPassphrase,
+};
+
 // Uses `sync_service` to determine whether the user is signed in with
 // sync-the-feature turned on, and if so, return the e-mail representing the
 // account for which sync is on. Returns an empty string otherwise (which
@@ -77,9 +87,10 @@
     const syncer::SyncService* sync_service);
 
 // Reports whether and how passwords are currently synced. In particular, for a
-// null `sync_service` returns NOT_SYNCING.
-password_manager::SyncState GetPasswordSyncState(
-    const syncer::SyncService* sync_service);
+// null `sync_service` returns kNotActive.
+// TODO(b/1488793): Revisit this function and avoid distinguishing account
+// passwords from full sync.
+SyncState GetPasswordSyncState(const syncer::SyncService* sync_service);
 
 }  // namespace sync_util
 
diff --git a/components/password_manager/core/browser/password_sync_util_unittest.cc b/components/password_manager/core/browser/password_sync_util_unittest.cc
index 3113fb7..446fc479 100644
--- a/components/password_manager/core/browser/password_sync_util_unittest.cc
+++ b/components/password_manager/core/browser/password_sync_util_unittest.cc
@@ -91,6 +91,7 @@
   EXPECT_EQ(
       std::string(),
       GetAccountEmailIfSyncFeatureEnabledIncludingPasswords(&sync_service));
+  EXPECT_EQ(SyncState::kNotActive, GetPasswordSyncState(&sync_service));
 }
 
 TEST_F(PasswordSyncUtilTest, SyncEnabledButNotForPasswords) {
@@ -104,6 +105,7 @@
   EXPECT_EQ(
       std::string(),
       GetAccountEmailIfSyncFeatureEnabledIncludingPasswords(&sync_service));
+  EXPECT_EQ(SyncState::kNotActive, GetPasswordSyncState(&sync_service));
 }
 
 TEST_F(PasswordSyncUtilTest, SyncEnabled) {
@@ -118,6 +120,8 @@
   EXPECT_EQ(
       active_info.email,
       GetAccountEmailIfSyncFeatureEnabledIncludingPasswords(&sync_service));
+  EXPECT_EQ(SyncState::kSyncingNormalEncryption,
+            GetPasswordSyncState(&sync_service));
 }
 
 TEST_F(PasswordSyncUtilTest, SyncPaused) {
@@ -131,6 +135,57 @@
   EXPECT_NE(
       std::string(),
       GetAccountEmailIfSyncFeatureEnabledIncludingPasswords(&sync_service));
+  EXPECT_EQ(SyncState::kNotActive, GetPasswordSyncState(&sync_service));
+}
+
+TEST_F(PasswordSyncUtilTest, SyncEnabledWithCustomPassphrase) {
+  syncer::TestSyncService sync_service;
+  sync_service.SetTransportState(syncer::SyncService::TransportState::ACTIVE);
+  sync_service.SetHasSyncConsent(true);
+  AccountInfo active_info;
+  active_info.email = "test@email.com";
+  sync_service.SetAccountInfo(active_info);
+  sync_service.SetIsUsingExplicitPassphrase(true);
+  EXPECT_TRUE(IsSyncFeatureEnabledIncludingPasswords(&sync_service));
+  EXPECT_TRUE(IsSyncFeatureActiveIncludingPasswords(&sync_service));
+  EXPECT_EQ(
+      active_info.email,
+      GetAccountEmailIfSyncFeatureEnabledIncludingPasswords(&sync_service));
+  EXPECT_EQ(SyncState::kSyncingWithCustomPassphrase,
+            GetPasswordSyncState(&sync_service));
+}
+
+TEST_F(PasswordSyncUtilTest, AccountPasswordsActive) {
+  syncer::TestSyncService sync_service;
+  sync_service.SetTransportState(syncer::SyncService::TransportState::ACTIVE);
+  sync_service.SetHasSyncConsent(false);
+  AccountInfo active_info;
+  active_info.email = "test@email.com";
+  sync_service.SetAccountInfo(active_info);
+  EXPECT_FALSE(IsSyncFeatureEnabledIncludingPasswords(&sync_service));
+  EXPECT_FALSE(IsSyncFeatureActiveIncludingPasswords(&sync_service));
+  EXPECT_EQ(
+      std::string(),
+      GetAccountEmailIfSyncFeatureEnabledIncludingPasswords(&sync_service));
+  EXPECT_EQ(SyncState::kAccountPasswordsActiveNormalEncryption,
+            GetPasswordSyncState(&sync_service));
+}
+
+TEST_F(PasswordSyncUtilTest, AccountPasswordsActiveAndCustomPassphrase) {
+  syncer::TestSyncService sync_service;
+  sync_service.SetTransportState(syncer::SyncService::TransportState::ACTIVE);
+  sync_service.SetHasSyncConsent(false);
+  AccountInfo active_info;
+  active_info.email = "test@email.com";
+  sync_service.SetAccountInfo(active_info);
+  sync_service.SetIsUsingExplicitPassphrase(true);
+  EXPECT_FALSE(IsSyncFeatureEnabledIncludingPasswords(&sync_service));
+  EXPECT_FALSE(IsSyncFeatureActiveIncludingPasswords(&sync_service));
+  EXPECT_EQ(
+      std::string(),
+      GetAccountEmailIfSyncFeatureEnabledIncludingPasswords(&sync_service));
+  EXPECT_EQ(SyncState::kAccountPasswordsActiveWithCustomPassphrase,
+            GetPasswordSyncState(&sync_service));
 }
 
 }  // namespace sync_util
diff --git a/components/password_manager/core/browser/store_metrics_reporter.cc b/components/password_manager/core/browser/store_metrics_reporter.cc
index fff8961..cea96065 100644
--- a/components/password_manager/core/browser/store_metrics_reporter.cc
+++ b/components/password_manager/core/browser/store_metrics_reporter.cc
@@ -53,15 +53,17 @@
 constexpr char kWithCustomPassphraseSuffix[] = ".WithCustomPassphrase";
 constexpr char kWithoutCustomPassphraseSuffix[] = ".WithoutCustomPassphrase";
 
-bool IsCustomPassphraseEnabled(password_manager::SyncState sync_state) {
+bool IsCustomPassphraseEnabled(
+    password_manager::sync_util::SyncState sync_state) {
   switch (sync_state) {
-    case password_manager::SyncState::kSyncingWithCustomPassphrase:
-    case password_manager::SyncState::
+    case password_manager::sync_util::SyncState::kSyncingWithCustomPassphrase:
+    case password_manager::sync_util::SyncState::
         kAccountPasswordsActiveWithCustomPassphrase:
       return true;
-    case password_manager::SyncState::kNotSyncing:
-    case password_manager::SyncState::kSyncingNormalEncryption:
-    case password_manager::SyncState::kAccountPasswordsActiveNormalEncryption:
+    case password_manager::sync_util::SyncState::kNotActive:
+    case password_manager::sync_util::SyncState::kSyncingNormalEncryption:
+    case password_manager::sync_util::SyncState::
+        kAccountPasswordsActiveNormalEncryption:
       return false;
   }
   NOTREACHED_NORETURN();
diff --git a/components/password_manager/core/browser/sync_credentials_filter.cc b/components/password_manager/core/browser/sync_credentials_filter.cc
index 242f43a..0983d68e 100644
--- a/components/password_manager/core/browser/sync_credentials_filter.cc
+++ b/components/password_manager/core/browser/sync_credentials_filter.cc
@@ -21,21 +21,19 @@
 
 namespace password_manager {
 
-SyncCredentialsFilter::SyncCredentialsFilter(
-    PasswordManagerClient* client,
-    SyncServiceFactoryFunction sync_service_factory_function)
-    : client_(client),
-      sync_service_factory_function_(std::move(sync_service_factory_function)) {
-}
+SyncCredentialsFilter::SyncCredentialsFilter(PasswordManagerClient* client)
+    : client_(client) {}
 
 SyncCredentialsFilter::~SyncCredentialsFilter() = default;
 
 bool SyncCredentialsFilter::ShouldSave(const PasswordForm& form) const {
-  if (client_->IsOffTheRecord())
+  if (client_->IsOffTheRecord()) {
     return false;
+  }
 
-  if (form.form_data.is_gaia_with_skip_save_password_form)
+  if (form.form_data.is_gaia_with_skip_save_password_form) {
     return false;
+  }
 
   if (!sync_util::IsGaiaCredentialPage(form.signon_realm)) {
     return true;
@@ -43,8 +41,7 @@
 
   // Note that `sync_service` may be null in advanced cases like --disable-sync
   // being used as per syncer::IsSyncAllowedByFlag().
-  const syncer::SyncService* sync_service =
-      sync_service_factory_function_.Run();
+  const syncer::SyncService* sync_service = client_->GetSyncService();
 
   // The requirement to fulfill is "don't offer to save a Gaia password inside
   // its own account".
diff --git a/components/password_manager/core/browser/sync_credentials_filter.h b/components/password_manager/core/browser/sync_credentials_filter.h
index f78993a9..2df2b867 100644
--- a/components/password_manager/core/browser/sync_credentials_filter.h
+++ b/components/password_manager/core/browser/sync_credentials_filter.h
@@ -8,11 +8,9 @@
 #include <memory>
 #include <string>
 
-#include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "components/password_manager/core/browser/credentials_filter.h"
 #include "components/password_manager/core/browser/password_manager_client.h"
-#include "components/sync/service/sync_service.h"
 
 namespace password_manager {
 
@@ -21,15 +19,9 @@
 // The sync- and GAIA- aware implementation of the filter.
 class SyncCredentialsFilter : public CredentialsFilter {
  public:
-  using SyncServiceFactoryFunction =
-      base::RepeatingCallback<const syncer::SyncService*(void)>;
-
   // Implements protection of sync credentials. Uses |client| to get the last
-  // commited entry URL for a check against GAIA reauth site. Uses the factory
-  // function repeatedly to get the sync service to pass to sync_util methods.
-  SyncCredentialsFilter(
-      PasswordManagerClient* client,
-      SyncServiceFactoryFunction sync_service_factory_function);
+  // committed entry URL for a check against GAIA reauth site.
+  explicit SyncCredentialsFilter(PasswordManagerClient* client);
 
   SyncCredentialsFilter(const SyncCredentialsFilter&) = delete;
   SyncCredentialsFilter& operator=(const SyncCredentialsFilter&) = delete;
@@ -45,8 +37,6 @@
 
  private:
   const raw_ptr<PasswordManagerClient> client_;
-
-  const SyncServiceFactoryFunction sync_service_factory_function_;
 };
 
 }  // namespace password_manager
diff --git a/components/password_manager/core/browser/sync_credentials_filter_unittest.cc b/components/password_manager/core/browser/sync_credentials_filter_unittest.cc
index 6ff0c2ff..4a6674c2 100644
--- a/components/password_manager/core/browser/sync_credentials_filter_unittest.cc
+++ b/components/password_manager/core/browser/sync_credentials_filter_unittest.cc
@@ -60,8 +60,9 @@
 
 class FakePasswordManagerClient : public StubPasswordManagerClient {
  public:
-  explicit FakePasswordManagerClient(signin::IdentityManager* identity_manager)
-      : identity_manager_(identity_manager) {
+  FakePasswordManagerClient(signin::IdentityManager* identity_manager,
+                            const syncer::SyncService* sync_service)
+      : identity_manager_(identity_manager), sync_service_(sync_service) {
     if (!base::FeatureList::IsEnabled(
             features::kPasswordReuseDetectionEnabled)) {
       return;
@@ -93,6 +94,9 @@
   url::Origin GetLastCommittedOrigin() const override {
     return last_committed_origin_;
   }
+  const syncer::SyncService* GetSyncService() const override {
+    return sync_service_;
+  }
   MockPasswordStoreInterface* GetProfilePasswordStore() const override {
     return password_store_.get();
   }
@@ -119,7 +123,8 @@
   MockWebAuthnCredentialsDelegate webauthn_credentials_delegate_;
   std::optional<std::vector<PasskeyCredential>> passkeys_;
   bool is_incognito_ = false;
-  raw_ptr<signin::IdentityManager> identity_manager_;
+  const raw_ptr<signin::IdentityManager> identity_manager_;
+  const raw_ptr<const syncer::SyncService> sync_service_;
   std::unique_ptr<TestingPrefServiceSimple> prefs_;
 };
 
@@ -131,7 +136,8 @@
   enum class LoginState { NEW, EXISTING };
 
   CredentialsFilterTest() {
-    client_ = std::make_unique<FakePasswordManagerClient>(identity_manager());
+    client_ = std::make_unique<FakePasswordManagerClient>(identity_manager(),
+                                                          sync_service());
     signin::IdentityManager::RegisterProfilePrefs(
         client_->GetPrefs()->registry());
     form_manager_ = std::make_unique<PasswordFormManager>(
@@ -140,9 +146,7 @@
             /*profile_form_saver=*/std::make_unique<StubFormSaver>(),
             /*account_form_saver=*/nullptr),
         nullptr /* metrics_recorder */);
-    filter_ = std::make_unique<SyncCredentialsFilter>(
-        client_.get(), base::BindRepeating(&SyncUsernameTestBase::sync_service,
-                                           base::Unretained(this)));
+    filter_ = std::make_unique<SyncCredentialsFilter>(client_.get());
 
     fetcher_.Fetch();
   }
@@ -208,10 +212,14 @@
   SetSyncingPasswords(false);
 
   // Create a new filter that uses a null SyncService.
-  filter_ = std::make_unique<SyncCredentialsFilter>(
-      client_.get(), base::BindRepeating([]() -> const syncer::SyncService* {
-        return nullptr;
-      }));
+  filter_.reset();
+  form_manager_.reset();
+  client_ =
+      std::make_unique<FakePasswordManagerClient>(identity_manager(),
+                                                  /*sync_service=*/nullptr);
+  signin::IdentityManager::RegisterProfilePrefs(
+      client_->GetPrefs()->registry());
+  filter_ = std::make_unique<SyncCredentialsFilter>(client_.get());
 
   // Non-Gaia forms should always offer saving.
   EXPECT_TRUE(filter_->ShouldSave(SimpleNonGaiaForm(kPrimaryAccountEmail)));
diff --git a/components/password_manager/core/common/password_manager_features.cc b/components/password_manager/core/common/password_manager_features.cc
index cc364afe..ddbf66d 100644
--- a/components/password_manager/core/common/password_manager_features.cc
+++ b/components/password_manager/core/common/password_manager_features.cc
@@ -85,6 +85,11 @@
              "RecoverFromNeverSaveAndroid_LAUNCHED",
              base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Removes password suggestion filtering by username.
+BASE_FEATURE(kNoPasswordSuggestionFiltering,
+             "NoPasswordSuggestionFiltering",
+             base::FEATURE_DISABLED_BY_DEFAULT);
+
 #if BUILDFLAG(IS_ANDROID)
 // Use GMS AccountSettings to manage passkeys when UPM is not available.
 BASE_FEATURE(kPasskeyManagementUsingAccountSettingsAndroid,
diff --git a/components/password_manager/core/common/password_manager_features.h b/components/password_manager/core/common/password_manager_features.h
index eb0d105..31698c72 100644
--- a/components/password_manager/core/common/password_manager_features.h
+++ b/components/password_manager/core/common/password_manager_features.h
@@ -34,6 +34,7 @@
 BASE_DECLARE_FEATURE(kPasswordChangeWellKnown);
 BASE_DECLARE_FEATURE(kPasswordReuseDetectionEnabled);
 BASE_DECLARE_FEATURE(kRecoverFromNeverSaveAndroid);
+BASE_DECLARE_FEATURE(kNoPasswordSuggestionFiltering);
 
 #if BUILDFLAG(IS_ANDROID)
 BASE_DECLARE_FEATURE(kPasskeyManagementUsingAccountSettingsAndroid);
diff --git a/components/policy/core/common/local_test_policy_loader.cc b/components/policy/core/common/local_test_policy_loader.cc
index 950f640e..edd44050 100644
--- a/components/policy/core/common/local_test_policy_loader.cc
+++ b/components/policy/core/common/local_test_policy_loader.cc
@@ -24,6 +24,7 @@
 const char kLevel[] = "level";
 const char kScope[] = "scope";
 const char kSource[] = "source";
+const char kNamespace[] = "namespace";
 const char kName[] = "name";
 const char kValue[] = "value";
 const char kLocalTestId[] = "local_test_id";
@@ -76,8 +77,13 @@
     std::string key = base::StringPrintf(
         "%i_%i_%i", *policy_dict.FindInt(kLevel), *policy_dict.FindInt(kScope),
         *policy_dict.FindInt(kSource));
-    PolicyMap& policy_map =
-        bundles[key].Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+    base::Value* component_id = policy_dict.Find(kNamespace);
+    PolicyNamespace ns =
+        (!component_id || *component_id == "chrome")
+            ? PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())
+            : PolicyNamespace(POLICY_DOMAIN_EXTENSIONS,
+                              component_id->GetString());
+    PolicyMap& policy_map = bundles[key].Get(ns);
     // Add affiliation id if user should be affiliated.
     if (is_user_affiliated_ && !policy_map.IsUserAffiliated()) {
       base::flat_set<std::string> user_ids(policy_map.GetUserAffiliationIds());
@@ -116,6 +122,9 @@
   CHECK(source_int.has_value() &&
         *source_int < static_cast<int>(PolicySource::POLICY_SOURCE_COUNT))
       << "Invalid source found";
+  if (policy_dict->Find(kNamespace)) {
+    CHECK(policy_dict->FindString(kNamespace)) << "Invalid namespace found";
+  }
   CHECK(policy_dict->FindString(kName)) << "Invalid name found";
   CHECK(policy_dict->contains(kValue)) << "Invalid value found";
 }
diff --git a/components/policy/core/common/policy_utils.cc b/components/policy/core/common/policy_utils.cc
index d962a22c..06eb582 100644
--- a/components/policy/core/common/policy_utils.cc
+++ b/components/policy/core/common/policy_utils.cc
@@ -53,7 +53,9 @@
     const policy::Schema& schema) {
   base::Value::Dict result;
   for (auto& policy_name : policy_names) {
-    switch (schema.GetKnownProperty(policy_name.GetString()).type()) {
+    base::Value::Type policy_type =
+        schema.GetKnownProperty(policy_name.GetString()).type();
+    switch (policy_type) {
       case base::Value::Type::BOOLEAN:
         result.Set(policy_name.GetString(), "boolean");
         break;
@@ -73,7 +75,8 @@
         result.Set(policy_name.GetString(), "string");
         break;
       default:
-        NOTREACHED();
+        NOTREACHED() << "Unrecognized policy type " << (int)policy_type << " ("
+                     << policy_name << ")";
         break;
     }
   }
diff --git a/components/policy/resources/webui/test/policy_test.ts b/components/policy/resources/webui/test/policy_test.ts
index 5e2b0838..ee224d73 100644
--- a/components/policy/resources/webui/test/policy_test.ts
+++ b/components/policy/resources/webui/test/policy_test.ts
@@ -4,6 +4,7 @@
 
 import './policy_test_table.js';
 
+import {addWebUiListener} from 'chrome://resources/js/cr.js';
 import {getRequiredElement} from 'chrome://resources/js/util.js';
 
 import {LevelNamesToValues, PolicyInfo, PolicyLevel, PolicyScope, PolicySource, PolicyTestBrowserProxy, ScopeNamesToValues, SourceNamesToValues} from './policy_test_browser_proxy.js';
@@ -31,6 +32,15 @@
     "profileSeparationSettings": 1,
     "profileSeparationDataMigrationSettings": 2
   }`;
+  addWebUiListener('schema-updated', onSchemaUpdated);
+  policyTestBrowserProxy.listenPoliciesUpdates();
+}
+
+// Fired from PolicyUIHandler, called when the policy schema changes e.g.
+// because an extension was installed/uninstalled.
+function onSchemaUpdated(schema: any) {
+  getRequiredElement<PolicyTestTableElement>('policy-test-table')
+      .setSchema(schema);
 }
 
 function uploadPoliciesFile() {
@@ -81,21 +91,38 @@
           // object format is used.
           const policies = JSON.parse(reader.result as string);
           if (policies.constructor === Array) {
-            // Add row for each policy.
-            policies.forEach((policy: PolicyInfo) => {
-              policyTable.addRow(policy);
+            // Exported from chrome://policy/test. Add row for each policy.
+            policies.forEach((policy: Omit<PolicyInfo, 'namespace'>) => {
+              // Old exports didn't have the 'namespace' property, and only
+              // support Chrome policies. Populate it with 'chrome' by default,
+              // for backwards compat.
+              policyTable.addRow({
+                namespace: 'chrome',
+                ...policy,
+              });
             });
           } else {
-            const policiesObj = policies['policyValues']['chrome']['policies'];
+            // Exported from chrome://policy.
+            const policiesObj = {
+              chrome: policies.policyValues.chrome.policies,
+              ...Object.fromEntries(
+                  Object
+                      .entries(
+                          policies.policyValues.extensions as
+                          Record<string, any>)
+                      .map(([extensionId,
+                             {policies}]) => [extensionId, policies])),
+            };
 
-            // Add row for each policy
-            for (const [key, value] of Object.entries(policiesObj)) {
-              if (key.startsWith('_')) {
-                continue;
+            // Add row for each policy.
+            for (const [ns, schema] of Object.entries(policiesObj)) {
+              for (const [key, value] of Object.entries(schema)) {
+                if (key.startsWith('_')) {
+                  continue;
+                }
+                policyTable.addRow(
+                    convertToPolicyInfo(ns, key, value as Record<string, any>));
               }
-
-              policyTable.addRow(
-                  convertToPolicyInfo(key, value as {[key: string]: any}));
             }
           }
 
@@ -110,8 +137,10 @@
   );
 }
 
-function convertToPolicyInfo(policyName: string, value: {[key: string]: any}) {
+function convertToPolicyInfo(
+    policyNamespace: string, policyName: string, value: {[key: string]: any}) {
   const policy: PolicyInfo = {
+    namespace: policyNamespace,
     name: policyName,
     source: Number(SourceNamesToValues[value['source']]) ??
         PolicySource.SOURCE_ENTERPRISE_DEFAULT_VAL,
@@ -193,3 +222,6 @@
 }
 
 document.addEventListener('DOMContentLoaded', initialize);
+
+addWebUiListener('schema-updated', onSchemaUpdated);
+policyTestBrowserProxy.listenPoliciesUpdates();
diff --git a/components/policy/resources/webui/test/policy_test_browser_proxy.ts b/components/policy/resources/webui/test/policy_test_browser_proxy.ts
index 73f315d3..57e22f1d 100644
--- a/components/policy/resources/webui/test/policy_test_browser_proxy.ts
+++ b/components/policy/resources/webui/test/policy_test_browser_proxy.ts
@@ -40,8 +40,21 @@
   PRESET_CLOUD_ACCOUNT,
 }
 
+export type PolicyType =
+    'boolean'|'integer'|'number'|'string'|'list'|'dictionary';
+
+export interface PolicyNamespace {
+  [policyName: string]: PolicyType;
+}
+
+export interface PolicySchema {
+  chrome: PolicyNamespace;
+  [extensionId: string]: PolicyNamespace;
+}
+
 // Object mapping policy information types to their values.
 export interface PolicyInfo {
+  namespace: string;
   name: string;
   source: PolicySource;
   scope: PolicyScope;
@@ -87,6 +100,10 @@
         'setLocalTestPolicies', policies, profileSeparationResponse);
   }
 
+  listenPoliciesUpdates() {
+    return sendWithPromise('listenPoliciesUpdates');
+  }
+
   revertTestPolicies() {
     return sendWithPromise('revertLocalTestPolicies');
   }
diff --git a/components/policy/resources/webui/test/policy_test_row.html b/components/policy/resources/webui/test/policy_test_row.html
index 11a8434..8cccd08 100644
--- a/components/policy/resources/webui/test/policy_test_row.html
+++ b/components/policy/resources/webui/test/policy_test_row.html
@@ -103,12 +103,17 @@
   }
 </style>
 <div role="cell" class="name-cell">
+  <label>$i18n{testTableNamespace}</label>
+  <select class="namespace" value="chrome">
+    <option value="chrome">Chrome</option>
+  </select>
+</div>
+<div role="cell" class="name-cell">
   <label>$i18n{testTableName}</label>
   <input class='name' list="policy-name-list" autocomplete="off"
     placeholder="$i18n{testNameSelect}">
   </input>
-  <datalist id="policy-name-list">
-</datalist>
+  <datalist id="policy-name-list"></datalist>
 </div>
 <div role="cell">
   <label>$i18n{testTableValue}</label>
@@ -183,4 +188,4 @@
 </div>
 <div role="cell" class="row-remove-btn-cell">
   <button class="remove-btn">–</button>
-</div>
\ No newline at end of file
+</div>
diff --git a/components/policy/resources/webui/test/policy_test_row.ts b/components/policy/resources/webui/test/policy_test_row.ts
index 1140b1c4..17ac7f1 100644
--- a/components/policy/resources/webui/test/policy_test_row.ts
+++ b/components/policy/resources/webui/test/policy_test_row.ts
@@ -3,19 +3,18 @@
 // found in the LICENSE file.
 import '../strings.m.js';
 
-import {assertNotReached} from 'chrome://resources/js/assert.js';
+import {assert, assertNotReached} from 'chrome://resources/js/assert.js';
 import {CustomElement} from 'chrome://resources/js/custom_element.js';
 import {EventTracker} from 'chrome://resources/js/event_tracker.js';
-import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 
-import {LevelNamesToValues, PolicyInfo, PolicyLevel, PolicyScope, PolicySource, PresetAtrributes, Presets, ScopeNamesToValues, SourceNamesToValues} from './policy_test_browser_proxy.js';
+import {LevelNamesToValues, PolicyInfo, PolicyLevel, PolicySchema, PolicyScope, PolicySource, PresetAtrributes, Presets, ScopeNamesToValues, SourceNamesToValues} from './policy_test_browser_proxy.js';
 import {getTemplate} from './policy_test_row.html.js';
 
 export class PolicyTestRowElement extends CustomElement {
   private hasAnError_: boolean = false;
   private errorEvents_: EventTracker = new EventTracker();
   private inputType_: string|number|boolean|any[]|object;
-  private policyNamesToTypes_: {[key: string]: string};
+  private schema_?: PolicySchema;
 
   static override get template() {
     return getTemplate();
@@ -23,26 +22,56 @@
 
   constructor() {
     super();
-    this.initialize_();
   }
 
   getErrorState(): boolean {
     return this.hasAnError_;
   }
 
-  // Event listener function for changing the input type when a policy name is
-  // selected.
-  private changeInputTypeEvent_(event: Event) {
-    this.changeInputType_(event.target! as HTMLInputElement);
+  setSchema(schema: PolicySchema) {
+    const hadSchema = !!this.schema_;
+    this.schema_ = schema;
+    if (hadSchema) {
+      this.updatePolicyNamespaces_();
+      this.updatePolicyNames();
+    } else {
+      this.initialize_();
+    }
   }
 
-  private changeInputType_(nameInput: HTMLInputElement) {
+  // Event listener function that clears the policy name when the namespace is
+  // changed.
+  private onPolicyNamespaceChanged_(_: Event) {
+    this.updatePolicyNames();
+    const nameInput = this.getRequiredElement<HTMLInputElement>('.name');
+    nameInput.value = '';
+    this.changeInputType_(
+        this.getRequiredElement<HTMLSelectElement>('.namespace'), nameInput,
+        false);
+  }
+
+  // Event listener function for changing the input type when a policy name is
+  // selected.
+  private onPolicyNameChanged_(_: Event) {
+    this.changeInputType_(
+        this.getRequiredElement<HTMLSelectElement>('.namespace'),
+        this.getRequiredElement<HTMLInputElement>('.name'));
+  }
+
+  private changeInputType_(
+      namespaceInput: HTMLSelectElement, nameInput: HTMLInputElement,
+      updateErrorState: boolean = true) {
+    assert(this.schema_);
+    assert(namespaceInput.value in this.schema_);
     // Return if invalid policy name
-    if (!this.isValidPolicyName_(nameInput.value)) {
-      this.setInErrorState_(nameInput);
+    if (!this.isValidPolicyName_(namespaceInput.value, nameInput.value)) {
+      if (updateErrorState) {
+        this.setInErrorState_(nameInput);
+      }
       return;
     }
-    const newValueType = this.policyNamesToTypes_[nameInput.value];
+    const ns = this.getNamespace();
+    const newValueType = this.schema_[ns]![nameInput.value];
     const inputElement = this.getRequiredElement<HTMLInputElement>('.value');
     const inputElementCell = inputElement.parentNode! as HTMLElement;
     inputElement.remove();
@@ -171,30 +200,86 @@
   }
 
   // Function that verifies policy name is a valid.
-  private isValidPolicyName_(policyName: string) {
-    if (policyName in this.policyNamesToTypes_) {
+  private isValidPolicyName_(policyNamespace: string, policyName: string) {
+    if (this.schema_ && policyNamespace in this.schema_ &&
+        policyName in this.schema_[policyNamespace]!) {
       return true;
     } else {
       return false;
     }
   }
 
-  // Function that initializes the policy selection dropdowns and delete
-  // button for the current row.
-  private initialize_() {
-    const policyNameDatalist = this.getRequiredElement('#policy-name-list');
-    const policyNameInput = this.getRequiredElement('.name');
-    policyNameInput.addEventListener(
-        'change', this.changeInputTypeEvent_.bind(this));
+  getNamespace() {
+    return this.getRequiredElement<HTMLSelectElement>('.namespace').value;
+  }
 
-    // Populate the policy name dropdown with all policy names.
-    this.policyNamesToTypes_ =
-        JSON.parse(loadTimeData.getString('policyNamesToTypes'));
-    for (const name in this.policyNamesToTypes_) {
+  // Updates the policy namespaces <select> to match extensions from the schema.
+  private updatePolicyNamespaces_() {
+    assert(this.schema_);
+    const policyNamespaceInput =
+        this.getRequiredElement<HTMLSelectElement>('.namespace');
+    assert(policyNamespaceInput.value in this.schema_);
+    // Clear old values.
+    while (policyNamespaceInput.firstChild) {
+      policyNamespaceInput.removeChild(policyNamespaceInput.firstChild);
+    }
+    // Populate with extension IDs, keep sorted. 'Chrome' is always the first
+    // element for browser policies.
+    const sortedNamespacesWithChromeFirst =
+        Object.keys(this.schema_).toSorted((a, b) => {
+          if (a === 'chrome') {
+            return -1;
+          }
+          if (b === 'chrome') {
+            return 1;
+          }
+          return a < b ? -1 : (b < a ? 1 : 0);
+        });
+    for (const ns of sortedNamespacesWithChromeFirst) {
+      const currOption = document.createElement('option');
+      currOption.textContent = ns === 'chrome' ? 'Chrome' : ns;
+      currOption.value = ns;
+      policyNamespaceInput.appendChild(currOption);
+    }
+  }
+
+  // Updates the policy names <datalist> to match the current namespace.
+  updatePolicyNames() {
+    assert(this.schema_);
+    const ns = this.getNamespace();
+    const policyNameDatalist = this.getRequiredElement('#policy-name-list');
+    // Clear old values.
+    while (policyNameDatalist.firstChild) {
+      policyNameDatalist.removeChild(policyNameDatalist.firstChild);
+    }
+    // Populate with new values.
+    for (const name in this.schema_[ns]!) {
       const currOption = document.createElement('option');
       currOption.textContent = name;
       policyNameDatalist.appendChild(currOption);
     }
+    const policyNameInput = this.getRequiredElement<HTMLInputElement>('.name');
+    if (policyNameInput.value) {
+      this.changeInputType_(
+          this.getRequiredElement('.namespace'), policyNameInput);
+    }
+  }
+
+  // Function that initializes the policy selection dropdowns and delete
+  // button for the current row.
+  private initialize_() {
+    assert(this.schema_);
+    const policyNamespaceInput = this.getRequiredElement('.namespace');
+    policyNamespaceInput.addEventListener(
+        'change', this.onPolicyNamespaceChanged_.bind(this));
+
+    const policyNameInput = this.getRequiredElement('.name');
+    policyNameInput.addEventListener(
+        'change', this.onPolicyNameChanged_.bind(this));
+
+    // Populate the policy namespace & name dropdowns with <option>s.
+    this.updatePolicyNamespaces_();
+    this.updatePolicyNames();
 
     // Add an event listener for this row's delete button.
     this.getRequiredElement('.remove-btn')
@@ -249,6 +334,8 @@
   // Class method for setting the name, source, scope, level and value cells for
   // this row.
   setInitialValues(initialValues: PolicyInfo) {
+    const policyNamespaceInput =
+        this.getRequiredElement<HTMLSelectElement>('.namespace');
     const policyNameInput = this.getRequiredElement<HTMLInputElement>('.name');
     const policySourceInput =
         this.getRequiredElement<HTMLInputElement>('.source');
@@ -257,13 +344,16 @@
     const policyScopeInput =
         this.getRequiredElement<HTMLInputElement>('.scope');
 
+    policyNamespaceInput.value = String(initialValues.namespace);
+    this.updatePolicyNames();
+
     policySourceInput.value = String(initialValues.source);
     policyLevelInput.value = String(initialValues.level);
     policyScopeInput.value = String(initialValues.scope);
 
     // Change input type according to policy, set value to new input
     policyNameInput.value = initialValues.name;
-    this.changeInputType_(policyNameInput);
+    this.changeInputType_(policyNamespaceInput, policyNameInput);
 
     const policyValueInput =
         this.getRequiredElement<HTMLInputElement>('.value');
@@ -325,16 +415,30 @@
     return '';
   }
 
+  getPolicyNamespace(): string {
+    assert(this.schema_);
+    const namespaceInput =
+        this.getRequiredElement<HTMLSelectElement>('.namespace');
+
+    if (namespaceInput.value in this.schema_) {
+      return namespaceInput.value;
+    } else {
+      this.setInErrorState_(namespaceInput);
+      return '';
+    }
+  }
+
   // Class method for returning the name for this policy (the value in the
   // name cell of this row)
   getPolicyName(): string {
-    const inputElement: HTMLInputElement =
-        this.getRequiredElement<HTMLInputElement>('.name');
+    const namespaceInput =
+        this.getRequiredElement<HTMLSelectElement>('.namespace');
+    const nameInput = this.getRequiredElement<HTMLInputElement>('.name');
 
-    if (this.isValidPolicyName_(inputElement.value)) {
-      return inputElement.value;
+    if (this.isValidPolicyName_(namespaceInput.value, nameInput.value)) {
+      return nameInput.value;
     } else {
-      this.setInErrorState_(inputElement);
+      this.setInErrorState_(nameInput);
       return '';
     }
   }
diff --git a/components/policy/resources/webui/test/policy_test_table.html b/components/policy/resources/webui/test/policy_test_table.html
index a68f332..219b8c0 100644
--- a/components/policy/resources/webui/test/policy_test_table.html
+++ b/components/policy/resources/webui/test/policy_test_table.html
@@ -48,7 +48,7 @@
       padding: 12px 7px;
     }
 
-    #name-header {
+    #namespace-header {
       border-radius: var(--element-border-radius) 0 0 0;
     }
 
@@ -74,7 +74,8 @@
 </style>
 <div class="table" role="table">
   <header role="row">
-    <div class="cell" id="name-header">$i18n{testTableName}</div>
+    <div class="cell" id="namespace-header">$i18n{testTableNamespace}</div>
+    <div class="cell">$i18n{testTableName}</div>
     <div class="cell">$i18n{testTableValue}</div>
     <div class="cell">$i18n{testTablePreset}</div>
     <div class="cell">$i18n{testTableSource}</div>
@@ -82,6 +83,5 @@
     <div class="cell">$i18n{testTableLevel}</div>
     <div class="cell" id="header-remove-btn-cell"></div>
   </header>
-  <policy-test-row></policy-test-row>
 </div>
 <button id="add-policy-btn">+</button>
diff --git a/components/policy/resources/webui/test/policy_test_table.ts b/components/policy/resources/webui/test/policy_test_table.ts
index 0aff083..8fc3b9a8 100644
--- a/components/policy/resources/webui/test/policy_test_table.ts
+++ b/components/policy/resources/webui/test/policy_test_table.ts
@@ -3,23 +3,47 @@
 // found in the LICENSE file.
 import './policy_test_row.js';
 
+import {assert} from 'chrome://resources/js/assert.js';
 import {CustomElement} from 'chrome://resources/js/custom_element.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
 
-import {PolicyInfo} from './policy_test_browser_proxy.js';
+import {PolicyInfo, PolicySchema} from './policy_test_browser_proxy.js';
 import {PolicyTestRowElement} from './policy_test_row.js';
 import {getTemplate} from './policy_test_table.html.js';
 
 export class PolicyTestTableElement extends CustomElement {
+  private schema_?: PolicySchema;
+
   static override get template() {
     return getTemplate();
   }
 
   constructor() {
     super();
+    this.setSchema(JSON.parse(loadTimeData.getString('initialSchema')));
     this.getRequiredElement('#add-policy-btn')
         .addEventListener('click', this.addEmptyRow.bind(this));
   }
 
+  setSchema(schema: PolicySchema) {
+    const hadSchema = !!this.schema_;
+    this.schema_ = schema;
+    for (const row of this.shadowRoot!.querySelectorAll('policy-test-row')) {
+      if (!(row.getNamespace() in schema)) {
+        // This was a policy for a now-uninstalled extension, so completely
+        // delete the row.
+        row.remove();
+      } else {
+        // Update the namespace dropdown, etc.
+        row.setSchema(schema);
+      }
+    }
+    if (!hadSchema && !this.shadowRoot!.querySelector('policy-test-row')) {
+      // On startup, add a single empty row.
+      this.addEmptyRow();
+    }
+  }
+
   clearRows() {
     const table = this.getRequiredElement<HTMLElement>('.table');
     while (table.childElementCount > 1) {
@@ -30,13 +54,16 @@
   // Event listener function that adds a new PolicyTestRowElement to the table
   // when the Add Policy button is clicked.
   addEmptyRow() {
+    assert(this.schema_);
     const newRow = document.createElement('policy-test-row');
-    // If there is a row before this one, copy its source, scope, level and
-    // preset values.
+    newRow.setSchema(this.schema_);
+    // If there is a row before this one, copy its namespace, source, scope,
+    // level and preset values.
     const rows = this.shadowRoot!.querySelectorAll('policy-test-row');
     if (rows.length > 0) {
       const lastRow = rows[rows.length - 1];
-      const attributesToCopy = ['.source', '.scope', '.level', '.preset'];
+      const attributesToCopy =
+          ['.namespace', '.source', '.scope', '.level', '.preset'];
       attributesToCopy.forEach((attribute: string) => {
         const currSelectElement =
             newRow.getRequiredElement<HTMLSelectElement>(attribute);
@@ -46,13 +73,16 @@
         currSelectElement.disabled = prevSelectElement.disabled;
       });
     }
+    newRow.updatePolicyNames();
     this.getRequiredElement('.table').appendChild(newRow);
   }
 
   // Method for adding a row with the initial values in initialValues.
   addRow(initialValues: PolicyInfo) {
+    assert(this.schema_);
     const row = this.getRequiredElement<HTMLElement>('.table').appendChild(
         document.createElement('policy-test-row'));
+    row.setSchema(this.schema_);
     row.setInitialValues(initialValues);
   }
 
@@ -64,6 +94,7 @@
         Array.from(this.shadowRoot!.querySelectorAll('policy-test-row'));
     const policyInfoArray: PolicyInfo[] = policyRowArray.map(
         (row: PolicyTestRowElement) => ({
+          namespace: row.getPolicyNamespace(),
           name: row.getPolicyName(),
           source: Number.parseInt(row.getPolicyAttribute('source')),
           scope: Number.parseInt(row.getPolicyAttribute('scope')),
diff --git a/components/policy_strings.grdp b/components/policy_strings.grdp
index 23d3f3c..12575c3c 100644
--- a/components/policy_strings.grdp
+++ b/components/policy_strings.grdp
@@ -581,6 +581,9 @@
   <message name="IDS_POLICY_HEADER_LEVEL" desc="Table header for the column in the policy table that details whether a policy is mandatory or recommended.">
     Level
   </message>
+  <message name="IDS_POLICY_HEADER_NAMESPACE" desc="Table header for the column in the policy table that contains the policy namespace (either 'Chrome', or an extension ID).">
+    Namespace
+  </message>
   <message name="IDS_POLICY_HEADER_NAME" desc="Table header for the column in the policy table that contains the policy name.">
     Policy name
   </message>
diff --git a/components/policy_strings_grdp/IDS_POLICY_HEADER_NAMESPACE.png.sha1 b/components/policy_strings_grdp/IDS_POLICY_HEADER_NAMESPACE.png.sha1
new file mode 100644
index 0000000..dd67b0e
--- /dev/null
+++ b/components/policy_strings_grdp/IDS_POLICY_HEADER_NAMESPACE.png.sha1
@@ -0,0 +1 @@
+d1040e5a0001c084ab4ec613494d6850b187dff6
\ No newline at end of file
diff --git a/components/safe_browsing/content/browser/client_side_phishing_model.cc b/components/safe_browsing/content/browser/client_side_phishing_model.cc
index 2e1a926b..fa0ff5d 100644
--- a/components/safe_browsing/content/browser/client_side_phishing_model.cc
+++ b/components/safe_browsing/content/browser/client_side_phishing_model.cc
@@ -153,6 +153,11 @@
   model_file.Close();
 }
 
+int* GetLiveClientPhishingModelCount() {
+  static int count = 0;
+  return &count;
+}
+
 }  // namespace
 
 // --- ClientSidePhishingModel methods ---
@@ -166,6 +171,10 @@
   opt_guide_->AddObserverForOptimizationTargetModel(
       optimization_guide::proto::OPTIMIZATION_TARGET_CLIENT_SIDE_PHISHING,
       /*model_metadata=*/std::nullopt, this);
+  *GetLiveClientPhishingModelCount() += 1;
+  base::UmaHistogramCounts1000(
+      "SBClientPhishing.LiveClientPhishingModelCountAtCreation",
+      *GetLiveClientPhishingModelCount());
 }
 
 void ClientSidePhishingModel::OnModelUpdated(
@@ -433,6 +442,8 @@
   }
 
   opt_guide_ = nullptr;
+
+  *GetLiveClientPhishingModelCount() -= 1;
 }
 
 base::CallbackListSubscription ClientSidePhishingModel::RegisterCallback(
diff --git a/components/safe_browsing/content/renderer/phishing_classifier/scorer.cc b/components/safe_browsing/content/renderer/phishing_classifier/scorer.cc
index a4ad46f..0841f90 100644
--- a/components/safe_browsing/content/renderer/phishing_classifier/scorer.cc
+++ b/components/safe_browsing/content/renderer/phishing_classifier/scorer.cc
@@ -304,6 +304,11 @@
 }
 #endif
 
+int* GetLiveScorerCount() {
+  static int count = 0;
+  return &count;
+}
+
 }  // namespace
 
 #if BUILDFLAG(BUILD_WITH_TFLITE_LIB)
@@ -367,8 +372,14 @@
   return odds / (odds + 1.0);
 }
 
-Scorer::Scorer() = default;
-Scorer::~Scorer() = default;
+Scorer::Scorer() {
+  *GetLiveScorerCount() += 1;
+  base::UmaHistogramCounts1000("SBClientPhishing.LiveScorersAtCreation",
+                               *GetLiveScorerCount());
+}
+Scorer::~Scorer() {
+  *GetLiveScorerCount() -= 1;
+}
 
 // static
 ScorerStorage* ScorerStorage::GetInstance() {
diff --git a/components/search_engines/template_url_service.cc b/components/search_engines/template_url_service.cc
index f3dbbfd..81ed58f 100644
--- a/components/search_engines/template_url_service.cc
+++ b/components/search_engines/template_url_service.cc
@@ -988,6 +988,14 @@
       ApplyDefaultSearchChange(url ? &url->data() : nullptr,
                                DefaultSearchManager::FROM_USER);
       selection_added = true;
+    } else {
+      // When we are setting the search engine choice from choice screens,
+      // the DSP source is expected to allow the search engine to be changed by
+      // the user. So we are guaranteed to not drop one of the choices coming
+      // from these screens here.
+      // TODO(crbug.com/323905627): Remove milestone if no hits by then.
+      CHECK_NE(choice_made_location, search_engines::ChoiceMadeLocation::kOther,
+               base::NotFatalUntil::M124);
     }
   } else {
     // We rely on the DefaultSearchManager to call ApplyDefaultSearchChange if,
diff --git a/components/viz/common/transition_utils.cc b/components/viz/common/transition_utils.cc
index 3d46bf86..27057c6 100644
--- a/components/viz/common/transition_utils.cc
+++ b/components/viz/common/transition_utils.cc
@@ -105,7 +105,10 @@
         stack.pop_back();
         continue;
       }
-
+      if (!*frame.quad_iter) {
+        stack.pop_back();
+        continue;
+      }
       frame.indent += 2;
     } else {
       if (++frame.quad_iter == pass->quad_list.end()) {
@@ -174,8 +177,8 @@
     const CompositorRenderPassList& list) {
   std::ostringstream str;
 
-  if (list.size() > kMaxListToProcess) {
-    str << "RenderPassList too large (" << list.size()
+  if (list.size() > kMaxListToProcess || list.empty()) {
+    str << "RenderPassList too large or too small (" << list.size()
         << "), max supported list length " << kMaxListToProcess;
     return str.str();
   }
diff --git a/components/viz/service/display/overlay_combination_cache.cc b/components/viz/service/display/overlay_combination_cache.cc
index 554ec36..0298239c47 100644
--- a/components/viz/service/display/overlay_combination_cache.cc
+++ b/components/viz/service/display/overlay_combination_cache.cc
@@ -175,9 +175,6 @@
       stale_candidates.reset(id);
     }
   }
-  UMA_HISTOGRAM_COUNTS_100(
-      "Compositing.Display.OverlayCombinationCache.NumIdsEvicted",
-      stale_candidates.count());
   // Remove all cached combinations that contained these candidates.
   RemoveStaleCombinations(stale_candidates);
   // Remove stale candidates from the id mapper.
diff --git a/components/viz/service/display/overlay_processor_using_strategy.cc b/components/viz/service/display/overlay_processor_using_strategy.cc
index dd6ae04..49d814e 100644
--- a/components/viz/service/display/overlay_processor_using_strategy.cc
+++ b/components/viz/service/display/overlay_processor_using_strategy.cc
@@ -950,11 +950,6 @@
                                          first_candidate_without_masks,
                                          test_candidates);
 
-  UMA_HISTOGRAM_BOOLEAN(
-      "Compositing.Display.OverlayProcessorUsingStrategy."
-      "CandidateCombinationPreviouslySucceeded",
-      result.previously_succeeded);
-
   bool testing_underlay = false;
   // We'll keep track of the underlays that we're testing so we can assign their
   // `plane_z_order`s based on their order in the QuadList.
diff --git a/components/viz/service/display/surface_aggregator.cc b/components/viz/service/display/surface_aggregator.cc
index 2c70b41..5d418cfb 100644
--- a/components/viz/service/display/surface_aggregator.cc
+++ b/components/viz/service/display/surface_aggregator.cc
@@ -736,9 +736,7 @@
         surface->GetActiveFrameIndex()) {
       // If there is a new CompositorFrame for `surface` compute resolved frame
       // data.
-      base::ElapsedTimer timer;
       ProcessResolvedFrame(resolved_frame);
-      stats_->declare_resources_time += timer.Elapsed();
     } else if (resolved_frame.is_valid()) {
       // The same `CompositorFrame` since last aggregation. Set the
       // `CompositorRenderPass` pointer back to `ResolvedPassData`. Only
@@ -2401,10 +2399,6 @@
   UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
       "Compositing.SurfaceAggregator.CopyUs", stats_->copy_time,
       kHistogramMinTime, kHistogramMaxTime, kHistogramTimeBuckets);
-  UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
-      "Compositing.SurfaceAggregator.DeclareResourcesUs",
-      stats_->declare_resources_time, kHistogramMinTime, kHistogramMaxTime,
-      kHistogramTimeBuckets);
 
   UMA_HISTOGRAM_BOOLEAN("Compositing.SurfaceAggregator.HasCopyRequestsPerFrame",
                         has_copy_requests_);
diff --git a/components/viz/service/display/surface_aggregator.h b/components/viz/service/display/surface_aggregator.h
index 6ff5581..909f13d 100644
--- a/components/viz/service/display/surface_aggregator.h
+++ b/components/viz/service/display/surface_aggregator.h
@@ -134,7 +134,6 @@
     int orphaned_render_pass = 0;
     base::TimeDelta prewalk_time;
     base::TimeDelta copy_time;
-    base::TimeDelta declare_resources_time;
   };
 
   // SurfaceObserver implementation.
diff --git a/components/viz/service/display_embedder/skia_output_device_offscreen.cc b/components/viz/service/display_embedder/skia_output_device_offscreen.cc
index f799675..141d0ef 100644
--- a/components/viz/service/display_embedder/skia_output_device_offscreen.cc
+++ b/components/viz/service/display_embedder/skia_output_device_offscreen.cc
@@ -127,7 +127,13 @@
     // offscreen.
     skgpu::graphite::TextureInfo texture_info = gpu::GraphiteBackendTextureInfo(
         context_state_->gr_context_type(),
-        SkColorTypeToSinglePlaneSharedImageFormat(sk_color_type_));
+        SkColorTypeToSinglePlaneSharedImageFormat(sk_color_type_),
+        /*readonly=*/false,
+        /*plane_index=*/0,
+        /*is_yuv_plane=*/false, /*mipmapped=*/false,
+        /*scanout_dcomp_surface=*/false,
+        /*supports_multiplanar_rendering=*/false,
+        /*supports_multiplanar_copy=*/false);
     graphite_texture_ =
         context_state_->gpu_main_graphite_recorder()->createBackendTexture(
             gfx::SizeToSkISize(size_), texture_info);
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.cc b/components/viz/service/display_embedder/skia_output_surface_impl.cc
index 4c0cd81..7d12e49 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.cc
@@ -489,8 +489,12 @@
     // purpose of creating Graphite TextureInfo i.e. it will have CopySrc and
     // CopyDst usage. So don't treat it like a root surface which generally
     // won't have or support those usages.
-    skgpu::graphite::TextureInfo texture_info =
-        gpu::GraphiteBackendTextureInfo(gr_context_type_, format_);
+    skgpu::graphite::TextureInfo texture_info = gpu::GraphiteBackendTextureInfo(
+        gr_context_type_, format_, /*readonly=*/false, /*plane_index=*/0,
+        /*is_yuv_plane=*/false,
+        /*mipmapped=*/false, /*scanout_dcomp_surface=*/false,
+        /*supports_multiplanar_rendering=*/false,
+        /*supports_multiplanar_copy=*/false);
     CHECK(texture_info.isValid());
     current_paint_.emplace(graphite_recorder_, image_info, texture_info);
   } else {
@@ -879,9 +883,10 @@
         SkImageInfo::Make(gfx::SizeToSkISize(surface_size), color_type,
                           static_cast<SkAlphaType>(alpha_type), color_space);
     skgpu::graphite::TextureInfo texture_info = gpu::GraphiteBackendTextureInfo(
-        gr_context_type_, format, /*plane_index=*/0,
+        gr_context_type_, format, /*readonly=*/false, /*plane_index=*/0,
         /*is_yuv_plane=*/false, mipmap == skgpu::Mipmapped::kYes,
-        scanout_dcomp_surface);
+        scanout_dcomp_surface, /*supports_multiplanar_rendering=*/false,
+        /*supports_multiplanar_copy=*/false);
     if (!texture_info.isValid()) {
       DLOG(ERROR) << "BeginPaintRenderPass: invalid Graphite TextureInfo";
       return nullptr;
diff --git a/content/browser/attribution_reporting/attribution_storage_sql.cc b/content/browser/attribution_reporting/attribution_storage_sql.cc
index 51d75e97..4a132d9 100644
--- a/content/browser/attribution_reporting/attribution_storage_sql.cc
+++ b/content/browser/attribution_reporting/attribution_storage_sql.cc
@@ -467,9 +467,7 @@
     : path_to_database_(user_data_directory.empty()
                             ? base::FilePath()
                             : DatabasePath(user_data_directory)),
-      db_(sql::DatabaseOptions{.exclusive_locking = true,
-                               .page_size = 4096,
-                               .cache_size = 32}),
+      db_(sql::DatabaseOptions{.page_size = 4096, .cache_size = 32}),
       delegate_(std::move(delegate)),
       rate_limit_table_(delegate_.get()) {
   DCHECK(delegate_);
diff --git a/content/browser/browsing_topics/browsing_topics_site_data_storage.cc b/content/browser/browsing_topics/browsing_topics_site_data_storage.cc
index 5783ab7..82d4bd5 100644
--- a/content/browser/browsing_topics/browsing_topics_site_data_storage.cc
+++ b/content/browser/browsing_topics/browsing_topics_site_data_storage.cc
@@ -244,8 +244,8 @@
   if (db_init_status_ != InitStatus::kUnattempted)
     return db_init_status_ == InitStatus::kSuccess;
 
-  db_ = std::make_unique<sql::Database>(sql::DatabaseOptions{
-      .exclusive_locking = true, .page_size = 4096, .cache_size = 32});
+  db_ = std::make_unique<sql::Database>(
+      sql::DatabaseOptions{.page_size = 4096, .cache_size = 32});
   db_->set_histogram_tag("BrowsingTopics");
 
   // base::Unretained is safe here because this BrowsingTopicsSiteDataStorage
diff --git a/content/browser/child_process_task_port_provider_mac.cc b/content/browser/child_process_task_port_provider_mac.cc
index fad9835..ed054e2 100644
--- a/content/browser/child_process_task_port_provider_mac.cc
+++ b/content/browser/child_process_task_port_provider_mac.cc
@@ -29,22 +29,23 @@
 }
 
 void ChildProcessTaskPortProvider::OnChildProcessLaunched(
-    base::ProcessHandle pid,
+    base::ProcessHandle process_handle,
     mojom::ChildProcess* child_process) {
   if (!ShouldRequestTaskPorts())
     return;
 
   child_process->GetTaskPort(
       base::BindOnce(&ChildProcessTaskPortProvider::OnTaskPortReceived,
-                     base::Unretained(this), pid));
+                     base::Unretained(this), process_handle));
 }
 
-mach_port_t ChildProcessTaskPortProvider::TaskForPid(
-    base::ProcessHandle pid) const {
+mach_port_t ChildProcessTaskPortProvider::TaskForHandle(
+    base::ProcessHandle process_handle) const {
   base::AutoLock lock(lock_);
-  PidToTaskPortMap::const_iterator it = pid_to_task_port_.find(pid);
-  if (it == pid_to_task_port_.end())
+  auto it = handle_to_task_port_.find(process_handle);
+  if (it == handle_to_task_port_.end()) {
     return MACH_PORT_NULL;
+  }
   return it->second.get();
 }
 
@@ -92,11 +93,12 @@
 }
 
 void ChildProcessTaskPortProvider::OnTaskPortReceived(
-    base::ProcessHandle pid,
+    base::ProcessHandle process_handle,
     mojo::PlatformHandle task_port) {
   DCHECK(ShouldRequestTaskPorts());
   if (!task_port.is_mach_send()) {
-    DLOG(ERROR) << "Invalid handle received as task port for pid " << pid;
+    DLOG(ERROR) << "Invalid handle received as task port for pid "
+                << base::GetProcId(process_handle);
     return;
   }
   base::apple::ScopedMachSendRight port = task_port.TakeMachSendRight();
@@ -114,14 +116,14 @@
     return;
   }
 
-  DVLOG(1) << "Received task port for PID=" << pid
+  DVLOG(1) << "Received task port for PID=" << base::GetProcId(process_handle)
            << ", port name=" << port.get();
 
   {
     base::AutoLock lock(lock_);
-    auto it = pid_to_task_port_.find(pid);
-    if (it == pid_to_task_port_.end()) {
-      pid_to_task_port_.emplace(pid, std::move(port));
+    auto it = handle_to_task_port_.find(process_handle);
+    if (it == handle_to_task_port_.end()) {
+      handle_to_task_port_.emplace(process_handle, std::move(port));
     } else {
       // If a task port already exists for the PID, then reset it if the port
       // is of a different name. The port name may be the same when running in
@@ -133,7 +135,7 @@
     }
   }
 
-  NotifyObservers(pid);
+  NotifyObservers(process_handle);
 }
 
 void ChildProcessTaskPortProvider::OnTaskPortDied() {
@@ -158,9 +160,9 @@
   base::apple::ScopedMachSendRight dead_port(notification.not_port);
 
   base::AutoLock lock(lock_);
-  std::erase_if(pid_to_task_port_, [&dead_port](const auto& pair) {
+  std::erase_if(handle_to_task_port_, [&dead_port](const auto& pair) {
     if (pair.second.get() == dead_port.get()) {
-      DVLOG(1) << "Task died, PID=" << pair.first
+      DVLOG(1) << "Task died, PID=" << base::GetProcId(pair.first)
                << ", task port name=" << dead_port.get();
       return true;
     }
diff --git a/content/browser/child_process_task_port_provider_mac.h b/content/browser/child_process_task_port_provider_mac.h
index 287e2196..239e332 100644
--- a/content/browser/child_process_task_port_provider_mac.h
+++ b/content/browser/child_process_task_port_provider_mac.h
@@ -13,15 +13,16 @@
 #include "base/process/port_provider_mac.h"
 #include "base/process/process_handle.h"
 #include "base/synchronization/lock.h"
+#include "base/thread_annotations.h"
 #include "content/common/child_process.mojom-forward.h"
 #include "content/common/content_export.h"
 #include "mojo/public/cpp/platform/platform_handle.h"
 
 namespace content {
 
-// The ChildProcessTaskPortProvider keeps an association between a PID and the
-// process's task port. This association is needed for the browser to manipulate
-// certain aspects of its child processes.
+// The ChildProcessTaskPortProvider keeps an association between the handle and
+// the task port of a process. This association is needed for the browser to
+// manipulate certain aspects of its child processes.
 class CONTENT_EXPORT ChildProcessTaskPortProvider : public base::PortProvider {
  public:
   // Returns the singleton instance.
@@ -31,19 +32,18 @@
   ChildProcessTaskPortProvider& operator=(const ChildProcessTaskPortProvider&) =
       delete;
 
-  // Called by BrowserChildProcessHostImpl and RenderProcessHostImpl when
-  // a new child has been created. This will invoke the GetTaskPort() method
-  // on |child_control| and will store the returned port as being associated to
-  // |pid|.
+  // Called by BrowserChildProcessHostImpl and RenderProcessHostImpl when a new
+  // child is launched. Invokes `GetTaskPort()` on `child_process` and stores
+  // the returned port as being associated to `process_handle`.
   //
   // When the kernel sends a notification that the port has become a dead name,
   // indicating that the child process has died, the association will be
   // removed.
-  void OnChildProcessLaunched(base::ProcessHandle pid,
+  void OnChildProcessLaunched(base::ProcessHandle process_handle,
                               mojom::ChildProcess* child_process);
 
   // base::PortProvider:
-  mach_port_t TaskForPid(base::ProcessHandle process) const override;
+  mach_port_t TaskForHandle(base::ProcessHandle process_handle) const override;
 
  private:
   friend class ChildProcessTaskPortProviderTest;
@@ -61,7 +61,7 @@
   bool ShouldRequestTaskPorts() const;
 
   // Callback for mojom::ChildProcess::GetTaskPort reply.
-  void OnTaskPortReceived(base::ProcessHandle pid,
+  void OnTaskPortReceived(base::ProcessHandle process_handle,
                           mojo::PlatformHandle task_port);
 
   // Event handler for |notification_source_|, invoked for
@@ -71,14 +71,14 @@
   // Lock that protects the map below.
   mutable base::Lock lock_;
 
-  // Maps a PID to the corresponding task port.
-  using PidToTaskPortMap =
+  // Maps a process handle to the corresponding task port.
+  using HandleToTaskPortMap =
       std::map<base::ProcessHandle, base::apple::ScopedMachSendRight>;
-  PidToTaskPortMap pid_to_task_port_;
+  HandleToTaskPortMap handle_to_task_port_ GUARDED_BY(lock_);
 
-  // A Mach port that is used to register for dead name notifications from
-  // the kernel. All the ports in |pid_to_task_port_| have a notification set
-  // up to send to this port.
+  // A Mach port that is used to register for dead name notifications from the
+  // kernel. All the ports in `handle_to_task_port_` have a notification set up
+  // to send to this port.
   base::apple::ScopedMachReceiveRight notification_port_;
 
   // Dispatch source for |notification_port_|.
diff --git a/content/browser/child_process_task_port_provider_mac_unittest.cc b/content/browser/child_process_task_port_provider_mac_unittest.cc
index 7e2a97f..8b198245 100644
--- a/content/browser/child_process_task_port_provider_mac_unittest.cc
+++ b/content/browser/child_process_task_port_provider_mac_unittest.cc
@@ -115,11 +115,11 @@
 static constexpr mach_port_t kMachPortNull = MACH_PORT_NULL;
 
 TEST_F(ChildProcessTaskPortProviderTest, InvalidProcess) {
-  EXPECT_EQ(kMachPortNull, provider()->TaskForPid(99));
+  EXPECT_EQ(kMachPortNull, provider()->TaskForHandle(99));
 }
 
 TEST_F(ChildProcessTaskPortProviderTest, ChildLifecycle) {
-  EXPECT_EQ(kMachPortNull, provider()->TaskForPid(99));
+  EXPECT_EQ(kMachPortNull, provider()->TaskForHandle(99));
 
   // Create a fake task port for the fake process.
   base::apple::ScopedMachReceiveRight receive_right;
@@ -140,25 +140,25 @@
 
   provider()->OnChildProcessLaunched(99, &child_process);
 
-  // Verify that the task-for-pid association is established.
+  // Verify that the task-for-handle association is established.
   WaitForTaskPort();
   EXPECT_EQ(std::vector<base::ProcessHandle>{99}, received_processes());
-  EXPECT_EQ(receive_right.get(), provider()->TaskForPid(99));
+  EXPECT_EQ(receive_right.get(), provider()->TaskForHandle(99));
 
   // References owned by |send_right| and the map.
-  EXPECT_EQ(2u, GetSendRightRefCount(provider()->TaskForPid(99)));
-  EXPECT_EQ(0u, GetDeadNameRefCount(provider()->TaskForPid(99)));
+  EXPECT_EQ(2u, GetSendRightRefCount(provider()->TaskForHandle(99)));
+  EXPECT_EQ(0u, GetDeadNameRefCount(provider()->TaskForHandle(99)));
 
   // "Kill" the process and verify that the association is deleted.
   receive_right.reset();
 
   WaitForCondition(base::BindRepeating(
       [](ChildProcessTaskPortProvider* provider) -> bool {
-        return provider->TaskForPid(99) == MACH_PORT_NULL;
+        return provider->TaskForHandle(99) == MACH_PORT_NULL;
       },
       base::Unretained(provider())));
 
-  EXPECT_EQ(kMachPortNull, provider()->TaskForPid(99));
+  EXPECT_EQ(kMachPortNull, provider()->TaskForHandle(99));
 
   // Send rights turned into a dead name right, which is owned by |send_right|.
   EXPECT_EQ(0u, GetSendRightRefCount(send_right.get()));
@@ -166,7 +166,7 @@
 }
 
 TEST_F(ChildProcessTaskPortProviderTest, DeadTaskPort) {
-  EXPECT_EQ(kMachPortNull, provider()->TaskForPid(6));
+  EXPECT_EQ(kMachPortNull, provider()->TaskForHandle(6));
 
   // Create a fake task port for the fake process.
   base::apple::ScopedMachReceiveRight receive_right;
@@ -222,21 +222,21 @@
 
   // Verify that the dead name does not register for the process.
   EXPECT_EQ(std::vector<base::ProcessHandle>{123}, received_processes());
-  EXPECT_EQ(kMachPortNull, provider()->TaskForPid(6));
-  EXPECT_EQ(receive_right2.get(), provider()->TaskForPid(123));
+  EXPECT_EQ(kMachPortNull, provider()->TaskForHandle(6));
+  EXPECT_EQ(receive_right2.get(), provider()->TaskForHandle(123));
 
   // Clean up the second receive right.
   receive_right2.reset();
   WaitForCondition(base::BindRepeating(
       [](ChildProcessTaskPortProvider* provider) -> bool {
-        return provider->TaskForPid(123) == MACH_PORT_NULL;
+        return provider->TaskForHandle(123) == MACH_PORT_NULL;
       },
       base::Unretained(provider())));
-  EXPECT_EQ(kMachPortNull, provider()->TaskForPid(123));
+  EXPECT_EQ(kMachPortNull, provider()->TaskForHandle(123));
 }
 
 TEST_F(ChildProcessTaskPortProviderTest, ReplacePort) {
-  EXPECT_EQ(kMachPortNull, provider()->TaskForPid(42));
+  EXPECT_EQ(kMachPortNull, provider()->TaskForHandle(42));
 
   // Create a fake task port for the fake process.
   base::apple::ScopedMachReceiveRight receive_right;
@@ -268,12 +268,12 @@
   EXPECT_EQ(2u, GetSendRightRefCount(send_right.get()));
   EXPECT_EQ(0u, GetDeadNameRefCount(send_right.get()));
 
-  // Verify that the task-for-pid association is established.
+  // Verify that the task-for-handle association is established.
   std::vector<base::ProcessHandle> expected_receive{42, 42};
   EXPECT_EQ(expected_receive, received_processes());
-  EXPECT_EQ(receive_right.get(), provider()->TaskForPid(42));
+  EXPECT_EQ(receive_right.get(), provider()->TaskForHandle(42));
 
-  // Now simulate PID reuse by replacing the task port with a new one.
+  // Now simulate handle reuse by replacing the task port with a new one.
   base::apple::ScopedMachReceiveRight receive_right2;
   base::apple::ScopedMachSendRight send_right2;
   ASSERT_TRUE(base::apple::CreateMachPort(&receive_right2, &send_right2));
@@ -300,7 +300,7 @@
 
   expected_receive.push_back(42);
   EXPECT_EQ(expected_receive, received_processes());
-  EXPECT_EQ(receive_right2.get(), provider()->TaskForPid(42));
+  EXPECT_EQ(receive_right2.get(), provider()->TaskForHandle(42));
 }
 
 }  // namespace content
diff --git a/content/browser/code_cache/generated_code_cache.cc b/content/browser/code_cache/generated_code_cache.cc
index 326ac3cc..f9e76bf 100644
--- a/content/browser/code_cache/generated_code_cache.cc
+++ b/content/browser/code_cache/generated_code_cache.cc
@@ -23,6 +23,7 @@
 #include "net/base/network_isolation_key.h"
 #include "net/base/url_util.h"
 #include "net/http/http_cache.h"
+#include "third_party/blink/public/common/scheme_registry.h"
 #include "url/gurl.h"
 
 using storage::BigIOBuffer;
@@ -50,7 +51,8 @@
       resource_url.SchemeIs(content::kChromeUIScheme) ||
       resource_url.SchemeIs(content::kChromeUIUntrustedScheme);
   DCHECK(resource_url.SchemeIsHTTPOrHTTPS() ||
-         resource_url_is_chrome_or_chrome_untrusted);
+         resource_url_is_chrome_or_chrome_untrusted ||
+         blink::CommonSchemeRegistry::IsExtensionScheme(resource_url.scheme()));
 
   // |origin_lock| should be either empty or should have
   // Http/Https/chrome/chrome-untrusted schemes and it should not be a URL with
@@ -59,10 +61,12 @@
   bool origin_lock_is_chrome_or_chrome_untrusted =
       origin_lock.SchemeIs(content::kChromeUIScheme) ||
       origin_lock.SchemeIs(content::kChromeUIUntrustedScheme);
-  DCHECK(origin_lock.is_empty() ||
-         ((origin_lock.SchemeIsHTTPOrHTTPS() ||
-           origin_lock_is_chrome_or_chrome_untrusted) &&
-          !url::Origin::Create(origin_lock).opaque()));
+  DCHECK(
+      origin_lock.is_empty() ||
+      ((origin_lock.SchemeIsHTTPOrHTTPS() ||
+        origin_lock_is_chrome_or_chrome_untrusted ||
+        blink::CommonSchemeRegistry::IsExtensionScheme(origin_lock.scheme())) &&
+       !url::Origin::Create(origin_lock).opaque()));
 
   // The chrome and chrome-untrusted schemes are only used with the WebUI
   // code cache type.
diff --git a/content/browser/fenced_frame/fenced_frame.cc b/content/browser/fenced_frame/fenced_frame.cc
index dc48d61..8f1ec3a7 100644
--- a/content/browser/fenced_frame/fenced_frame.cc
+++ b/content/browser/fenced_frame/fenced_frame.cc
@@ -330,10 +330,6 @@
   return false;
 }
 
-WebContents* FencedFrame::DeprecatedGetWebContents() {
-  return web_contents_;
-}
-
 void FencedFrame::UpdateOverridingUserAgent() {}
 
 void FencedFrame::DidChangeFramePolicy(const blink::FramePolicy& frame_policy) {
diff --git a/content/browser/fenced_frame/fenced_frame.h b/content/browser/fenced_frame/fenced_frame.h
index 268ade1..8ea2f59 100644
--- a/content/browser/fenced_frame/fenced_frame.h
+++ b/content/browser/fenced_frame/fenced_frame.h
@@ -88,7 +88,6 @@
   void NotifyNavigationEntriesDeleted() override;
   void ActivateAndShowRepostFormWarningDialog() override;
   bool ShouldPreserveAbortedURLs() override;
-  WebContents* DeprecatedGetWebContents() override;
   void UpdateOverridingUserAgent() override;
 
   const raw_ptr<WebContentsImpl> web_contents_;
diff --git a/content/browser/fenced_frame/fenced_frame_browsertest.cc b/content/browser/fenced_frame/fenced_frame_browsertest.cc
index a16300d..d7584ed 100644
--- a/content/browser/fenced_frame/fenced_frame_browsertest.cc
+++ b/content/browser/fenced_frame/fenced_frame_browsertest.cc
@@ -1519,6 +1519,100 @@
   ASSERT_EQ(ff_rfh->GetProcess(), primary_main_frame_host()->GetProcess());
 }
 
+class FencedFrameIsolatedSandboxedIframesBrowserTest
+    : public FencedFrameMPArchBrowserTest,
+      public ::testing::WithParamInterface<bool> {
+ public:
+  FencedFrameIsolatedSandboxedIframesBrowserTest() {
+    if (GetParam()) {
+      // Run test with both isolation features enabled.
+      feature_list_.InitWithFeatures({blink::features::kIsolateSandboxedIframes,
+                                      features::kIsolateFencedFrames},
+                                     {});
+    } else {
+      // Run test with only isolated sandboxed iframes enabled.
+      feature_list_.InitWithFeatures(
+          {blink::features::kIsolateSandboxedIframes},
+          {features::kIsolateFencedFrames});
+    }
+  }
+  ~FencedFrameIsolatedSandboxedIframesBrowserTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// This is a basic test to make sure the kIsolateSandboxedIframes (OOPSIF) load
+// properly with FencedFrames, both when FencedFrames isolation mode is off and
+// on. The OOPSIF frame is sandboxed due to a CSP sandbox header delivered with
+// the page loaded into the FencedFrame. The FencedFrame element doesn't support
+// the 'sandbox' attribute directly, nor can it be loaded inside an OOPSIF since
+// OOPSIFs by definition disallow same-origin, whereas the FencedFrame element
+// will only load inside a sandbox if allow-same-origin is specified on the
+// sandbox. See kFencedFrameMandatoryUnsandboxedFlags.
+IN_PROC_BROWSER_TEST_P(FencedFrameIsolatedSandboxedIframesBrowserTest,
+                       CSP_Mainframe) {
+  bool testing_with_isolate_fenced_frames = GetParam();
+  IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
+  ASSERT_TRUE(AreAllSitesIsolatedForTesting());
+  ASSERT_TRUE(https_server()->Start());
+
+  const GURL main_url = https_server()->GetURL("a.test", "/title1.html");
+  const GURL fenced_frame_url =
+      https_server()->GetURL("a.test", "/fenced_frames/sandbox_flags.html");
+
+  EXPECT_TRUE(NavigateToURL(shell(), main_url));
+  RenderFrameHostImpl* ff_rfh = static_cast<RenderFrameHostImpl*>(
+      fenced_frame_test_helper().CreateFencedFrame(primary_main_frame_host(),
+                                                   fenced_frame_url));
+  EXPECT_TRUE(ff_rfh->IsSandboxed(blink::kFencedFrameForcedSandboxFlags));
+
+  EXPECT_NE(ff_rfh->GetProcess(), primary_main_frame_host()->GetProcess());
+  // This next result may seem weird, but the is_fenced() bit only gets
+  // set if SiteIsolationPolicy::IsProcessIsolationForFencedFramesEnabled()
+  // returns true in SiteInstanceImpl::CreateForFencedFrame().
+  // The bit is picked up from the fenced frame's BrowsingInstance's
+  // IsolationContext when the SiteInfo is created.
+  EXPECT_EQ(testing_with_isolate_fenced_frames,
+            ff_rfh->GetSiteInstance()->GetSiteInfo().is_fenced());
+  EXPECT_TRUE(ff_rfh->GetSiteInstance()->GetSiteInfo().is_sandboxed());
+  EXPECT_NE(
+      primary_main_frame_host()->GetSiteInstance()->GetBrowsingInstanceId(),
+      ff_rfh->GetSiteInstance()->GetBrowsingInstanceId());
+}
+
+// Similar to CSP_Mainframe, but in this test OOPSIF doesn't isolate the fenced
+// frames, while kIsolateFencedFrames does.
+IN_PROC_BROWSER_TEST_P(FencedFrameIsolatedSandboxedIframesBrowserTest,
+                       Non_CSP_Mainframe) {
+  bool testing_with_isolate_fenced_frames = GetParam();
+  IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
+  ASSERT_TRUE(AreAllSitesIsolatedForTesting());
+  ASSERT_TRUE(https_server()->Start());
+
+  const GURL main_url = https_server()->GetURL("a.test", "/title1.html");
+  const GURL fenced_frame_url =
+      https_server()->GetURL("a.test", "/fenced_frames/title1.html");
+
+  EXPECT_TRUE(NavigateToURL(shell(), main_url));
+  RenderFrameHostImpl* ff_rfh = static_cast<RenderFrameHostImpl*>(
+      fenced_frame_test_helper().CreateFencedFrame(primary_main_frame_host(),
+                                                   fenced_frame_url));
+  EXPECT_TRUE(ff_rfh->IsSandboxed(blink::kFencedFrameForcedSandboxFlags));
+
+  EXPECT_EQ(testing_with_isolate_fenced_frames,
+            ff_rfh->GetSiteInstance()->GetSiteInfo().is_fenced());
+  if (testing_with_isolate_fenced_frames) {
+    EXPECT_NE(ff_rfh->GetProcess(), primary_main_frame_host()->GetProcess());
+  } else {
+    EXPECT_EQ(ff_rfh->GetProcess(), primary_main_frame_host()->GetProcess());
+  }
+  EXPECT_FALSE(ff_rfh->GetSiteInstance()->GetSiteInfo().is_sandboxed());
+  EXPECT_NE(
+      primary_main_frame_host()->GetSiteInstance()->GetBrowsingInstanceId(),
+      ff_rfh->GetSiteInstance()->GetBrowsingInstanceId());
+}
+
 class FencedFrameProcessIsolationBrowserTest
     : public FencedFrameMPArchBrowserTest {
  public:
@@ -1545,6 +1639,9 @@
                                                    fenced_frame_url));
   EXPECT_TRUE(ff_rfh->GetSiteInstance()->GetSiteInfo().is_fenced());
   EXPECT_NE(ff_rfh->GetProcess(), primary_main_frame_host()->GetProcess());
+  EXPECT_NE(
+      primary_main_frame_host()->GetSiteInstance()->GetBrowsingInstanceId(),
+      ff_rfh->GetSiteInstance()->GetBrowsingInstanceId());
 }
 
 // Tests that fenced frames that are same-origin with each other are put in
@@ -7511,4 +7608,12 @@
                          ::testing::Combine(testing::Bool(), testing::Bool()),
                          &UUIDFrameTreeBrowserTest::DescribeParams);
 
+INSTANTIATE_TEST_SUITE_P(All,
+                         FencedFrameIsolatedSandboxedIframesBrowserTest,
+                         ::testing::Bool(),
+                         [](const testing::TestParamInfo<bool>& info) {
+                           return info.param ? "kIsolateFencedFramesEnabled"
+                                             : "kIsolateFencedFramesDisabled";
+                         });
+
 }  // namespace content
diff --git a/content/browser/file_system_access/file_system_access_lock_manager.cc b/content/browser/file_system_access/file_system_access_lock_manager.cc
index 0dec665..e6ef35fb 100644
--- a/content/browser/file_system_access/file_system_access_lock_manager.cc
+++ b/content/browser/file_system_access/file_system_access_lock_manager.cc
@@ -24,9 +24,9 @@
 using LockHandle = FileSystemAccessLockManager::LockHandle;
 using LockType = FileSystemAccessLockManager::LockType;
 
-// This class represents an active lock on the `path_component`. The lock is
-// kept alive when there is some `LockHandle` to it. The lock is released when
-// all its `LockHandle`s have been destroyed.
+// This class represents an active lock on the `path`. The lock is kept alive
+// when there is some `LockHandle` to it. The lock is released when all its
+// `LockHandle`s have been destroyed.
 //
 // Terminology:
 //  - A "caller" is the caller of `FileSystemAccessLockManager` `TakeLock`
@@ -61,11 +61,11 @@
 //    have its `LockHandle`s given to the caller.
 class Lock {
  public:
-  Lock(const base::FilePath::StringType& path_component,
+  Lock(const base::FilePath& path,
        const LockType& type,
        const LockType& exclusive_lock_type,
        base::optional_ref<Lock> parent_lock)
-      : path_component_(path_component),
+      : path_(path),
         type_(type),
         exclusive_lock_type_(exclusive_lock_type),
         parent_lock_(std::move(parent_lock)),
@@ -79,9 +79,7 @@
   Lock(Lock const&) = delete;
   Lock& operator=(Lock const&) = delete;
 
-  const base::FilePath::StringType& path_component() const {
-    return path_component_;
-  }
+  const base::FilePath& path() const { return path_; }
 
   const LockType& type() const { return type_; }
 
@@ -90,23 +88,22 @@
   // Returns whether this lock is contentious with `type`.
   bool IsContentious(LockType type) { return type != type_ || IsExclusive(); }
 
-  Lock* GetChild(const base::FilePath::StringType& path_component) {
+  Lock* GetChild(const base::FilePath& path) {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-    auto child_lock_it = child_locks_.find(path_component);
+    auto child_lock_it = child_locks_.find(path);
     return child_lock_it != child_locks_.end() ? child_lock_it->second.get()
                                                : nullptr;
   }
 
   // Get the child if it exists. If it doesn't, creates it if it can. Otherwise
   // return null.
-  Lock* GetOrCreateChild(const base::FilePath::StringType& path_component,
-                         LockType lock_type) {
+  Lock* GetOrCreateChild(const base::FilePath& path, LockType lock_type) {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    Lock* child = GetChild(path_component);
+    Lock* child = GetChild(path);
 
     if (!child) {
-      return CreateChild(path_component, lock_type);
+      return CreateChild(path, lock_type);
     }
 
     if (!child->IsContentious(lock_type)) {
@@ -127,9 +124,9 @@
     }
 
     // Create a child that is pending on the eviction of the current child.
-    std::unique_ptr<Lock> evicting_subroot_lock = TakeChild(path_component);
+    std::unique_ptr<Lock> evicting_subroot_lock = TakeChild(path);
     CHECK(evicting_subroot_lock);
-    child = CreateChild(path_component, lock_type);
+    child = CreateChild(path, lock_type);
     child->SetEvictingSubrootLock(std::move(evicting_subroot_lock));
 
     return child;
@@ -178,34 +175,31 @@
   bool InPendingSubtree() { return is_pending_; }
 
  protected:
-  virtual void DestroySelf() { parent_lock_->ReleaseChild(path_component_); }
+  virtual void DestroySelf() { parent_lock_->ReleaseChild(path_); }
 
  private:
   friend class FileSystemAccessLockManager::LockHandle;
 
-  Lock* CreateChild(const base::FilePath::StringType& path_component,
-                    LockType lock_type) {
+  Lock* CreateChild(const base::FilePath& path, LockType lock_type) {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    Lock* child_lock =
-        new Lock(path_component, lock_type, exclusive_lock_type_, this);
-    child_locks_.emplace(path_component, base::WrapUnique<Lock>(child_lock));
+    Lock* child_lock = new Lock(path, lock_type, exclusive_lock_type_, this);
+    child_locks_.emplace(path, base::WrapUnique<Lock>(child_lock));
     return child_lock;
   }
 
-  std::unique_ptr<Lock> TakeChild(
-      const base::FilePath::StringType& path_component) {
+  std::unique_ptr<Lock> TakeChild(const base::FilePath& path) {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    auto child_node = child_locks_.extract(path_component);
+    auto child_node = child_locks_.extract(path);
     if (child_node.empty()) {
       return nullptr;
     }
     return std::move(child_node.mapped());
   }
 
-  void ReleaseChild(const base::FilePath::StringType& path_component) {
+  void ReleaseChild(const base::FilePath& path) {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-    size_t count_removed = child_locks_.erase(path_component);
+    size_t count_removed = child_locks_.erase(path);
 
     CHECK_EQ(1u, count_removed);
   }
@@ -266,7 +260,7 @@
 
       // If we are an Evicting subroot, then we must destroy ourselves through
       // the `pending_lock` that owns us.
-      Lock* pending_lock = parent_lock_->GetChild(path_component_);
+      Lock* pending_lock = parent_lock_->GetChild(path_);
 
       if (InPendingSubtree()) {
         CHECK(IsPendingSubroot());
@@ -302,7 +296,7 @@
 
     // Will destroy `this` if this its an ancestor Lock since ancestors are
     // owned by their children, and we're destroying all the children.
-    for (auto& [_path_component, child] : child_locks_) {
+    for (auto& [_path, child] : child_locks_) {
       // Destroys `child`.
       child->EvictPendingSubtree();
     }
@@ -320,7 +314,7 @@
 
     is_pending_ = false;
     evicting_subroot_lock_ = nullptr;
-    for (auto& [_path_component, child] : child_locks_) {
+    for (auto& [_path, child] : child_locks_) {
       child->PromotePendingToTaken();
     }
     for (auto& pending_callback : pending_callbacks_) {
@@ -349,8 +343,7 @@
   // Returns if we're the subroot of an Evicting subtree. See class comment.
   bool IsEvictingSubroot() {
     return parent_lock_.has_value() &&
-           parent_lock_->GetChild(path_component_)
-                   ->evicting_subroot_lock_.get() == this;
+           parent_lock_->GetChild(path_)->evicting_subroot_lock_.get() == this;
   }
 
   // Makes `this` a Pending subtree that is waiting on the destruction of the
@@ -379,8 +372,8 @@
   base::flat_map<GlobalRenderFrameHostId, raw_ref<LockHandle>>
       frame_id_lock_handles_;
 
-  // The file path component of what we're locking within our parent `Lock`.
-  const base::FilePath::StringType path_component_;
+  // The file path of what we're locking within our parent `Lock`.
+  const base::FilePath path_;
 
   const LockType type_;
 
@@ -391,8 +384,8 @@
   // is safe to dereference since `parent_lock_` owns `this`.
   base::optional_ref<Lock> parent_lock_;
 
-  // The map of path components to the respective children.
-  std::map<base::FilePath::StringType, std::unique_ptr<Lock>> child_locks_;
+  // The map of path and lock to the respective children.
+  std::map<base::FilePath, std::unique_ptr<Lock>> child_locks_;
 
   bool is_pending_;
 
@@ -514,11 +507,15 @@
     return false;
   }
 
-  auto base_component = url.path().BaseName().value();
-  for (const auto& component : url.path().GetComponents()) {
+  base::FilePath cur_component_path;
+  std::vector<base::FilePath::StringType> components =
+      url.path().GetComponents();
+  for (size_t i = 0; i < components.size(); ++i) {
+    cur_component_path = i == 0 ? base::FilePath(components[0])
+                                : cur_component_path.Append(components[i]);
     LockType component_lock_type =
-        component == base_component ? lock_type : ancestor_lock_type_;
-    lock = lock->GetChild(component);
+        i == components.size() - 1 ? lock_type : ancestor_lock_type_;
+    lock = lock->GetChild(cur_component_path);
     if (!lock) {
       // If there's no lock, then it's not contentious.
       return false;
@@ -543,13 +540,16 @@
   CHECK(lock);
 
   // Attempt to take a lock on all components in the path.
-  auto base_component = url.path().BaseName().value();
-  for (const auto& component : url.path().GetComponents()) {
+  base::FilePath cur_component_path;
+  std::vector<base::FilePath::StringType> components =
+      url.path().GetComponents();
+  for (size_t i = 0; i < components.size(); ++i) {
+    cur_component_path = i == 0 ? base::FilePath(components[0])
+                                : cur_component_path.Append(components[i]);
     // Take `lock_type` on the base and ancestor locks on the ancestors.
     LockType component_lock_type =
-        component == base_component ? lock_type : ancestor_lock_type_;
-    lock = lock->GetOrCreateChild(component, component_lock_type);
-
+        i == components.size() - 1 ? lock_type : ancestor_lock_type_;
+    lock = lock->GetOrCreateChild(cur_component_path, component_lock_type);
     if (!lock) {
       // Couldn't take lock due to contention with a lock in `url`'s path held
       // by an active page.
diff --git a/content/browser/file_system_access/file_system_access_lock_manager_unittest.cc b/content/browser/file_system_access/file_system_access_lock_manager_unittest.cc
index d198c0db..3398acb7 100644
--- a/content/browser/file_system_access/file_system_access_lock_manager_unittest.cc
+++ b/content/browser/file_system_access/file_system_access_lock_manager_unittest.cc
@@ -414,6 +414,44 @@
   AssertAncestorLockBehavior(parent_url, child_url);
 }
 
+TEST_F(FileSystemAccessLockManagerTest, AncestorWithSameName) {
+  {
+    base::FilePath parent_path = dir_.GetPath().AppendASCII("foo");
+    auto parent_url = manager_->CreateFileSystemURLFromPath(
+        FileSystemAccessEntryFactory::PathType::kLocal, parent_path);
+    auto child_url = manager_->CreateFileSystemURLFromPath(
+        FileSystemAccessEntryFactory::PathType::kLocal,
+        parent_path.Append(FILE_PATH_LITERAL("foo")));
+
+    AssertAncestorLockBehavior(parent_url, child_url);
+  }
+
+  {
+    base::FilePath parent_path =
+        base::FilePath::FromUTF8Unsafe(kTestMountPoint).AppendASCII("foo");
+    auto parent_url = manager_->CreateFileSystemURLFromPath(
+        FileSystemAccessEntryFactory::PathType::kExternal, parent_path);
+    auto child_url = manager_->CreateFileSystemURLFromPath(
+        FileSystemAccessEntryFactory::PathType::kExternal,
+        parent_path.Append(FILE_PATH_LITERAL("foo")));
+
+    AssertAncestorLockBehavior(parent_url, child_url);
+  }
+
+  {
+    auto parent_path = base::FilePath::FromUTF8Unsafe("test/foo/bar");
+    auto parent_url = file_system_context_->CreateCrackedFileSystemURL(
+        kTestStorageKey, storage::kFileSystemTypeTemporary, parent_path);
+    parent_url.SetBucket(kTestBucketLocator);
+    auto child_url = file_system_context_->CreateCrackedFileSystemURL(
+        kTestStorageKey, storage::kFileSystemTypeTemporary,
+        parent_path.Append(FILE_PATH_LITERAL("foo")));
+    child_url.SetBucket(kTestBucketLocator);
+
+    AssertAncestorLockBehavior(parent_url, child_url);
+  }
+}
+
 TEST_F(FileSystemAccessLockManagerTest, BFCacheExclusive) {
   RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(main_rfh());
 
diff --git a/content/browser/file_system_access/file_system_access_manager_impl.cc b/content/browser/file_system_access/file_system_access_manager_impl.cc
index 31de79b3..80a00595 100644
--- a/content/browser/file_system_access/file_system_access_manager_impl.cc
+++ b/content/browser/file_system_access/file_system_access_manager_impl.cc
@@ -455,6 +455,22 @@
     return;
   }
 
+  // Don't show the file picker if there is an already active file picker for
+  // this render frame host.
+  GlobalRenderFrameHostId global_rfh_id = rfh->GetGlobalId();
+  if (rfhs_with_active_file_pickers_.contains(global_rfh_id)) {
+    std::move(callback).Run(
+        file_system_access_error::FromStatus(
+            FileSystemAccessStatus::kPermissionDenied,
+            "File picker already active."),
+        std::vector<blink::mojom::FileSystemAccessEntryPtr>());
+    return;
+  }
+  rfhs_with_active_file_pickers_.insert(global_rfh_id);
+  ChooseEntriesCallback wrapped_callback = std::move(callback).Then(
+      base::BindOnce(&FileSystemAccessManagerImpl::FilePickerDeactivated,
+                     weak_factory_.GetWeakPtr(), global_rfh_id));
+
   if (!options->start_in_options.is_null() &&
       options->start_in_options->is_directory_token() &&
       options->start_in_options->get_directory_token().is_valid()) {
@@ -464,14 +480,21 @@
         std::move(token),
         base::BindOnce(&FileSystemAccessManagerImpl::ResolveDefaultDirectory,
                        weak_factory_.GetWeakPtr(), context, std::move(options),
-                       std::move(callback)));
+                       std::move(wrapped_callback)));
     return;
   }
 
-  ResolveDefaultDirectory(context, std::move(options), std::move(callback),
+  ResolveDefaultDirectory(context, std::move(options),
+                          std::move(wrapped_callback),
                           /*resolved_directory_token=*/nullptr);
 }
 
+void FileSystemAccessManagerImpl::FilePickerDeactivated(
+    GlobalRenderFrameHostId global_rfh_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  rfhs_with_active_file_pickers_.erase(global_rfh_id);
+}
+
 void FileSystemAccessManagerImpl::ResolveDefaultDirectory(
     const BindingContext& context,
     blink::mojom::FilePickerOptionsPtr options,
diff --git a/content/browser/file_system_access/file_system_access_manager_impl.h b/content/browser/file_system_access/file_system_access_manager_impl.h
index 1e0b8c2..0e5c005 100644
--- a/content/browser/file_system_access/file_system_access_manager_impl.h
+++ b/content/browser/file_system_access/file_system_access_manager_impl.h
@@ -31,6 +31,7 @@
 #include "content/public/browser/file_system_access_entry_factory.h"
 #include "content/public/browser/file_system_access_permission_context.h"
 #include "content/public/browser/file_system_access_permission_grant.h"
+#include "content/public/browser/global_routing_id.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
@@ -590,6 +591,8 @@
       FileSystemAccessPermissionContext::HandleType type,
       const base::FilePath& root_permission_path);
 
+  void FilePickerDeactivated(GlobalRenderFrameHostId global_rfh_id);
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   const scoped_refptr<storage::FileSystemContext> context_;
@@ -660,6 +663,9 @@
            storage::FileSystemURL::Comparator>
       directory_ids_ GUARDED_BY_CONTEXT(sequence_checker_);
 
+  std::set<GlobalRenderFrameHostId> rfhs_with_active_file_pickers_
+      GUARDED_BY_CONTEXT(sequence_checker_);
+
   std::optional<FileSystemChooser::ResultEntry>
       auto_file_picker_result_for_test_ GUARDED_BY_CONTEXT(sequence_checker_);
 
diff --git a/content/browser/gpu/browser_child_process_backgrounded_bridge.mm b/content/browser/gpu/browser_child_process_backgrounded_bridge.mm
index d049b1e9..0f64d6d 100644
--- a/content/browser/gpu/browser_child_process_backgrounded_bridge.mm
+++ b/content/browser/gpu/browser_child_process_backgrounded_bridge.mm
@@ -33,7 +33,7 @@
     : process_(process), objc_storage_(std::make_unique<ObjCStorage>()) {
   base::PortProvider* port_provider =
       BrowserChildProcessHost::GetPortProvider();
-  if (port_provider->TaskForPid(process_->GetData().GetProcess().Pid()) !=
+  if (port_provider->TaskForHandle(process_->GetData().GetProcess().Handle()) !=
       MACH_PORT_NULL) {
     Initialize();
   } else {
diff --git a/content/browser/gpu/browser_child_process_backgrounded_bridge_browsertest.mm b/content/browser/gpu/browser_child_process_backgrounded_bridge_browsertest.mm
index 0bed502..cfd5562 100644
--- a/content/browser/gpu/browser_child_process_backgrounded_bridge_browsertest.mm
+++ b/content/browser/gpu/browser_child_process_backgrounded_bridge_browsertest.mm
@@ -61,7 +61,8 @@
   // Waits until the port for the GPU process is available.
   void WaitForPort() {
     auto* port_provider = content::BrowserChildProcessHost::GetPortProvider();
-    DCHECK(port_provider->TaskForPid(
+    // Note: On macOS, a process id and a process handle are the same thing.
+    DCHECK(port_provider->TaskForHandle(
                content::GpuProcessHost::Get()->process_id()) == MACH_PORT_NULL);
     port_provider->AddObserver(this);
     base::RunLoop run_loop;
diff --git a/content/browser/interest_group/interest_group_caching_storage.cc b/content/browser/interest_group/interest_group_caching_storage.cc
index e9adb114..3b943ea 100644
--- a/content/browser/interest_group/interest_group_caching_storage.cc
+++ b/content/browser/interest_group/interest_group_caching_storage.cc
@@ -311,13 +311,21 @@
   if (CacheIsEnabled()) {
     auto cached_groups_it = cached_interest_groups_.find(group_key.owner);
     if (cached_groups_it != cached_interest_groups_.end()) {
-      std::optional<SingleStorageInterestGroup> output =
-          cached_groups_it->second.get()->FindGroup(group_key.name);
-      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
-          FROM_HERE, base::BindOnce(std::move(callback), std::move(output)));
-      base::UmaHistogramBoolean("Ads.InterestGroup.GetInterestGroupCacheHit",
-                                true);
-      return;
+      scoped_refptr<StorageInterestGroups> groups =
+          cached_groups_it->second.get();
+      if (groups) {
+        std::optional<SingleStorageInterestGroup> output =
+            groups->FindGroup(group_key.name);
+        if (output &&
+            output.value()->interest_group.expiry < base::Time::Now()) {
+          output.reset();
+        }
+        base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+            FROM_HERE, base::BindOnce(std::move(callback), std::move(output)));
+        base::UmaHistogramBoolean("Ads.InterestGroup.GetInterestGroupCacheHit",
+                                  true);
+        return;
+      }
     }
     base::UmaHistogramBoolean("Ads.InterestGroup.GetInterestGroupCacheHit",
                               false);
diff --git a/content/browser/interest_group/interest_group_caching_storage_unittest.cc b/content/browser/interest_group/interest_group_caching_storage_unittest.cc
index f7a33d6..66342646b 100644
--- a/content/browser/interest_group/interest_group_caching_storage_unittest.cc
+++ b/content/browser/interest_group/interest_group_caching_storage_unittest.cc
@@ -503,6 +503,15 @@
             &(post_cache_loaded_group.value()->interest_group));
   histogram_tester.ExpectBucketCount(
       "Ads.InterestGroup.GetInterestGroupCacheHit", true, 1);
+
+  // After the cached value expires there should be no cached value.
+  task_environment().FastForwardBy(base::Days(2));
+  std::optional<SingleStorageInterestGroup> post_expiration_loaded_group =
+      GetInterestGroup(caching_storage.get(),
+                       blink::InterestGroupKey(ig.owner, ig.name));
+  EXPECT_FALSE(post_expiration_loaded_group.has_value());
+  histogram_tester.ExpectBucketCount(
+      "Ads.InterestGroup.GetInterestGroupCacheHit", true, 2);
 }
 
 TEST_F(InterestGroupCachingStorageTest, CacheWorksWhenPointerReleased) {
diff --git a/content/browser/preloading/prerender/prerender_host.cc b/content/browser/preloading/prerender/prerender_host.cc
index 8a030862..779e5c4 100644
--- a/content/browser/preloading/prerender/prerender_host.cc
+++ b/content/browser/preloading/prerender/prerender_host.cc
@@ -364,10 +364,6 @@
   return false;
 }
 
-WebContents* PrerenderHost::DeprecatedGetWebContents() {
-  return &*web_contents_;
-}
-
 // TODO(https://crbug.com/1132746): Inspect diffs from the current
 // no-state-prefetch implementation. See PrerenderContents::StartPrerendering()
 // for example.
diff --git a/content/browser/preloading/prerender/prerender_host.h b/content/browser/preloading/prerender/prerender_host.h
index cc7bcd7..63b0548 100644
--- a/content/browser/preloading/prerender/prerender_host.h
+++ b/content/browser/preloading/prerender/prerender_host.h
@@ -162,7 +162,6 @@
   void NotifyNavigationEntriesDeleted() override {}
   void ActivateAndShowRepostFormWarningDialog() override;
   bool ShouldPreserveAbortedURLs() override;
-  WebContents* DeprecatedGetWebContents() override;
   void UpdateOverridingUserAgent() override {}
 
   NavigationControllerImpl& GetNavigationController() {
diff --git a/content/browser/renderer_host/clipboard_host_impl.cc b/content/browser/renderer_host/clipboard_host_impl.cc
index bea7bc0..6b7f744 100644
--- a/content/browser/renderer_host/clipboard_host_impl.cc
+++ b/content/browser/renderer_host/clipboard_host_impl.cc
@@ -493,18 +493,28 @@
 }
 
 void ClipboardHostImpl::WriteText(const std::u16string& text) {
-  CopyIfAllowed(
-      text.size() * sizeof(std::u16string::value_type),
-      base::BindOnce(&ui::ScopedClipboardWriter::WriteText,
-                     base::Unretained(clipboard_writer_.get()), text));
+  GetContentClient()->browser()->IsClipboardCopyAllowedByPolicy(
+      CreateClipboardEndpoint(),
+      {
+          .size = text.size() * sizeof(std::u16string::value_type),
+          .format_type = ui::ClipboardFormatType::PlainTextType(),
+      },
+      text,
+      base::BindOnce(&ClipboardHostImpl::OnCopyTextAllowedResult,
+                     weak_ptr_factory_.GetWeakPtr()));
 }
 
 void ClipboardHostImpl::WriteHtml(const std::u16string& markup,
                                   const GURL& url) {
-  CopyIfAllowed(markup.size() * sizeof(std::u16string::value_type),
-                base::BindOnce(&ui::ScopedClipboardWriter::WriteHTML,
-                               base::Unretained(clipboard_writer_.get()),
-                               markup, url.spec()));
+  GetContentClient()->browser()->IsClipboardCopyAllowedByPolicy(
+      CreateClipboardEndpoint(),
+      {
+          .size = markup.size() * sizeof(std::u16string::value_type),
+          .format_type = ui::ClipboardFormatType::HtmlType(),
+      },
+      markup,
+      base::BindOnce(&ClipboardHostImpl::OnCopyHtmlAllowedResult,
+                     weak_ptr_factory_.GetWeakPtr(), url));
 }
 
 void ClipboardHostImpl::WriteSvg(const std::u16string& markup) {
@@ -658,18 +668,7 @@
       .IsClipboardPasteAllowedByPolicy(
           ClipboardEndpoint(ui::Clipboard::GetForCurrentThread()->GetSource(
               clipboard_buffer)),
-          ClipboardEndpoint(
-              CreateDataEndpoint().get(),
-              base::BindRepeating(
-                  [](GlobalRenderFrameHostId rfh_id) -> BrowserContext* {
-                    auto* rfh = RenderFrameHost::FromID(rfh_id);
-                    if (!rfh) {
-                      return nullptr;
-                    }
-                    return rfh->GetBrowserContext();
-                  },
-                  render_frame_host().GetGlobalId()),
-              render_frame_host()),
+          CreateClipboardEndpoint(),
           {
               .size = data_size,
               .format_type = data_type,
@@ -690,26 +689,30 @@
   request.Complete(std::move(clipboard_paste_data));
 }
 
-void ClipboardHostImpl::CopyIfAllowed(size_t data_size_in_bytes,
-                                      CopyAllowedCallback callback) {
-  GetContentClient()->browser()->IsClipboardCopyAllowedByPolicy(
-      render_frame_host().GetBrowserContext(),
-      render_frame_host().GetLastCommittedURL(), data_size_in_bytes,
-      base::BindOnce(&ClipboardHostImpl::OnCopyAllowedResult,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-}
-
-void ClipboardHostImpl::OnCopyAllowedResult(
-    CopyAllowedCallback callback,
+void ClipboardHostImpl::OnCopyTextAllowedResult(
+    const std::u16string& text,
     std::optional<std::u16string> replacement_data) {
   if (replacement_data) {
     clipboard_writer_->WriteText(std::move(*replacement_data));
   } else {
-    // Set the source of the clipboard text/html
     clipboard_writer_->SetDataSourceURL(
         render_frame_host().GetMainFrame()->GetLastCommittedURL(),
         render_frame_host().GetLastCommittedURL());
-    std::move(callback).Run();
+    clipboard_writer_->WriteText(text);
+  }
+}
+
+void ClipboardHostImpl::OnCopyHtmlAllowedResult(
+    const GURL& source_url,
+    const std::u16string& markup,
+    std::optional<std::u16string> replacement_data) {
+  if (replacement_data) {
+    clipboard_writer_->WriteText(std::move(*replacement_data));
+  } else {
+    clipboard_writer_->SetDataSourceURL(
+        render_frame_host().GetMainFrame()->GetLastCommittedURL(),
+        render_frame_host().GetLastCommittedURL());
+    clipboard_writer_->WriteHTML(markup, source_url.spec());
   }
 }
 
@@ -729,4 +732,20 @@
       render_frame_host().GetBrowserContext()->IsOffTheRecord(),
       render_frame_host().HasTransientUserActivation());
 }
+
+ClipboardEndpoint ClipboardHostImpl::CreateClipboardEndpoint() {
+  return ClipboardEndpoint(
+      CreateDataEndpoint().get(),
+      base::BindRepeating(
+          [](GlobalRenderFrameHostId rfh_id) -> BrowserContext* {
+            auto* rfh = RenderFrameHost::FromID(rfh_id);
+            if (!rfh) {
+              return nullptr;
+            }
+            return rfh->GetBrowserContext();
+          },
+          render_frame_host().GetGlobalId()),
+      render_frame_host());
+}
+
 }  // namespace content
diff --git a/content/browser/renderer_host/clipboard_host_impl.h b/content/browser/renderer_host/clipboard_host_impl.h
index 33ac93a..c761b9f 100644
--- a/content/browser/renderer_host/clipboard_host_impl.h
+++ b/content/browser/renderer_host/clipboard_host_impl.h
@@ -212,15 +212,18 @@
   bool IsRendererPasteAllowed(ui::ClipboardBuffer clipboard_buffer,
                               RenderFrameHost& render_frame_host);
 
-  using CopyAllowedCallback = base::OnceCallback<void()>;
-  void CopyIfAllowed(size_t data_size_in_bytes, CopyAllowedCallback callback);
+  // Helpers to be used when checking if data is allowed to be copied.
+  // If `replacement_data` is null, `clipboard_writer_` will be used to write
+  // the corresponding text/markup data to the clipboard. If it is not, instead
+  // write the replacement string to the clipboard as plaintext. This can be
+  // called asynchronously.
+  void OnCopyTextAllowedResult(const std::u16string& text,
+                               std::optional<std::u16string> replacement_data);
+  void OnCopyHtmlAllowedResult(const GURL& url,
+                               const std::u16string& markup,
+                               std::optional<std::u16string> replacement_data);
 
-  // If `replacement_data` is null, calls `callback` to copy data to the
-  // clipboard. If it is not, instead write the replacement string to the
-  // clipboard. This can be called asynchronously and should only be called by
-  // `CopyIfAllowed`.
-  void OnCopyAllowedResult(CopyAllowedCallback callback,
-                           std::optional<std::u16string> replacement_data);
+  using CopyAllowedCallback = base::OnceCallback<void()>;
 
   void OnReadPng(ui::ClipboardBuffer clipboard_buffer,
                  ReadPngCallback callback,
@@ -229,6 +232,9 @@
   // Creates a `ui::DataTransferEndpoint` representing the last committed URL.
   std::unique_ptr<ui::DataTransferEndpoint> CreateDataEndpoint();
 
+  // Creates a `content::ClipboardEndpoint` representing the last committed URL.
+  ClipboardEndpoint CreateClipboardEndpoint();
+
   std::unique_ptr<ui::ScopedClipboardWriter> clipboard_writer_;
 
   // Outstanding is allowed requests per clipboard contents.  Maps a clipboard
diff --git a/content/browser/renderer_host/code_cache_host_impl.cc b/content/browser/renderer_host/code_cache_host_impl.cc
index 985dc00..b083cd8 100644
--- a/content/browser/renderer_host/code_cache_host_impl.cc
+++ b/content/browser/renderer_host/code_cache_host_impl.cc
@@ -26,6 +26,7 @@
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "net/base/io_buffer.h"
 #include "third_party/blink/public/common/cache_storage/cache_storage_utils.h"
+#include "third_party/blink/public/common/scheme_registry.h"
 #include "url/gurl.h"
 #include "url/origin.h"
 
@@ -68,7 +69,8 @@
     return process_lock.matches_scheme(content::kChromeUIScheme) ||
            process_lock.matches_scheme(content::kChromeUIUntrustedScheme);
   }
-  if (resource_url.SchemeIsHTTPOrHTTPS()) {
+  if (resource_url.SchemeIsHTTPOrHTTPS() ||
+      blink::CommonSchemeRegistry::IsExtensionScheme(resource_url.scheme())) {
     if (process_lock.matches_scheme(content::kChromeUIScheme) ||
         process_lock.matches_scheme(content::kChromeUIUntrustedScheme)) {
       // It is possible for WebUI pages to include open-web content, but such
@@ -422,7 +424,9 @@
   if (process_lock.matches_scheme(url::kHttpScheme) ||
       process_lock.matches_scheme(url::kHttpsScheme) ||
       process_lock.matches_scheme(content::kChromeUIScheme) ||
-      process_lock.matches_scheme(content::kChromeUIUntrustedScheme)) {
+      process_lock.matches_scheme(content::kChromeUIUntrustedScheme) ||
+      blink::CommonSchemeRegistry::IsExtensionScheme(
+          process_lock.lock_url().scheme())) {
     return process_lock.lock_url();
   }
 
diff --git a/content/browser/renderer_host/navigation_controller_delegate.h b/content/browser/renderer_host/navigation_controller_delegate.h
index d0da3ab..7a26081 100644
--- a/content/browser/renderer_host/navigation_controller_delegate.h
+++ b/content/browser/renderer_host/navigation_controller_delegate.h
@@ -14,7 +14,6 @@
 namespace content {
 
 struct LoadCommittedDetails;
-class WebContents;
 
 // Interface for objects embedding a NavigationController to provide the
 // functionality NavigationController needs.
@@ -42,10 +41,6 @@
   // preserved in the omnibox.  Defaults to false.
   virtual bool ShouldPreserveAbortedURLs() = 0;
 
-  // TODO(crbug.com/1225205): Remove this. It is a layering violation as
-  // renderer_host/ cannot depend on WebContents.
-  virtual WebContents* DeprecatedGetWebContents() = 0;
-
   virtual void UpdateOverridingUserAgent() = 0;
 };
 
diff --git a/content/browser/renderer_host/navigation_controller_impl.cc b/content/browser/renderer_host/navigation_controller_impl.cc
index 84abbc4..02c86ec 100644
--- a/content/browser/renderer_host/navigation_controller_impl.cc
+++ b/content/browser/renderer_host/navigation_controller_impl.cc
@@ -818,10 +818,6 @@
   DiscardNonCommittedEntries();
 }
 
-WebContents* NavigationControllerImpl::DeprecatedGetWebContents() {
-  return delegate_->DeprecatedGetWebContents();
-}
-
 BrowserContext* NavigationControllerImpl::GetBrowserContext() {
   return browser_context_;
 }
diff --git a/content/browser/renderer_host/navigation_controller_impl.h b/content/browser/renderer_host/navigation_controller_impl.h
index 7bbdaf88..4b2ac2d0 100644
--- a/content/browser/renderer_host/navigation_controller_impl.h
+++ b/content/browser/renderer_host/navigation_controller_impl.h
@@ -98,7 +98,6 @@
   ~NavigationControllerImpl() override;
 
   // NavigationController implementation:
-  WebContents* DeprecatedGetWebContents() override;
   BrowserContext* GetBrowserContext() override;
   void Restore(int selected_navigation,
                RestoreType type,
diff --git a/content/browser/resources/attribution_reporting/attribution_internals.ts b/content/browser/resources/attribution_reporting/attribution_internals.ts
index cc63b4e..f253808 100644
--- a/content/browser/resources/attribution_reporting/attribution_internals.ts
+++ b/content/browser/resources/attribution_reporting/attribution_internals.ts
@@ -51,6 +51,18 @@
   };
 }
 
+function compareLexicographic<V>(f: CompareFunc<V>): CompareFunc<V[]> {
+  return (a: V[], b: V[]): number => {
+    for (let i = 0; i < a.length && i < b.length; ++i) {
+      const r = f(a[i]!, b[i]!);
+      if (r !== 0) {
+        return r;
+      }
+    }
+    return compareDefault(a.length, b.length);
+  };
+}
+
 function bigintReplacer(_key: string, value: any): any {
   return typeof value === 'bigint' ? value.toString() : value;
 }
@@ -137,13 +149,21 @@
 }
 
 class ListColumn<T, V> extends ValueColumn<T, V[]> {
+  readonly compare?: (a: T, b: T) => number;
+
   constructor(
       header: string,
       getValue: (row: T) => V[],
       private readonly renderItem: RenderFunc<V> = setInnerText,
       private readonly tdClass?: string,
+      compareValues?: CompareFunc<V>,
   ) {
     super(header, getValue);
+
+    if (compareValues) {
+      const cmp = compareLexicographic(compareValues);
+      this.compare = (a, b) => cmp(this.getValue(a), this.getValue(b));
+    }
   }
 
   override render(td: HTMLElement, row: T): void {
@@ -359,7 +379,9 @@
           numberColumn('Source Event ID', (e) => e.sourceEventId),
           stringOrBoolColumn('Status', (e) => e.status),
           urlColumn('Source Origin', (e) => e.sourceOrigin),
-          new ListColumn('Destinations', (e) => e.destinations, renderUrl),
+          new ListColumn(
+              'Destinations', (e) => e.destinations, renderUrl,
+              /*tdClass=*/ undefined, compareDefault),
           urlColumn('Reporting Origin', (e) => e.reportingOrigin),
           dateColumn('Registration Time', (e) => e.sourceTime),
           dateColumn('Expiry Time', (e) => e.expiryTime),
diff --git a/content/browser/tracing/trace_report/trace_report_database.cc b/content/browser/tracing/trace_report/trace_report_database.cc
index 196cb40..613f53a 100644
--- a/content/browser/tracing/trace_report/trace_report_database.cc
+++ b/content/browser/tracing/trace_report/trace_report_database.cc
@@ -82,9 +82,7 @@
 ClientTraceReport::~ClientTraceReport() = default;
 
 TraceReportDatabase::TraceReportDatabase()
-    : database_(sql::DatabaseOptions{.exclusive_locking = true,
-                                     .page_size = 4096,
-                                     .cache_size = 128}) {
+    : database_(sql::DatabaseOptions{.page_size = 4096, .cache_size = 128}) {
   DETACH_FROM_SEQUENCE(sequence_checker_);
 }
 
diff --git a/content/browser/tracing/tracing_end_to_end_browsertest.cc b/content/browser/tracing/tracing_end_to_end_browsertest.cc
index 04f4c41..808411ba 100644
--- a/content/browser/tracing/tracing_end_to_end_browsertest.cc
+++ b/content/browser/tracing/tracing_end_to_end_browsertest.cc
@@ -572,7 +572,8 @@
   // connecting to the service), but it doesn't matter. We just want to make
   // sure that at least one of them is there.
   std::vector<char> trace;
-  for (size_t i = 0; i < 100; i++) {
+  size_t i = 0;
+  for (; i < 300; i++) {
     EXPECT_TRUE(ExecJs(tab, "performance.mark('mark1');"));
 
     base::RunLoop flush;
@@ -588,6 +589,7 @@
       break;
     }
   }
+  ASSERT_LT(i, 300U);
 
   base::test::TestTraceProcessorImpl ttp;
   absl::Status status = ttp.ParseTrace(trace);
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index bc23b09d..5413cfa 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -3405,10 +3405,6 @@
   return tc;
 }
 
-WebContents* WebContentsImpl::DeprecatedGetWebContents() {
-  return this;
-}
-
 void WebContentsImpl::Init(const WebContents::CreateParams& params,
                            blink::FramePolicy primary_main_frame_policy) {
   TRACE_EVENT0("content", "WebContentsImpl::Init");
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 8e012756..93e0df9 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -1101,7 +1101,6 @@
 
   // NavigationControllerDelegate ----------------------------------------------
 
-  WebContents* DeprecatedGetWebContents() override;
   void NotifyNavigationEntryCommitted(
       const LoadCommittedDetails& load_details) override;
   void NotifyNavigationEntryChanged(
diff --git a/content/browser/webid/fake_identity_request_dialog_controller.cc b/content/browser/webid/fake_identity_request_dialog_controller.cc
index 805b06f..f8bdd2d7 100644
--- a/content/browser/webid/fake_identity_request_dialog_controller.cc
+++ b/content/browser/webid/fake_identity_request_dialog_controller.cc
@@ -28,7 +28,8 @@
     bool show_auto_reauthn_checkbox,
     AccountSelectionCallback on_selected,
     LoginToIdPCallback on_add_account,
-    DismissCallback dismiss_callback) {
+    DismissCallback dismiss_callback,
+    AccountsDisplayedCallback accounts_displayed_callback) {
   // TODO(crbug.com/1348262): Temporarily support only the first IDP, extend to
   // support multiple IDPs.
   std::vector<IdentityRequestAccount> accounts =
diff --git a/content/browser/webid/fake_identity_request_dialog_controller.h b/content/browser/webid/fake_identity_request_dialog_controller.h
index 83ad8940..6fa68758 100644
--- a/content/browser/webid/fake_identity_request_dialog_controller.h
+++ b/content/browser/webid/fake_identity_request_dialog_controller.h
@@ -37,7 +37,8 @@
       bool show_auto_reauthn_checkbox,
       AccountSelectionCallback on_selected,
       LoginToIdPCallback on_add_account,
-      DismissCallback dismiss_callback) override;
+      DismissCallback dismmiss_callback,
+      AccountsDisplayedCallback accounts_displayed_callback) override;
 
   void ShowFailureDialog(const std::string& top_frame_for_display,
                          const std::optional<std::string>& iframe_for_display,
diff --git a/content/browser/webid/fedcm_metrics.cc b/content/browser/webid/fedcm_metrics.cc
index 4533c85..c863e4b 100644
--- a/content/browser/webid/fedcm_metrics.cc
+++ b/content/browser/webid/fedcm_metrics.cc
@@ -285,11 +285,13 @@
   base::UmaHistogramBoolean("Blink.FedCm.IsSignInUser", is_sign_in);
 }
 
-void FedCmMetrics::RecordWebContentsVisibilityUponReadyToShowDialog(
-    bool is_visible) {
+void FedCmMetrics::RecordWebContentsStatusUponReadyToShowDialog(
+    bool is_visible,
+    bool is_active) {
   if (is_disabled_)
     return;
   base::UmaHistogramBoolean("Blink.FedCm.WebContentsVisible", is_visible);
+  base::UmaHistogramBoolean("Blink.FedCm.WebContentsActive", is_active);
 }
 
 void FedCmMetrics::RecordAutoReauthnMetrics(
diff --git a/content/browser/webid/fedcm_metrics.h b/content/browser/webid/fedcm_metrics.h
index bf0f561..b4933f3 100644
--- a/content/browser/webid/fedcm_metrics.h
+++ b/content/browser/webid/fedcm_metrics.h
@@ -241,9 +241,10 @@
   // Records whether the user selected account is for sign-in or not.
   void RecordIsSignInUser(bool is_sign_in);
 
-  // Records whether a user has left the page where the API is called when the
-  // browser is ready to show the accounts dialog.
-  void RecordWebContentsVisibilityUponReadyToShowDialog(bool is_visible);
+  // Records whether the frame is visible or active upon ready to show accounts
+  // UI.
+  void RecordWebContentsStatusUponReadyToShowDialog(bool is_visible,
+                                                    bool is_active);
 
   // This enum is used in histograms. Do not remove or modify existing entries.
   // You may add entries at the end, and update |kMaxValue|.
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index 0377ce68..de4fadb 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -421,6 +421,10 @@
   return iframe_for_display;
 }
 
+bool IsFrameActive(RenderFrameHost* frame) {
+  return frame && frame->IsActive();
+}
+
 bool IsFrameVisible(RenderFrameHost* frame) {
   return frame && frame->IsActive() &&
          frame->GetVisibilityState() == content::PageVisibilityState::kVisible;
@@ -1583,8 +1587,7 @@
   // The RenderFrameHost may be alive but not visible in the following
   // situations:
   // Situation #1: User switched tabs
-  // Situation #2: User navigated the page. The RenderFrameHost is still
-  //   alive thanks to the BFCache.
+  // Situation #2: User navigated the page with bfcache
   //
   // - If this fetch is as a result of an IdP sign-in status change, the FedCM
   // dialog is either visible or temporarily hidden. Update the contents of
@@ -1593,11 +1596,11 @@
   // if the RenderFrameHost is hidden because the user does not seem interested
   // in the contents of the current page.
   if (!fetch_data_.for_idp_signin) {
-    bool is_visible = IsFrameVisible(render_frame_host().GetMainFrame());
-    fedcm_metrics_->RecordWebContentsVisibilityUponReadyToShowDialog(
-        is_visible);
+    bool is_active = IsFrameActive(render_frame_host().GetMainFrame());
+    fedcm_metrics_->RecordWebContentsStatusUponReadyToShowDialog(
+        IsFrameVisible(render_frame_host().GetMainFrame()), is_active);
 
-    if (!is_visible) {
+    if (!is_active) {
       CompleteRequestWithError(
           FederatedAuthRequestResult::kErrorRpPageNotVisible,
           TokenStatus::kRpPageNotVisible,
@@ -1606,9 +1609,9 @@
       return;
     }
 
-    show_accounts_dialog_time_ = base::TimeTicks::Now();
-    fedcm_metrics_->RecordShowAccountsDialogTime(show_accounts_dialog_time_ -
-                                                 start_time_);
+    ready_to_display_accounts_dialog_time_ = base::TimeTicks::Now();
+    fedcm_metrics_->RecordShowAccountsDialogTime(
+        ready_to_display_accounts_dialog_time_ - start_time_);
   }
 
   fetch_data_ = FetchData();
@@ -1655,6 +1658,8 @@
                      weak_ptr_factory_.GetWeakPtr(),
                      /*can_append_hints=*/false),
       base::BindOnce(&FederatedAuthRequestImpl::OnDialogDismissed,
+                     weak_ptr_factory_.GetWeakPtr()),
+      base::BindOnce(&FederatedAuthRequestImpl::OnAccountsDisplayed,
                      weak_ptr_factory_.GetWeakPtr()));
   devtools_instrumentation::DidShowFedCmDialog(render_frame_host());
 
@@ -1672,6 +1677,10 @@
   fedcm_metrics_->RecordAccountsDialogShown();
 }
 
+void FederatedAuthRequestImpl::OnAccountsDisplayed() {
+  accounts_dialog_display_time_ = base::TimeTicks::Now();
+}
+
 void FederatedAuthRequestImpl::HandleAccountsFetchFailure(
     std::unique_ptr<IdentityProviderInfo> idp_info,
     std::optional<bool> old_idp_signin_status,
@@ -1694,7 +1703,7 @@
     return;
   }
 
-  if (!IsFrameVisible(render_frame_host().GetMainFrame())) {
+  if (!IsFrameActive(render_frame_host().GetMainFrame())) {
     CompleteRequestWithError(FederatedAuthRequestResult::kErrorRpPageNotVisible,
                              TokenStatus::kRpPageNotVisible,
                              /*token_error=*/std::nullopt,
@@ -2012,7 +2021,7 @@
   account_id_ = account_id;
   select_account_time_ = base::TimeTicks::Now();
   fedcm_metrics_->RecordContinueOnDialogTime(select_account_time_ -
-                                             show_accounts_dialog_time_);
+                                             accounts_dialog_display_time_);
 
   network_manager_->SendTokenRequest(
       idp_info.endpoints.token, account_id_,
@@ -2117,7 +2126,7 @@
   if (should_embargo) {
     base::TimeTicks dismiss_dialog_time = base::TimeTicks::Now();
     fedcm_metrics_->RecordCancelOnDialogTime(dismiss_dialog_time -
-                                             show_accounts_dialog_time_);
+                                             accounts_dialog_display_time_);
   }
   fedcm_metrics_->RecordCancelReason(dismiss_reason);
 
@@ -2349,7 +2358,9 @@
 
       fedcm_metrics_->RecordTokenResponseAndTurnaroundTime(
           token_response_time_ - select_account_time_,
-          token_response_time_ - start_time_);
+          token_response_time_ - start_time_ -
+              (accounts_dialog_display_time_ -
+               ready_to_display_accounts_dialog_time_));
 
       if (IsFedCmMetricsEndpointEnabled()) {
         for (const auto& metrics_endpoint_kv : metrics_endpoints_) {
@@ -2360,10 +2371,13 @@
 
           if (metrics_endpoint_kv.first == idp_config_url) {
             network_manager_->SendSuccessfulTokenRequestMetrics(
-                metrics_endpoint, show_accounts_dialog_time_ - start_time_,
-                select_account_time_ - show_accounts_dialog_time_,
+                metrics_endpoint,
+                ready_to_display_accounts_dialog_time_ - start_time_,
+                select_account_time_ - accounts_dialog_display_time_,
                 token_response_time_ - select_account_time_,
-                token_response_time_ - start_time_);
+                token_response_time_ - start_time_ -
+                    (accounts_dialog_display_time_ -
+                     ready_to_display_accounts_dialog_time_));
           } else {
             // Send kUserFailure so that IDP cannot tell difference between user
             // selecting a different IDP and user dismissing dialog without
@@ -2510,7 +2524,8 @@
   provider_fetcher_.reset();
   account_id_ = std::string();
   start_time_ = base::TimeTicks();
-  show_accounts_dialog_time_ = base::TimeTicks();
+  ready_to_display_accounts_dialog_time_ = base::TimeTicks();
+  accounts_dialog_display_time_ = base::TimeTicks();
   select_account_time_ = base::TimeTicks();
   token_response_time_ = base::TimeTicks();
   accounts_dialog_shown_time_ = std::nullopt;
diff --git a/content/browser/webid/federated_auth_request_impl.h b/content/browser/webid/federated_auth_request_impl.h
index 48d2c7a..0c474ab 100644
--- a/content/browser/webid/federated_auth_request_impl.h
+++ b/content/browser/webid/federated_auth_request_impl.h
@@ -257,6 +257,7 @@
   // Called when we should show a failure dialog in the case where a single IDP
   // account fetch resulted in a mismatch with its login status.
   void ShowSingleIdpFailureDialog();
+  void OnAccountsDisplayed();
 
   // Updates the IdpSigninStatus in case of accounts fetch failure and shows a
   // failure UI if applicable.
@@ -424,7 +425,8 @@
   // mediation flow.
   std::string account_id_;
   base::TimeTicks start_time_;
-  base::TimeTicks show_accounts_dialog_time_;
+  base::TimeTicks ready_to_display_accounts_dialog_time_;
+  base::TimeTicks accounts_dialog_display_time_;
   base::TimeTicks select_account_time_;
   base::TimeTicks token_response_time_;
   bool errors_logged_to_console_{false};
diff --git a/content/browser/webid/federated_auth_request_impl_multiple_frames_unittest.cc b/content/browser/webid/federated_auth_request_impl_multiple_frames_unittest.cc
index 4a13c6a..f9d4b13 100644
--- a/content/browser/webid/federated_auth_request_impl_multiple_frames_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_multiple_frames_unittest.cc
@@ -169,8 +169,9 @@
       bool show_auto_reauthn_checkbox,
       IdentityRequestDialogController::AccountSelectionCallback on_selected,
       IdentityRequestDialogController::LoginToIdPCallback on_add_account,
-      IdentityRequestDialogController::DismissCallback dismiss_callback)
-      override {
+      IdentityRequestDialogController::DismissCallback dismiss_callback,
+      IdentityRequestDialogController::AccountsDisplayedCallback
+          accounts_displayed_callback) override {
     state_->did_show_accounts_dialog = true;
     state_->top_frame_for_display = top_frame_for_display;
     state_->iframe_for_display = iframe_for_display;
diff --git a/content/browser/webid/federated_auth_request_impl_unittest.cc b/content/browser/webid/federated_auth_request_impl_unittest.cc
index 73b438d..829307e 100644
--- a/content/browser/webid/federated_auth_request_impl_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_unittest.cc
@@ -679,8 +679,9 @@
       bool show_auto_reauthn_checkbox,
       IdentityRequestDialogController::AccountSelectionCallback on_selected,
       IdentityRequestDialogController::LoginToIdPCallback on_add_account,
-      IdentityRequestDialogController::DismissCallback dismiss_callback)
-      override {
+      IdentityRequestDialogController::DismissCallback dismiss_callback,
+      IdentityRequestDialogController::AccountsDisplayedCallback
+          accounts_displayed_callback) override {
     if (!state_) {
       return;
     }
@@ -2809,9 +2810,11 @@
   EXPECT_EQ(LoginState::kSignIn, displayed_accounts()[0].login_state);
 
   histogram_tester_.ExpectUniqueSample("Blink.FedCm.WebContentsVisible", 1, 1);
+  histogram_tester_.ExpectUniqueSample("Blink.FedCm.WebContentsActive", 1, 1);
 }
 
-// Test that request fails if the web contents are hidden.
+// Test that request could succeed with auto re-authn even if the web contents
+// invisible.
 TEST_F(FederatedAuthRequestImplTest, MetricsForWebContentsInvisible) {
   base::HistogramTester histogram_tester;
   test_rvh()->SimulateWasShown();
@@ -2823,16 +2826,19 @@
   ASSERT_NE(test_rvh()->GetMainRenderFrameHost()->GetVisibilityState(),
             content::PageVisibilityState::kVisible);
 
-  RequestExpectations expectations = {
-      RequestTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorRpPageNotVisible,
-      /*standalone_console_message=*/std::nullopt,
-      /*selected_idp_config_url=*/std::nullopt};
-  RunAuthTest(kDefaultRequestParameters, expectations, kConfigurationValid);
-  EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
-  EXPECT_FALSE(did_show_accounts_dialog());
+  // Pretends that the sharing permission has been granted for this account.
+  EXPECT_CALL(*test_permission_delegate_,
+              HasSharingPermission(_, _, OriginFromString(kProviderUrlFull),
+                                   Optional(std::string(kAccountId))))
+      .Times(2)
+      .WillRepeatedly(Return(true));
+
+  RunAuthTest(kDefaultRequestParameters, kExpectationSuccess,
+              kConfigurationValid);
+  EXPECT_EQ(LoginState::kSignIn, displayed_accounts()[0].login_state);
 
   histogram_tester_.ExpectUniqueSample("Blink.FedCm.WebContentsVisible", 0, 1);
+  histogram_tester_.ExpectUniqueSample("Blink.FedCm.WebContentsActive", 1, 1);
 }
 
 TEST_F(FederatedAuthRequestImplTest, MetricsForFeatureIsDisabled) {
@@ -3130,8 +3136,9 @@
       bool show_auto_reauthn_checkbox,
       IdentityRequestDialogController::AccountSelectionCallback on_selected,
       IdentityRequestDialogController::LoginToIdPCallback on_add_account,
-      IdentityRequestDialogController::DismissCallback dismiss_callback)
-      override {
+      IdentityRequestDialogController::DismissCallback dismiss_callback,
+      IdentityRequestDialogController::AccountsDisplayedCallback
+          accounts_displayed_callback) override {
     // Disable FedCM API
     api_permission_delegate_->permission_override_ = std::make_pair(
         rp_origin_to_disable_, ApiPermissionStatus::BLOCKED_SETTINGS);
@@ -3141,7 +3148,8 @@
         top_frame_for_display, iframe_for_display,
         std::move(identity_provider_data), sign_in_mode, rp_mode,
         show_auto_reauthn_checkbox, std::move(on_selected),
-        std::move(on_add_account), std::move(dismiss_callback));
+        std::move(on_add_account), std::move(dismiss_callback),
+        std::move(accounts_displayed_callback));
   }
 
  private:
@@ -3458,6 +3466,8 @@
   RunAuthTest(kDefaultRequestParameters, expectations, kConfigurationValid);
   EXPECT_TRUE(DidFetch(FetchedEndpoint::ACCOUNTS));
   EXPECT_FALSE(did_show_accounts_dialog());
+
+  histogram_tester_.ExpectUniqueSample("Blink.FedCm.WebContentsActive", 0, 1);
 }
 
 // Test that the account chooser is not shown if the page navigates prior to the
diff --git a/content/browser/webid/test/mock_identity_request_dialog_controller.h b/content/browser/webid/test/mock_identity_request_dialog_controller.h
index a34340a..4196913 100644
--- a/content/browser/webid/test/mock_identity_request_dialog_controller.h
+++ b/content/browser/webid/test/mock_identity_request_dialog_controller.h
@@ -23,16 +23,17 @@
   MockIdentityRequestDialogController& operator=(
       const MockIdentityRequestDialogController&) = delete;
 
-  MOCK_METHOD9(ShowAccountsDialog,
-               void(const std::string&,
-                    const std::optional<std::string>&,
-                    const std::vector<content::IdentityProviderData>&,
-                    IdentityRequestAccount::SignInMode,
-                    blink::mojom::RpMode rp_mode,
-                    bool,
-                    AccountSelectionCallback,
-                    LoginToIdPCallback,
-                    DismissCallback));
+  MOCK_METHOD10(ShowAccountsDialog,
+                void(const std::string&,
+                     const std::optional<std::string>&,
+                     const std::vector<content::IdentityProviderData>&,
+                     IdentityRequestAccount::SignInMode,
+                     blink::mojom::RpMode rp_mode,
+                     bool,
+                     AccountSelectionCallback,
+                     LoginToIdPCallback,
+                     DismissCallback,
+                     AccountsDisplayedCallback));
   MOCK_METHOD0(DestructorCalled, void());
   MOCK_METHOD8(ShowFailureDialog,
                void(const std::string&,
diff --git a/content/browser/webid/webid_browsertest.cc b/content/browser/webid/webid_browsertest.cc
index ffd64a2..0f37a5bb 100644
--- a/content/browser/webid/webid_browsertest.cc
+++ b/content/browser/webid/webid_browsertest.cc
@@ -1174,7 +1174,7 @@
           test_browser_client_->GetIdentityRequestDialogControllerForTests());
 
   // Expects the account chooser to be opened. Selects the first account.
-  EXPECT_CALL(*controller, ShowAccountsDialog(_, _, _, _, _, _, _, _, _))
+  EXPECT_CALL(*controller, ShowAccountsDialog(_, _, _, _, _, _, _, _, _, _))
       .WillOnce(::testing::WithArg<6>([&config_url](auto on_selected) {
         std::move(on_selected)
             .Run(config_url,
diff --git a/content/public/android/java/src/org/chromium/content_public/common/ResourceRequestBody.java b/content/public/android/java/src/org/chromium/content_public/common/ResourceRequestBody.java
index 9da4422..2bd510c 100644
--- a/content/public/android/java/src/org/chromium/content_public/common/ResourceRequestBody.java
+++ b/content/public/android/java/src/org/chromium/content_public/common/ResourceRequestBody.java
@@ -4,6 +4,8 @@
 
 package org.chromium.content_public.common;
 
+import androidx.annotation.VisibleForTesting;
+
 import org.jni_zero.CalledByNative;
 import org.jni_zero.JNINamespace;
 import org.jni_zero.NativeMethods;
@@ -40,8 +42,9 @@
         return new ResourceRequestBody(encodedNativeForm);
     }
 
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
     @CalledByNative
-    private byte[] getEncodedNativeForm() {
+    public byte[] getEncodedNativeForm() {
         return mEncodedNativeForm;
     }
 
@@ -57,8 +60,9 @@
         return createFromEncodedNativeForm(encodedNativeForm);
     }
 
+    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
     @NativeMethods
-    interface Natives {
+    public interface Natives {
         /**
          * Equivalent of the native content::ResourceRequestBody::CreateFromBytes.
          *
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 008246a..a4571b5f 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -1416,11 +1416,11 @@
 }
 
 void ContentBrowserClient::IsClipboardCopyAllowedByPolicy(
-    content::BrowserContext* browser_context,
-    const GURL& url,
-    size_t data_size_in_bytes,
+    const ClipboardEndpoint& source,
+    const ClipboardMetadata& metadata,
+    const std::u16string& data,
     IsClipboardCopyAllowedCallback callback) {
-  std::move(callback).Run(std::move(std::nullopt));
+  std::move(callback).Run(data, std::nullopt);
 }
 
 #if BUILDFLAG(ENABLE_VR)
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 31fc85dc..3799887e 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -297,10 +297,12 @@
       std::optional<ClipboardPasteData> clipboard_paste_data)>;
 
   // Callback used with the `IsClipboardCopyAllowedByPolicy()` method.
-  // If the copy is allowed, nullopt is passed to the callback. Otherwise, the
-  // data that should be put in the clipboard instead is passed to the callback.
+  // If the copy is allowed, nullopt is passed to the callback and `data` is
+  // expected to be copied. Otherwise, `replacement_data` should be written in
+  // plaintext to the clipboard.
   using IsClipboardCopyAllowedCallback =
-      base::OnceCallback<void(std::optional<std::u16string> replacement_data)>;
+      base::OnceCallback<void(const std::u16string& data,
+                              std::optional<std::u16string> replacement_data)>;
 
   virtual ~ContentBrowserClient() = default;
 
@@ -2473,9 +2475,9 @@
   // implementation might show UX to the user and call `callback`
   // asynchronously.
   virtual void IsClipboardCopyAllowedByPolicy(
-      content::BrowserContext* browser_context,
-      const GURL& url,
-      size_t data_size_in_bytes,
+      const ClipboardEndpoint& source,
+      const ClipboardMetadata& metadata,
+      const std::u16string& data,
       IsClipboardCopyAllowedCallback callback);
 
 #if BUILDFLAG(ENABLE_VR)
diff --git a/content/public/browser/identity_request_dialog_controller.cc b/content/public/browser/identity_request_dialog_controller.cc
index 28fb145c..8b5b0ad 100644
--- a/content/public/browser/identity_request_dialog_controller.cc
+++ b/content/public/browser/identity_request_dialog_controller.cc
@@ -64,7 +64,8 @@
     bool show_auto_reauthn_checkbox,
     AccountSelectionCallback on_selected,
     LoginToIdPCallback on_add_account,
-    DismissCallback dismiss_callback) {
+    DismissCallback dismiss_callback,
+    AccountsDisplayedCallback accounts_displayed_callback) {
   if (!is_interception_enabled_) {
     std::move(dismiss_callback).Run(DismissReason::kOther);
   }
diff --git a/content/public/browser/identity_request_dialog_controller.h b/content/public/browser/identity_request_dialog_controller.h
index 471b8d1..56356cb 100644
--- a/content/public/browser/identity_request_dialog_controller.h
+++ b/content/public/browser/identity_request_dialog_controller.h
@@ -111,6 +111,7 @@
       base::OnceCallback<void(const GURL& /*idp_config_url*/,
                               GURL /*idp_login_url*/)>;
   using MoreDetailsCallback = base::OnceCallback<void()>;
+  using AccountsDisplayedCallback = base::OnceCallback<void()>;
 
   IdentityRequestDialogController() = default;
 
@@ -144,7 +145,8 @@
       bool show_auto_reauthn_checkbox,
       AccountSelectionCallback on_selected,
       LoginToIdPCallback on_add_account,
-      DismissCallback dismiss_callback);
+      DismissCallback dismiss_callback,
+      AccountsDisplayedCallback accounts_displayed_callback);
 
   // Shows a failure UI when the accounts fetch is failed such that it is
   // observable by users. This could happen when an IDP claims that the user is
diff --git a/content/public/browser/navigation_controller.h b/content/public/browser/navigation_controller.h
index 32c5518..d941f1e 100644
--- a/content/public/browser/navigation_controller.h
+++ b/content/public/browser/navigation_controller.h
@@ -49,7 +49,6 @@
 class BrowserContext;
 class NavigationEntry;
 class RenderFrameHost;
-class WebContents;
 class NavigationHandle;
 struct OpenURLParams;
 
@@ -335,13 +334,6 @@
 
   virtual ~NavigationController() {}
 
-  // Returns the web contents associated with this controller. It can never be
-  // nullptr.
-  //
-  // TODO(crbug.com/1225205): Remove this. It is a layering violation as it is
-  // implemented in renderer_host/ which cannot depend on WebContents.
-  virtual WebContents* DeprecatedGetWebContents() = 0;
-
   // Get the browser context for this controller. It can never be nullptr.
   virtual BrowserContext* GetBrowserContext() = 0;
 
diff --git a/content/services/auction_worklet/bidder_lazy_filler.cc b/content/services/auction_worklet/bidder_lazy_filler.cc
index cecbf3be..0311352 100644
--- a/content/services/auction_worklet/bidder_lazy_filler.cc
+++ b/content/services/auction_worklet/bidder_lazy_filler.cc
@@ -4,14 +4,21 @@
 
 #include "content/services/auction_worklet/bidder_lazy_filler.h"
 
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "base/functional/callback.h"
 #include "content/services/auction_worklet/auction_v8_helper.h"
 #include "content/services/auction_worklet/auction_v8_logger.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
 #include "gin/converter.h"
 #include "gin/dictionary.h"
+#include "third_party/blink/public/common/interest_group/interest_group.h"
 #include "v8/include/v8-exception.h"
 #include "v8/include/v8-external.h"
 #include "v8/include/v8-json.h"
+#include "v8/include/v8-template.h"
 
 namespace auction_worklet {
 
@@ -124,7 +131,11 @@
   bidder_worklet_non_shared_params_ = bidder_worklet_non_shared_params;
 }
 
-bool InterestGroupLazyFiller::FillInObject(v8::Local<v8::Object> object) {
+bool InterestGroupLazyFiller::FillInObject(
+    v8::Local<v8::Object> object,
+    base::RepeatingCallback<bool(const std::string&)> is_ad_excluded,
+    base::RepeatingCallback<bool(const std::string&)>
+        is_ad_component_excluded) {
   if (bidder_worklet_non_shared_params_->user_bidding_signals &&
       !DefineLazyAttribute(object, "userBiddingSignals",
                            &HandleUserBiddingSignals)) {
@@ -169,6 +180,21 @@
                            &HandleUseBiddingSignalsPrioritization)) {
     return false;
   }
+
+  v8::Local<v8::ObjectTemplate> lazy_filler_template;
+  if (bidder_worklet_non_shared_params_->ads &&
+      !CreateAdVector(object, "ads", is_ad_excluded,
+                      *bidder_worklet_non_shared_params_->ads,
+                      lazy_filler_template)) {
+    return false;
+  }
+  if (bidder_worklet_non_shared_params_->ad_components &&
+      !CreateAdVector(object, "adComponents", is_ad_component_excluded,
+                      *bidder_worklet_non_shared_params_->ad_components,
+                      lazy_filler_template)) {
+    return false;
+  }
+
   return true;
 }
 
@@ -179,6 +205,43 @@
   bidder_worklet_non_shared_params_ = nullptr;
 }
 
+bool InterestGroupLazyFiller::CreateAdVector(
+    v8::Local<v8::Object>& object,
+    std::string_view name,
+    base::RepeatingCallback<bool(const std::string&)> is_ad_excluded,
+    const std::vector<blink::InterestGroup::Ad>& ads,
+    v8::Local<v8::ObjectTemplate>& lazy_filler_template) {
+  v8::Isolate* isolate = v8_helper()->isolate();
+
+  v8::LocalVector<v8::Value> ads_vector(isolate);
+  for (const auto& ad : ads) {
+    if (is_ad_excluded.Run(ad.render_url())) {
+      continue;
+    }
+    v8::Local<v8::Object> ad_object = v8::Object::New(isolate);
+    gin::Dictionary ad_dict(isolate, ad_object);
+
+    v8::Local<v8::Value> v8_url;
+    if (!gin::TryConvertToV8(isolate, ad.render_url(), &v8_url)) {
+      return false;
+    }
+    if (!ad_dict.Set("renderURL", v8_url) ||
+        // TODO(crbug.com/1441988): Remove deprecated `renderUrl` alias.
+        !DefineLazyAttributeWithMetadata(ad_object, v8_url, "renderUrl",
+                                         &HandleDeprecatedAdsRenderUrl,
+                                         lazy_filler_template) ||
+        (ad.metadata &&
+         !v8_helper()->InsertJsonValue(isolate->GetCurrentContext(), "metadata",
+                                       *ad.metadata, ad_object))) {
+      return false;
+    }
+    ads_vector.emplace_back(std::move(ad_object));
+  }
+  return v8_helper()->InsertValue(
+      name, v8::Array::New(isolate, ads_vector.data(), ads_vector.size()),
+      object);
+}
+
 // static
 void InterestGroupLazyFiller::HandleUserBiddingSignals(
     v8::Local<v8::Name> name,
@@ -396,6 +459,19 @@
   }
 }
 
+// static
+void InterestGroupLazyFiller::HandleDeprecatedAdsRenderUrl(
+    v8::Local<v8::Name> name,
+    const v8::PropertyCallbackInfo<v8::Value>& info) {
+  v8::Local<v8::Value> render_url;
+  InterestGroupLazyFiller* self =
+      GetSelfWithMetadata<InterestGroupLazyFiller>(info, render_url);
+  self->v8_logger_->LogConsoleWarning(
+      "AuctionAd.renderUrl is deprecated."
+      " Please use AuctionAd.renderURL instead.");
+  SetResult(info, render_url);
+}
+
 BiddingBrowserSignalsLazyFiller::BiddingBrowserSignalsLazyFiller(
     AuctionV8Helper* v8_helper)
     : LazyFiller(v8_helper) {}
diff --git a/content/services/auction_worklet/bidder_lazy_filler.h b/content/services/auction_worklet/bidder_lazy_filler.h
index 364c726..661f6da 100644
--- a/content/services/auction_worklet/bidder_lazy_filler.h
+++ b/content/services/auction_worklet/bidder_lazy_filler.h
@@ -5,14 +5,22 @@
 #ifndef CONTENT_SERVICES_AUCTION_WORKLET_BIDDER_LAZY_FILLER_H_
 #define CONTENT_SERVICES_AUCTION_WORKLET_BIDDER_LAZY_FILLER_H_
 
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "base/time/time.h"
 #include "content/common/content_export.h"
 #include "content/services/auction_worklet/auction_v8_helper.h"
 #include "content/services/auction_worklet/context_recycler.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom-forward.h"
+#include "third_party/blink/public/common/interest_group/interest_group.h"
 #include "v8/include/v8-forward.h"
 
+class GURL;
+
 namespace auction_worklet {
 
 class AuctionV8Logger;
@@ -35,10 +43,30 @@
                     const mojom::BidderWorkletNonSharedParams*
                         bidder_worklet_non_shared_params);
 
-  bool FillInObject(v8::Local<v8::Object> object) override;
+  // Returns success/failure.
+  //
+  // `is_ad_excluded` and `is_ad_component_excluded` are used to filter the
+  // `ads` and `adComponents` arrays, respectively.
+  bool FillInObject(
+      v8::Local<v8::Object> object,
+      base::RepeatingCallback<bool(const std::string&)> is_ad_excluded,
+      base::RepeatingCallback<bool(const std::string&)>
+          is_ad_component_excluded);
+
   void Reset() override;
 
  private:
+  // Converts a vector of blink::InterestGroup::Ads into a v8 object, and writes
+  // it to `object's` `name` field. Sets `deprecated_render_url_callback` as the
+  // lazy callback for the deprecated `renderUrl` field of each entry. Returns
+  // false on failure.
+  bool CreateAdVector(
+      v8::Local<v8::Object>& object,
+      std::string_view name,
+      base::RepeatingCallback<bool(const std::string&)> is_ad_excluded,
+      const std::vector<blink::InterestGroup::Ad>& ads,
+      v8::Local<v8::ObjectTemplate>& lazy_filler_template);
+
   static void HandleUserBiddingSignals(
       v8::Local<v8::Name> name,
       const v8::PropertyCallbackInfo<v8::Value>& info);
@@ -96,6 +124,13 @@
       v8::Local<v8::Name> name,
       const v8::PropertyCallbackInfo<v8::Value>& info);
 
+  // Handles "renderUrl" for the ads and ad components arrays, which is
+  // deprecated.
+  // TODO(https://crbug.com/1441988): Remove this method.
+  static void HandleDeprecatedAdsRenderUrl(
+      v8::Local<v8::Name> name,
+      const v8::PropertyCallbackInfo<v8::Value>& info);
+
   raw_ptr<const GURL> bidding_logic_url_ = nullptr;
   raw_ptr<const GURL> bidding_wasm_helper_url_ = nullptr;
   raw_ptr<const GURL> trusted_bidding_signals_url_ = nullptr;
@@ -115,7 +150,9 @@
   void ReInitialize(mojom::BiddingBrowserSignals* bidder_browser_signals,
                     base::Time auction_start_time);
 
-  bool FillInObject(v8::Local<v8::Object> object) override;
+  // Returns success/failure.
+  bool FillInObject(v8::Local<v8::Object> object);
+
   void Reset() override;
 
  private:
diff --git a/content/services/auction_worklet/bidder_worklet.cc b/content/services/auction_worklet/bidder_worklet.cc
index 77e1263..1155d13 100644
--- a/content/services/auction_worklet/bidder_worklet.cc
+++ b/content/services/auction_worklet/bidder_worklet.cc
@@ -185,35 +185,6 @@
          SetDictMember(isolate, top_level_object, "requestedSize", size_object);
 }
 
-// Converts a vector of blink::InterestGroup::Ads into a v8 object.
-bool CreateAdVector(
-    AuctionV8Helper* v8_helper,
-    v8::Local<v8::Context> context,
-    base::RepeatingCallback<bool(const std::string&)> is_ad_excluded,
-    const std::vector<blink::InterestGroup::Ad>& ads,
-    v8::Local<v8::Value>& out_value) {
-  v8::Isolate* isolate = v8_helper->isolate();
-
-  v8::LocalVector<v8::Value> ads_vector(isolate);
-  for (const auto& ad : ads) {
-    if (is_ad_excluded.Run(ad.render_url())) {
-      continue;
-    }
-    v8::Local<v8::Object> ad_object = v8::Object::New(isolate);
-    gin::Dictionary ad_dict(isolate, ad_object);
-    if (
-        // TODO(crbug.com/1441988): Remove deprecated `renderUrl` alias.
-        !SetRenderUrl(isolate, ad_object, ad.render_url()) ||
-        (ad.metadata && !v8_helper->InsertJsonValue(context, "metadata",
-                                                    *ad.metadata, ad_object))) {
-      return false;
-    }
-    ads_vector.emplace_back(std::move(ad_object));
-  }
-  out_value = v8::Array::New(isolate, ads_vector.data(), ads_vector.size());
-  return true;
-}
-
 bool HasKAnonFailureComponent(
     const auction_worklet::mojom::PrivateAggregationRequestPtr& request) {
   if (request->contribution->is_histogram_contribution()) {
@@ -1233,28 +1204,11 @@
           : nullptr,
       &bidder_worklet_non_shared_params);
   if (!context_recycler->interest_group_lazy_filler()->FillInObject(
-          interest_group_object)) {
+          interest_group_object, should_exclude_ad_due_to_kanon,
+          should_exclude_component_ad_due_to_kanon)) {
     return std::nullopt;
   }
 
-  v8::Local<v8::Value> ads;
-  if (!CreateAdVector(v8_helper_.get(), context, should_exclude_ad_due_to_kanon,
-                      *bidder_worklet_non_shared_params.ads, ads) ||
-      !v8_helper_->InsertValue("ads", std::move(ads), interest_group_object)) {
-    return std::nullopt;
-  }
-
-  if (bidder_worklet_non_shared_params.ad_components) {
-    v8::Local<v8::Value> ad_components;
-    if (!CreateAdVector(
-            v8_helper_.get(), context, should_exclude_component_ad_due_to_kanon,
-            *bidder_worklet_non_shared_params.ad_components, ad_components) ||
-        !v8_helper_->InsertValue("adComponents", std::move(ad_components),
-                                 interest_group_object)) {
-      return std::nullopt;
-    }
-  }
-
   args.push_back(std::move(interest_group_object));
 
   if (!AppendJsonValueOrNull(v8_helper_.get(), context, auction_signals_json,
diff --git a/content/services/auction_worklet/bidder_worklet_unittest.cc b/content/services/auction_worklet/bidder_worklet_unittest.cc
index 0667d85..c7a83a19 100644
--- a/content/services/auction_worklet/bidder_worklet_unittest.cc
+++ b/content/services/auction_worklet/bidder_worklet_unittest.cc
@@ -1482,6 +1482,107 @@
        "is neither object, null, nor undefined."});
 }
 
+// Check that accessing `renderUrl` of an entry in the ads array displays a
+// warning. Also checks that renderUrl works as expected.
+//
+// TODO(https://crbug.com/1441988): Remove this test when the field itself is
+// removed.
+TEST_F(BidderWorkletTest, AdsRenderUrlDeprecationWarning) {
+  ScopedInspectorSupport inspector_support(v8_helper_.get());
+
+  interest_group_ads_.emplace_back(GURL("https://response2.test/"),
+                                   /*metadata=*/std::nullopt);
+
+  AddJavascriptResponse(
+      &url_loader_factory_, interest_group_bidding_url_,
+      CreateGenerateBidScript(
+          R"({ad: ["ad"], bid:1, render:"https://response.test/"})",
+          R"(if (interestGroup.ads[0].renderUrl !== "https://response.test/" ||
+             interestGroup.ads[1].renderUrl !== "https://response2.test/") {
+           return;
+         })"));
+
+  interest_group_enable_bidding_signals_prioritization_ = true;
+
+  BidderWorklet* worklet_impl;
+  auto worklet =
+      CreateWorklet(interest_group_bidding_url_,
+                    /*pause_for_debugger_on_start=*/false, &worklet_impl);
+
+  int id = worklet_impl->context_group_id_for_testing();
+  TestChannel* channel =
+      inspector_support.ConnectDebuggerSessionAndRuntimeEnable(id);
+
+  load_script_run_loop_ = std::make_unique<base::RunLoop>();
+  GenerateBid(worklet.get());
+  load_script_run_loop_->Run();
+  // Each access of a different `renderUrl` value should have generated a
+  // separate warning.
+  channel->WaitForAndValidateConsoleMessage(
+      "warning", /*json_args=*/
+      "[{\"type\":\"string\", "
+      "\"value\":\"AuctionAd.renderUrl is deprecated. Please use "
+      "AuctionAd.renderURL instead.\"}]",
+      /*stack_trace_size=*/1, /*function=*/"generateBid",
+      interest_group_bidding_url_, /*line_number=*/4);
+
+  channel->WaitForAndValidateConsoleMessage(
+      "warning", /*json_args=*/
+      "[{\"type\":\"string\", "
+      "\"value\":\"AuctionAd.renderUrl is deprecated. Please use "
+      "AuctionAd.renderURL instead.\"}]",
+      /*stack_trace_size=*/1, /*function=*/"generateBid",
+      interest_group_bidding_url_, /*line_number=*/5);
+
+  ASSERT_TRUE(bid_);
+  EXPECT_EQ(1, bid_->bid);
+  bid_.reset();
+  load_script_run_loop_.reset();
+}
+
+// Check that accessing `renderURL` of an entry in the ads array does not
+// display a warning.
+//
+// TODO(https://crbug.com/1441988): Remove this test when renderUrl is removed.
+TEST_F(BidderWorkletTest, AdsRenderUrlNoDeprecationWarning) {
+  ScopedInspectorSupport inspector_support(v8_helper_.get());
+
+  AddJavascriptResponse(
+      &url_loader_factory_, interest_group_bidding_url_,
+      CreateGenerateBidScript(
+          R"({ad: ["ad"], bid:1, render:"https://response.test/"})",
+          "if (!interestGroup.ads[0].renderURL) return;"));
+
+  interest_group_enable_bidding_signals_prioritization_ = true;
+
+  BidderWorklet* worklet_impl;
+  auto worklet =
+      CreateWorklet(interest_group_bidding_url_,
+                    /*pause_for_debugger_on_start=*/false, &worklet_impl);
+
+  int id = worklet_impl->context_group_id_for_testing();
+  TestChannel* channel =
+      inspector_support.ConnectDebuggerSessionAndRuntimeEnable(id);
+
+  load_script_run_loop_ = std::make_unique<base::RunLoop>();
+  GenerateBid(worklet.get());
+  load_script_run_loop_->Run();
+
+  ASSERT_TRUE(bid_);
+  EXPECT_EQ(1, bid_->bid);
+
+  // Make sure all events have been received.
+  task_environment_.RunUntilIdle();
+
+  std::list<TestChannel::Event> events = channel->TakeAllEvents();
+  for (const auto& event : events) {
+    if (event.type == TestChannel::Event::Type::Notification) {
+      EXPECT_NE(*event.value.GetDict().FindString("method"),
+                "Runtime.consoleAPICalled");
+    }
+  }
+}
+
 TEST_F(BidderWorkletTest, GenerateBidReturnValueAdComponents) {
   // ----------------------
   // No adComponents in IG.
@@ -1784,6 +1885,116 @@
        "items."});
 }
 
+// Check that accessing `renderUrl` of an entry in the adComponents array
+// displays a warning. Also checks that renderUrl works as expected.
+//
+// TODO(https://crbug.com/1441988): Remove this test when the field itself is
+// removed.
+TEST_F(BidderWorkletTest, AdComponentsRenderUrlDeprecationWarning) {
+  ScopedInspectorSupport inspector_support(v8_helper_.get());
+
+  interest_group_ad_components_.emplace();
+  interest_group_ad_components_->emplace_back(GURL("https://component1.test/"),
+                                              /*metadata=*/std::nullopt);
+  interest_group_ad_components_->emplace_back(GURL("https://component2.test/"),
+                                              /*metadata=*/std::nullopt);
+
+  AddJavascriptResponse(
+      &url_loader_factory_, interest_group_bidding_url_,
+      CreateGenerateBidScript(
+          R"({ad: ["ad"], bid:1, render:"https://response.test/"})",
+          R"(if (interestGroup.adComponents[0].renderUrl !==
+                 "https://component1.test/" ||
+             interestGroup.adComponents[1].renderUrl !==
+                 "https://component2.test/") {
+           return;
+         })"));
+
+  interest_group_enable_bidding_signals_prioritization_ = true;
+
+  BidderWorklet* worklet_impl;
+  auto worklet =
+      CreateWorklet(interest_group_bidding_url_,
+                    /*pause_for_debugger_on_start=*/false, &worklet_impl);
+
+  int id = worklet_impl->context_group_id_for_testing();
+  TestChannel* channel =
+      inspector_support.ConnectDebuggerSessionAndRuntimeEnable(id);
+
+  load_script_run_loop_ = std::make_unique<base::RunLoop>();
+  GenerateBid(worklet.get());
+  load_script_run_loop_->Run();
+  // Each access of a different `renderUrl` value should have generated a
+  // separate warning.
+  channel->WaitForAndValidateConsoleMessage(
+      "warning", /*json_args=*/
+      "[{\"type\":\"string\", "
+      "\"value\":\"AuctionAd.renderUrl is deprecated. Please use "
+      "AuctionAd.renderURL instead.\"}]",
+      /*stack_trace_size=*/1, /*function=*/"generateBid",
+      interest_group_bidding_url_, /*line_number=*/4);
+
+  channel->WaitForAndValidateConsoleMessage(
+      "warning", /*json_args=*/
+      "[{\"type\":\"string\", "
+      "\"value\":\"AuctionAd.renderUrl is deprecated. Please use "
+      "AuctionAd.renderURL instead.\"}]",
+      /*stack_trace_size=*/1, /*function=*/"generateBid",
+      interest_group_bidding_url_, /*line_number=*/6);
+
+  ASSERT_TRUE(bid_);
+  EXPECT_EQ(1, bid_->bid);
+  bid_.reset();
+  load_script_run_loop_.reset();
+}
+
+// Check that accessing `renderURL` of an entry in the ads array does not
+// display a warning.
+//
+// TODO(https://crbug.com/1441988): Remove this test when renderUrl is removed.
+TEST_F(BidderWorkletTest, AdComponentsRenderUrlNoDeprecationWarning) {
+  ScopedInspectorSupport inspector_support(v8_helper_.get());
+
+  interest_group_ad_components_.emplace();
+  interest_group_ad_components_->emplace_back(GURL("https://component1.test/"),
+                                              /*metadata=*/std::nullopt);
+
+  AddJavascriptResponse(
+      &url_loader_factory_, interest_group_bidding_url_,
+      CreateGenerateBidScript(
+          R"({ad: ["ad"], bid:1, render:"https://response.test/"})",
+          "if (!interestGroup.adComponents[0].renderURL) return;"));
+
+  interest_group_enable_bidding_signals_prioritization_ = true;
+
+  BidderWorklet* worklet_impl;
+  auto worklet =
+      CreateWorklet(interest_group_bidding_url_,
+                    /*pause_for_debugger_on_start=*/false, &worklet_impl);
+
+  int id = worklet_impl->context_group_id_for_testing();
+  TestChannel* channel =
+      inspector_support.ConnectDebuggerSessionAndRuntimeEnable(id);
+
+  load_script_run_loop_ = std::make_unique<base::RunLoop>();
+  GenerateBid(worklet.get());
+  load_script_run_loop_->Run();
+
+  ASSERT_TRUE(bid_);
+  EXPECT_EQ(1, bid_->bid);
+
+  // Make sure all events have been received.
+  task_environment_.RunUntilIdle();
+
+  std::list<TestChannel::Event> events = channel->TakeAllEvents();
+  for (const auto& event : events) {
+    if (event.type == TestChannel::Event::Type::Notification) {
+      EXPECT_NE(*event.value.GetDict().FindString("method"),
+                "Runtime.consoleAPICalled");
+    }
+  }
+}
+
 TEST_F(BidderWorkletTest, GenerateBidModelingSignals) {
   const std::string kGenerateBidBody =
       R"({bid:1, render:"https://response.test/", modelingSignals: 123})";
diff --git a/content/services/auction_worklet/context_recycler.cc b/content/services/auction_worklet/context_recycler.cc
index 541a463..ca2c65b 100644
--- a/content/services/auction_worklet/context_recycler.cc
+++ b/content/services/auction_worklet/context_recycler.cc
@@ -52,6 +52,41 @@
   return success.IsJust() && success.FromJust();
 }
 
+bool LazyFiller::DefineLazyAttributeWithMetadata(
+    v8::Local<v8::Object> object,
+    v8::Local<v8::Value> metadata,
+    base::StringPiece name,
+    v8::AccessorNameGetterCallback getter,
+    v8::Local<v8::ObjectTemplate>& lazy_filler_template) {
+  v8::Isolate* isolate = v8_helper_->isolate();
+  v8::Local<v8::Context> context = isolate->GetCurrentContext();
+
+  if (lazy_filler_template.IsEmpty()) {
+    lazy_filler_template = v8::ObjectTemplate::New(isolate);
+    lazy_filler_template->SetInternalFieldCount(2);
+  }
+  v8::Local<v8::Object> data =
+      lazy_filler_template->NewInstance(context).ToLocalChecked();
+  data->SetInternalField(0, v8::External::New(isolate, this));
+  data->SetInternalField(1, metadata);
+
+  v8::Maybe<bool> success = object->SetLazyDataProperty(
+      context, gin::StringToSymbol(isolate, name), getter, data,
+      /*attributes=*/v8::None,
+      /*getter_side_effect_type=*/v8::SideEffectType::kHasNoSideEffect,
+      /*setter_side_effect_type=*/v8::SideEffectType::kHasSideEffect);
+  return success.IsJust() && success.FromJust();
+}
+
+void* LazyFiller::GetSelfWithMetadataInternal(
+    const v8::PropertyCallbackInfo<v8::Value>& info,
+    v8::Local<v8::Value>& metadata) {
+  v8::Local<v8::Object> data = v8::Local<v8::Object>::Cast(info.Data());
+  metadata = data->GetInternalField(1).As<v8::Value>();
+  return (data->GetInternalField(0).As<v8::Value>().As<v8::External>())
+      ->Value();
+}
+
 ContextRecycler::ContextRecycler(AuctionV8Helper* v8_helper)
     : v8_helper_(v8_helper) {}
 
diff --git a/content/services/auction_worklet/context_recycler.h b/content/services/auction_worklet/context_recycler.h
index 65f7499..502f7da 100644
--- a/content/services/auction_worklet/context_recycler.h
+++ b/content/services/auction_worklet/context_recycler.h
@@ -59,14 +59,18 @@
 //
 // API for implementers is as follows:
 //
-// 1) In FillInObject, call DefineLazyAttribute for all relevant attributes.
+// 1) Call DefineLazyAttribute() and/or DefineLazyAttributeWithMetadata()
+//   for all relevant attributes. There's no requirement that all calls be
+//   on the same object (e.g., can defined lazy attributes on objects
+//   contained within the same top-level object).
+//
 // 2) In the static helpers registered with DefineLazyAttribute
 //    (which take (v8::Local<v8::Name> name,
 //                 const v8::PropertyCallbackInfo<v8::Value>& info)
-//    Use GetSelf and SetResult() to provide value.
+//    Use GetSelf() / GetSelfWithMetadata() and SetResult() to provide value.
 //    The implementation must be careful of `this` being in recycled state,
 //    and also re-check that the field itself is still there (the check in
-//    FillInObject may have been from pre-recycling).
+//    FillInObject() may have been from pre-recycling).
 //
 //    If you use the JSON parser, make sure to eat exceptions with v8::TryCatch.
 //
@@ -77,19 +81,31 @@
 class LazyFiller {
  public:
   virtual ~LazyFiller();
-  // Return success/failure.
-  virtual bool FillInObject(v8::Local<v8::Object> object) = 0;
   virtual void Reset() = 0;
 
  protected:
   explicit LazyFiller(AuctionV8Helper* v8_helper);
   AuctionV8Helper* v8_helper() { return v8_helper_.get(); }
 
+  // Returns the C++ object DefineLazyAttribute() was invoked on, from the
+  // PropertyCallbackInfo passed a lazy callback configured via
+  // DefineLazyAttribute().
+  //
+  // Does not work with attributes set by DefineLazyAttributeWithMetadata().
   template <typename T>
   static T* GetSelf(const v8::PropertyCallbackInfo<v8::Value>& info) {
     return static_cast<T*>(v8::External::Cast(*info.Data())->Value());
   }
 
+  // Like GetSelf(), but for DefineLazyAttributeWithMetadata().
+  //
+  // Does not work with attributes set by DefineLazyAttribute().
+  template <typename T>
+  static T* GetSelfWithMetadata(const v8::PropertyCallbackInfo<v8::Value>& info,
+                                v8::Local<v8::Value>& metadata) {
+    return static_cast<T*>(GetSelfWithMetadataInternal(info, metadata));
+  }
+
   static void SetResult(const v8::PropertyCallbackInfo<v8::Value>& info,
                         v8::Local<v8::Value> result);
 
@@ -97,7 +113,22 @@
                            base::StringPiece name,
                            v8::AccessorNameGetterCallback getter);
 
+  // `lazy_filler_template` is used to construct an internal v8 object with a
+  // pointer to `this` and `metadata` stored internally. It should be empty on
+  // the first call, and reused across calls, to avoid having to repeatedly
+  // create a new template.
+  bool DefineLazyAttributeWithMetadata(
+      v8::Local<v8::Object> object,
+      v8::Local<v8::Value> metadata,
+      base::StringPiece name,
+      v8::AccessorNameGetterCallback getter,
+      v8::Local<v8::ObjectTemplate>& lazy_filler_template);
+
  private:
+  static void* GetSelfWithMetadataInternal(
+      const v8::PropertyCallbackInfo<v8::Value>& info,
+      v8::Local<v8::Value>& metadata);
+
   const raw_ptr<AuctionV8Helper> v8_helper_;
 };
 
diff --git a/content/services/auction_worklet/context_recycler_unittest.cc b/content/services/auction_worklet/context_recycler_unittest.cc
index 053c99a..934b117 100644
--- a/content/services/auction_worklet/context_recycler_unittest.cc
+++ b/content/services/auction_worklet/context_recycler_unittest.cc
@@ -177,6 +177,11 @@
       ig_params2->priority_vector.emplace();
       ig_params2->priority_vector->insert(
           std::pair<std::string, double>("a", 42.0));
+      ig_params2->ads = {{{GURL("https://ad.test/1"), absl::nullopt},
+                          {GURL("https://ad.test/2"), {"\"metadata 1\""}}}};
+      ig_params2->ad_components = {
+          {{GURL("https://ad-component.test/1"), {"\"metadata 2\""}},
+           {GURL("https://ad-component.test/2"), absl::nullopt}}};
 
       mojom::BiddingBrowserSignalsPtr bs_params2 =
           mojom::BiddingBrowserSignals::New();
@@ -193,8 +198,14 @@
           bs_params2.get(), now2);
 
       v8::Local<v8::Object> arg(v8::Object::New(helper_->isolate()));
-      context_recycler.interest_group_lazy_filler()->FillInObject(arg);
-      context_recycler.bidding_browser_signals_lazy_filler()->FillInObject(arg);
+      // Exclude no ads.
+      base::RepeatingCallback<bool(const std::string&)> ad_callback =
+          base::BindRepeating([](const std::string&) { return false; });
+      ASSERT_TRUE(context_recycler.interest_group_lazy_filler()->FillInObject(
+          arg, ad_callback, ad_callback));
+      ASSERT_TRUE(
+          context_recycler.bidding_browser_signals_lazy_filler()->FillInObject(
+              arg));
 
       std::vector<std::string> error_msgs;
       Run(scope, script, "test", error_msgs, arg);
@@ -215,11 +226,16 @@
 
       v8::Local<v8::Object> arg(v8::Object::New(helper_->isolate()));
       if (ig_params) {
-        context_recycler.interest_group_lazy_filler()->FillInObject(arg);
+        // Use a new, short-lived callback that excludes no ads, to make sure
+        // its lifetime doesn't unexpectedly matter.
+        base::RepeatingCallback<bool(const std::string&)> ad_callback =
+            base::BindRepeating([](const std::string&) { return false; });
+        ASSERT_TRUE(context_recycler.interest_group_lazy_filler()->FillInObject(
+            arg, ad_callback, ad_callback));
       }
       if (bs_params) {
-        context_recycler.bidding_browser_signals_lazy_filler()->FillInObject(
-            arg);
+        ASSERT_TRUE(context_recycler.bidding_browser_signals_lazy_filler()
+                        ->FillInObject(arg));
       }
 
       std::vector<std::string> error_msgs;
@@ -908,6 +924,9 @@
   ig_params->priority_vector.emplace();
   ig_params->priority_vector->insert(std::pair<std::string, double>("e", 12.0));
   ig_params->enable_bidding_signals_prioritization = true;
+  ig_params->ads = {{{GURL("https://ad2.test/"), {"\"metadata 3\""}}}};
+  ig_params->ad_components = {
+      {{GURL("https://ad-component2.test/"), absl::nullopt}}};
 
   mojom::BiddingBrowserSignalsPtr bs_params =
       mojom::BiddingBrowserSignals::New();
@@ -931,6 +950,17 @@
       "\"trustedBiddingSignalsKeys\":[\"c\",\"d\"],"
       "\"priorityVector\":{\"e\":12},"
       "\"useBiddingSignalsPrioritization\":true,"
+      "\"ads\":"
+      "[{\"renderURL\":\"https://ad.test/1\","
+      "\"renderUrl\":\"https://ad.test/1\"},"
+      "{\"renderURL\":\"https://ad.test/2\","
+      "\"renderUrl\":\"https://ad.test/2\",\"metadata\":\"metadata 1\"}],"
+      "\"adComponents\":"
+      "[{\"renderURL\":\"https://ad-component.test/1\","
+      "\"renderUrl\":\"https://ad-component.test/1\","
+      "\"metadata\":\"metadata 2\"},"
+      "{\"renderURL\":\"https://ad-component.test/2\","
+      "\"renderUrl\":\"https://ad-component.test/2\"}],"
       "\"prevWins\":[[240,[\"d\"]],[180,[\"c\"]]],"
       "\"prevWinsMs\":[[240000,[\"d\"]],[180000,[\"c\"]]]}");
 }
@@ -963,6 +993,18 @@
       "\"trustedBiddingSignalsKeys\":null,"
       "\"priorityVector\":null,"
       "\"useBiddingSignalsPrioritization\":false,"
+      "\"ads\":"
+      "[{\"renderURL\":\"https://ad.test/1\","
+      "\"renderUrl\":\"https://ad.test/1\"},"
+      "{\"renderURL\":\"https://ad.test/2\","
+      "\"renderUrl\":\"https://ad.test/2\","
+      "\"metadata\":\"metadata 1\"}],"
+      "\"adComponents\":"
+      "[{\"renderURL\":\"https://ad-component.test/1\","
+      "\"renderUrl\":\"https://ad-component.test/1\","
+      "\"metadata\":\"metadata 2\"},"
+      "{\"renderURL\":\"https://ad-component.test/2\","
+      "\"renderUrl\":\"https://ad-component.test/2\"}],"
       "\"prevWins\":[],"
       "\"prevWinsMs\":[]}");
 }
@@ -975,22 +1017,35 @@
 // never invoked is accessed when a context is reused, without populating bidder
 // lazy fillers.
 TEST_F(ContextRecyclerTest, BidderLazyFiller3) {
-  RunBidderLazyFilterReuseTest(nullptr, nullptr, base::Time(),
-                               "{\"userBiddingSignals\":null,"
-                               "\"biddingLogicURL\":null,"
-                               "\"biddingLogicUrl\":null,"
-                               "\"biddingWasmHelperURL\":null,"
-                               "\"biddingWasmHelperUrl\":null,"
-                               "\"updateURL\":null,"
-                               "\"updateUrl\":null,"
-                               "\"dailyUpdateUrl\":null,"
-                               "\"trustedBiddingSignalsURL\":null,"
-                               "\"trustedBiddingSignalsUrl\":null,"
-                               "\"trustedBiddingSignalsKeys\":null,"
-                               "\"priorityVector\":null,"
-                               "\"useBiddingSignalsPrioritization\":null,"
-                               "\"prevWins\":null,"
-                               "\"prevWinsMs\":null}");
+  RunBidderLazyFilterReuseTest(
+      nullptr, nullptr, base::Time(),
+      "{\"userBiddingSignals\":null,"
+      "\"biddingLogicURL\":null,"
+      "\"biddingLogicUrl\":null,"
+      "\"biddingWasmHelperURL\":null,"
+      "\"biddingWasmHelperUrl\":null,"
+      "\"updateURL\":null,"
+      "\"updateUrl\":null,"
+      "\"dailyUpdateUrl\":null,"
+      "\"trustedBiddingSignalsURL\":null,"
+      "\"trustedBiddingSignalsUrl\":null,"
+      "\"trustedBiddingSignalsKeys\":null,"
+      "\"priorityVector\":null,"
+      "\"useBiddingSignalsPrioritization\":null,"
+      "\"ads\":"
+      "[{\"renderURL\":\"https://ad.test/1\","
+      "\"renderUrl\":\"https://ad.test/1\"},"
+      "{\"renderURL\":\"https://ad.test/2\","
+      "\"renderUrl\":\"https://ad.test/2\","
+      "\"metadata\":\"metadata 1\"}],"
+      "\"adComponents\":"
+      "[{\"renderURL\":\"https://ad-component.test/1\","
+      "\"renderUrl\":\"https://ad-component.test/1\","
+      "\"metadata\":\"metadata 2\"},"
+      "{\"renderURL\":\"https://ad-component.test/2\","
+      "\"renderUrl\":\"https://ad-component.test/2\"}],"
+      "\"prevWins\":null,"
+      "\"prevWinsMs\":null}");
 }
 
 TEST_F(ContextRecyclerTest, SharedStorageMethods) {
diff --git a/content/services/auction_worklet/set_bid_bindings.cc b/content/services/auction_worklet/set_bid_bindings.cc
index 87e9eb5d..29f6c72 100644
--- a/content/services/auction_worklet/set_bid_bindings.cc
+++ b/content/services/auction_worklet/set_bid_bindings.cc
@@ -74,17 +74,16 @@
 };
 
 // Handles conversion of (DOMString or AdRender) IDL type.
-bool ConvertDomStringOrAdRender(
+IdlConvert::Status ConvertDomStringOrAdRender(
     AuctionV8Helper* v8_helper,
     AuctionV8Helper::TimeLimitScope& time_limit_scope,
     const std::string& error_prefix,
     v8::Local<v8::Value> value,
-    AdRender& out,
-    DictConverter& propagate_errors_to) {
+    AdRender& out) {
   if (value->IsString()) {
     bool ok = gin::ConvertFromV8(v8_helper->isolate(), value, &out.url);
     DCHECK(ok);  // Shouldn't fail since it's known to be String.
-    return true;
+    return IdlConvert::Status::MakeSuccess();
   }
 
   DictConverter convert_ad_render(v8_helper, time_limit_scope, error_prefix,
@@ -93,10 +92,9 @@
   if (!convert_ad_render.GetOptional("height", out.height) ||
       !convert_ad_render.GetRequired("url", out.url) ||
       !convert_ad_render.GetOptional("width", out.width)) {
-    propagate_errors_to.SetStatus(convert_ad_render.TakeStatus());
-    return false;
+    return convert_ad_render.TakeStatus();
   }
-  return true;
+  return IdlConvert::Status::MakeSuccess();
 }
 
 // Parses an AdRender, either a top-level value of render: field in bid or
@@ -240,21 +238,19 @@
   auto collect_components = base::BindRepeating(
       [](scoped_refptr<AuctionV8Helper> v8_helper,
          AuctionV8Helper::TimeLimitScope& time_limit_scope,
-         DictConverter& convert_set_bid, const std::string& error_prefix,
-         GenerateBidOutput& idl, v8::Local<v8::Value> component) -> bool {
+         const std::string& error_prefix, GenerateBidOutput& idl,
+         v8::Local<v8::Value> component) -> IdlConvert::Status {
         AdRender converted_component;
-        if (ConvertDomStringOrAdRender(v8_helper.get(), time_limit_scope,
-                                       error_prefix, component,
-                                       converted_component, convert_set_bid)) {
+        IdlConvert::Status status = ConvertDomStringOrAdRender(
+            v8_helper.get(), time_limit_scope, error_prefix, component,
+            converted_component);
+        if (status.is_success()) {
           idl.ad_components->push_back(std::move(converted_component));
-          return true;
-        } else {
-          // ConvertDomStringOrAdRender already forwarded the error for us.
-          return false;
         }
+        return status;
       },
-      ref_v8_helper, std::ref(time_limit_scope), std::ref(convert_set_bid),
-      std::cref(components_prefix), std::ref(idl));
+      ref_v8_helper, std::ref(time_limit_scope), std::cref(components_prefix),
+      std::ref(idl));
 
   convert_set_bid.GetOptional("ad", idl.ad);
   convert_set_bid.GetOptionalSequence(
@@ -270,9 +266,9 @@
   if (convert_set_bid.GetOptional("render", render_value) &&
       render_value.has_value()) {
     idl.render.emplace();
-    ConvertDomStringOrAdRender(v8_helper_.get(), time_limit_scope,
-                               render_prefix, *render_value, *idl.render,
-                               convert_set_bid);
+    convert_set_bid.SetStatus(
+        ConvertDomStringOrAdRender(v8_helper_.get(), time_limit_scope,
+                                   render_prefix, *render_value, *idl.render));
   }
 
   if (convert_set_bid.is_failed()) {
@@ -375,9 +371,9 @@
     }
 
     // We want < rather than <= here so the semantic check is testable and not
-    // hidden by implementation details of DictConverter.
+    // hidden by implementation details of IdlConvert.
     static_assert(blink::kMaxAdAuctionAdComponentsConfigLimit <
-                  DictConverter::kSequenceLengthLimit);
+                  IdlConvert::kSequenceLengthLimit);
 
     const size_t kMaxAdAuctionAdComponents = blink::MaxAdAuctionAdComponents();
     if (idl.ad_components->size() > kMaxAdAuctionAdComponents) {
diff --git a/content/services/auction_worklet/webidl_compat.cc b/content/services/auction_worklet/webidl_compat.cc
index 53d23474..6ae0046d 100644
--- a/content/services/auction_worklet/webidl_compat.cc
+++ b/content/services/auction_worklet/webidl_compat.cc
@@ -30,8 +30,28 @@
       try_catch, error_prefix, error_subject, "record<DOMString, USVString>");
 }
 
+IdlConvert::Status MakeIterFailure(
+    std::string_view error_prefix,
+    std::initializer_list<std::string_view> error_subject,
+    const v8::TryCatch& catcher) {
+  if (catcher.HasTerminated()) {
+    return IdlConvert::Status::MakeTimeout(
+        base::StrCat({error_prefix, "Timeout iterating over ",
+                      base::StrCat(error_subject), "."}));
+  } else if (catcher.HasCaught()) {
+    return IdlConvert::Status::MakeException(catcher.Exception(),
+                                             catcher.Message());
+  } else {
+    return IdlConvert::Status::MakeErrorMessage(
+        base::StrCat({error_prefix, "Trouble iterating over ",
+                      base::StrCat(error_subject), "."}));
+  }
+}
+
 }  // namespace
 
+const size_t IdlConvert::kSequenceLengthLimit;
+
 IdlConvert::Status::Status() : value_(Success::kSuccessTag) {}
 IdlConvert::Status::Status(Status&& other) = default;
 IdlConvert::Status::~Status() = default;
@@ -387,8 +407,6 @@
   return IdlConvert::Status::MakeSuccess();
 }
 
-const size_t DictConverter::kSequenceLengthLimit;
-
 DictConverter::DictConverter(AuctionV8Helper* v8_helper,
                              AuctionV8Helper::TimeLimitScope& time_limit_scope,
                              std::string error_prefix,
@@ -410,7 +428,8 @@
 bool DictConverter::GetOptionalSequence(
     std::string_view field,
     base::OnceClosure exists_callback,
-    base::RepeatingCallback<bool(v8::Local<v8::Value>)> item_callback) {
+    base::RepeatingCallback<IdlConvert::Status(v8::Local<v8::Value>)>
+        item_callback) {
   if (is_failed()) {
     return false;
   }
@@ -426,7 +445,10 @@
 
   std::move(exists_callback).Run();
 
-  return ConvertSequence(field, val, std::move(item_callback));
+  status_ = IdlConvert::ConvertSequence(v8_helper_.get(), error_prefix_,
+                                        {"field '", field, "'"}, val,
+                                        std::move(item_callback));
+  return is_success();
 }
 
 std::string DictConverter::ErrorMessage() const {
@@ -480,49 +502,50 @@
   return val;
 }
 
-bool DictConverter::ConvertSequence(
-    std::string_view field,
+// static
+IdlConvert::Status IdlConvert::ConvertSequence(
+    AuctionV8Helper* v8_helper,
+    std::string_view error_prefix,
+    std::initializer_list<std::string_view> error_subject,
     v8::Local<v8::Value> value,
-    base::RepeatingCallback<bool(v8::Local<v8::Value>)> item_callback) {
+    base::RepeatingCallback<IdlConvert::Status(v8::Local<v8::Value>)>
+        item_callback) {
   // This is based on https://webidl.spec.whatwg.org/#es-sequence and its
   // implementation in
   // third_party/blink/renderer/bindings/core/v8/script_iterator.cc
   //
   // It's probably best understood from:
   //   https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols
-  v8::Isolate* isolate = v8_helper_->isolate();
+  v8::Isolate* isolate = v8_helper->isolate();
   v8::Local<v8::Context> current_context = isolate->GetCurrentContext();
   v8::TryCatch try_catch(isolate);
 
   // If Type(V) is not Object, throw a TypeError.
   if (!value->IsObject()) {
-    MarkFailed(
-        base::StrCat({"Sequence field '", field, "' must be an Object."}));
-    return false;
+    return Status::MakeErrorMessage(
+        base::StrCat({error_prefix, "Sequence ", base::StrCat(error_subject),
+                      " must be an Object."}));
   }
   v8::Local<v8::Object> iterable = value.As<v8::Object>();
 
-  v8::Local<v8::String> next_name = v8_helper_->CreateStringFromLiteral("next");
-  v8::Local<v8::String> done_name = v8_helper_->CreateStringFromLiteral("done");
+  v8::Local<v8::String> next_name = v8_helper->CreateStringFromLiteral("next");
+  v8::Local<v8::String> done_name = v8_helper->CreateStringFromLiteral("done");
   v8::Local<v8::String> value_name =
-      v8_helper_->CreateStringFromLiteral("value");
+      v8_helper->CreateStringFromLiteral("value");
 
   // Let method be ? GetMethod(V, @@iterator).
   // If method is undefined, throw a TypeError.
   v8::Local<v8::Value> method_val;
   if (!iterable->Get(current_context, v8::Symbol::GetIterator(isolate))
            .ToLocal(&method_val)) {
-    MarkFailedIter(field, try_catch);
-    return false;
+    return MakeIterFailure(error_prefix, error_subject, try_catch);
   }
   if (!method_val->IsObject()) {  // subsumes the undefined/null check.
-    MarkFailedIter(field, try_catch);
-    return false;
+    return MakeIterFailure(error_prefix, error_subject, try_catch);
   }
   v8::Local<v8::Object> method = method_val.As<v8::Object>();
   if (!method->IsCallable()) {
-    MarkFailedIter(field, try_catch);
-    return false;
+    return MakeIterFailure(error_prefix, error_subject, try_catch);
   }
 
   // Let iter be ? GetIterator(iterable, sync, method)
@@ -532,21 +555,18 @@
                             /*argv=*/nullptr)
            .ToLocal(&iterator_val) ||
       !iterator_val->IsObject()) {
-    MarkFailedIter(field, try_catch);
-    return false;
+    return MakeIterFailure(error_prefix, error_subject, try_catch);
   }
   v8::Local<v8::Object> iterator = iterator_val.As<v8::Object>();
 
   v8::Local<v8::Value> next_method_val;
   if (!iterator->Get(current_context, next_name).ToLocal(&next_method_val) ||
       !next_method_val->IsObject()) {
-    MarkFailedIter(field, try_catch);
-    return false;
+    return MakeIterFailure(error_prefix, error_subject, try_catch);
   }
   v8::Local<v8::Object> next_method = next_method_val.As<v8::Object>();
   if (!next_method->IsCallable()) {
-    MarkFailedIter(field, try_catch);
-    return false;
+    return MakeIterFailure(error_prefix, error_subject, try_catch);
   }
 
   size_t out_size = 0;
@@ -557,8 +577,7 @@
                               /*argv=*/nullptr)
              .ToLocal(&result_val) ||
         !result_val->IsObject()) {
-      MarkFailedIter(field, try_catch);
-      return false;
+      return MakeIterFailure(error_prefix, error_subject, try_catch);
     }
     v8::Local<v8::Object> result = result_val.As<v8::Object>();
 
@@ -568,8 +587,7 @@
       // Can use our Convert() since it doesn't fail for bools.
       IdlConvert::Convert(isolate, "", {}, done_val, done);
     } else if (try_catch.HasCaught() || try_catch.HasTerminated()) {
-      MarkFailedIter(field, try_catch);
-      return false;
+      return MakeIterFailure(error_prefix, error_subject, try_catch);
     }
 
     if (done) {
@@ -580,29 +598,23 @@
     // would put us over).
     ++out_size;
     if (out_size > kSequenceLengthLimit) {
-      MarkFailed(base::StrCat(
-          {"Length limit for sequence field '", field, "' exceeded."}));
-      return false;
+      return Status::MakeErrorMessage(
+          base::StrCat({error_prefix, "Length limit for sequence ",
+                        base::StrCat(error_subject), " exceeded."}));
     }
 
     v8::Local<v8::Value> next_item;
     if (!result->Get(current_context, value_name).ToLocal(&next_item)) {
-      MarkFailedIter(field, try_catch);
-      return false;
+      return MakeIterFailure(error_prefix, error_subject, try_catch);
     }
 
-    if (!item_callback.Run(next_item)) {
-      // It's possible that the callback already propagated an error to us;
-      // if not, set one ourselves.
-      if (!is_failed()) {
-        MarkFailed(base::StrCat({"Conversion for an item for sequence field '",
-                                 field, "' failed."}));
-      }
-      return false;
+    Status entry_status = item_callback.Run(next_item);
+    if (!entry_status.is_success()) {
+      return entry_status;
     }
   }
 
-  return true;
+  return Status::MakeSuccess();
 }
 
 void DictConverter::MarkFailed(std::string_view fail_message) {
@@ -623,18 +635,6 @@
       IdlConvert::Status::MakeException(catcher.Exception(), catcher.Message());
 }
 
-void DictConverter::MarkFailedIter(std::string_view field,
-                                   const v8::TryCatch& catcher) {
-  if (catcher.HasTerminated()) {
-    MarkFailedWithTimeout(
-        base::StrCat({"Timeout iterating over '", field, "'."}));
-  } else if (catcher.HasCaught()) {
-    MarkFailedWithException(catcher);
-  } else {
-    MarkFailed(base::StrCat({"Trouble iterating over '", field, "'."}));
-  }
-}
-
 ArgsConverter::ArgsConverter(AuctionV8Helper* v8_helper,
                              AuctionV8Helper::TimeLimitScope& time_limit_scope,
                              std::string error_prefix,
diff --git a/content/services/auction_worklet/webidl_compat.h b/content/services/auction_worklet/webidl_compat.h
index f034d08fe9..85d1023 100644
--- a/content/services/auction_worklet/webidl_compat.h
+++ b/content/services/auction_worklet/webidl_compat.h
@@ -47,6 +47,11 @@
 //  argument bar, etc.)
 class CONTENT_EXPORT IdlConvert {
  public:
+  // This should be bigger than the biggest Sequence<> the users of this need to
+  // handle, which is currently up to
+  // blink::kMaxAdAuctionAdComponentsConfigLimit.
+  static const size_t kSequenceLengthLimit = 101;
+
   // Outcome of the conversion, either success or some kind of error.
   class CONTENT_EXPORT Status {
    public:
@@ -186,6 +191,17 @@
                         v8::Local<v8::Value> value,
                         v8::Local<v8::Value>& out);
 
+  // Tries to convert the passed in value to a sequence. Entries will be
+  // provided one-by-one to `item_callback` (0 to kSequenceLengthLimit times).
+  // `item_callback` is expected to typecheck its input and return the status,
+  // with failures interrupting the conversion and propagated up.
+  static Status ConvertSequence(
+      AuctionV8Helper* v8_helper,
+      std::string_view error_prefix,
+      std::initializer_list<std::string_view> error_subject,
+      v8::Local<v8::Value> value,
+      base::RepeatingCallback<Status(v8::Local<v8::Value>)> item_callback);
+
   // Makes a failure result based on state of `catcher`. If nothing is set on
   // `catcher`, will report a conversion failure.
   static Status MakeConversionFailure(
@@ -226,11 +242,6 @@
 //  GetOptionalSequence.
 class CONTENT_EXPORT DictConverter {
  public:
-  // This should be bigger than the biggest Sequence<> the users of this need to
-  // handle, which is currently up to
-  // blink::kMaxAdAuctionAdComponentsConfigLimit.
-  static const size_t kSequenceLengthLimit = 101;
-
   // Prepares to convert `value` to a WebIDL dictionary.
   //
   // Since fields conversions may loop infinitely, a `time_limit_scope` must
@@ -292,15 +303,13 @@
   // Gets an optional sequence field `field`. If the field exists,
   // `exists_callback` will be called, and then entries will be provided
   // one-by-one to `item_callback` (0 to kSequenceLengthLimit times).
-  // `item_callback` is expected to typecheck its input and return true/false as
-  // appropriate.
-  //
-  // The conversion routine can use SetStatus() to forward errors to `this` from
-  // a different converter.
+  // `item_callback` is expected to typecheck its input and return status as
+  // appropriate; with failures forwarded to `this`.
   bool GetOptionalSequence(
       std::string_view field,
       base::OnceClosure exists_callback,
-      base::RepeatingCallback<bool(v8::Local<v8::Value>)> item_callback);
+      base::RepeatingCallback<IdlConvert::Status(v8::Local<v8::Value>)>
+          item_callback);
 
   std::string ErrorMessage() const;
 
@@ -328,15 +337,9 @@
   // This can mark failure.
   v8::Local<v8::Value> GetMember(std::string_view field);
 
-  bool ConvertSequence(
-      std::string_view field,
-      v8::Local<v8::Value> value,
-      base::RepeatingCallback<bool(v8::Local<v8::Value>)> item_callback);
-
   void MarkFailed(std::string_view fail_message);
   void MarkFailedWithTimeout(std::string_view fail_message);
   void MarkFailedWithException(const v8::TryCatch& catcher);
-  void MarkFailedIter(std::string_view field, const v8::TryCatch& catcher);
 
   raw_ptr<AuctionV8Helper> v8_helper_;
   std::string error_prefix_;
diff --git a/content/services/auction_worklet/webidl_compat_unittest.cc b/content/services/auction_worklet/webidl_compat_unittest.cc
index b781a90..b8fdcb7 100644
--- a/content/services/auction_worklet/webidl_compat_unittest.cc
+++ b/content/services/auction_worklet/webidl_compat_unittest.cc
@@ -38,7 +38,9 @@
     base::RunLoop().RunUntilIdle();
     v8_scope_ =
         std::make_unique<AuctionV8Helper::FullIsolateScope>(v8_helper_.get());
-    time_limit_ = v8_helper_->CreateTimeLimit(/*script_timeout=*/std::nullopt);
+    // Using a large timeout because bots are sometimes very slow.
+    time_limit_ =
+        v8_helper_->CreateTimeLimit(/*script_timeout=*/base::Milliseconds(500));
     time_limit_scope_ =
         std::make_unique<AuctionV8Helper::TimeLimitScope>(time_limit_.get());
   }
@@ -104,10 +106,11 @@
     bool got_it = false;
     bool result = converter->GetOptionalSequence(
         field, base::BindLambdaForTesting([&]() { got_it = true; }),
-        base::BindLambdaForTesting([&](v8::Local<v8::Value> value) -> bool {
-          out.push_back(value);
-          return true;
-        }));
+        base::BindLambdaForTesting(
+            [&](v8::Local<v8::Value> value) -> IdlConvert::Status {
+              out.push_back(value);
+              return IdlConvert::Status::MakeSuccess();
+            }));
     return got_it && result;
   }
 
@@ -583,6 +586,58 @@
   }
 }
 
+TEST_F(WebIDLCompatTest, StandaloneSequence) {
+  // Sequences are tested more thoroughly as parts of dictionaries for historic
+  // reasons.
+  v8::Local<v8::Context> context = v8_helper_->CreateContext();
+  v8::Isolate* isolate = v8_helper_->isolate();
+  v8::Context::Scope ctx(context);
+
+  {
+    auto in_value = MakeValueFromScript(context, "make = () => [1, 2, 3]");
+    std::vector<double> out;
+    IdlConvert::Status status = IdlConvert::ConvertSequence(
+        v8_helper_.get(), "test1 ", {"v1", "scalar"}, in_value,
+        base::BindLambdaForTesting(
+            [&](v8::Local<v8::Value> in) -> IdlConvert::Status {
+              double result = -1;
+              IdlConvert::Status status = IdlConvert::Convert(
+                  isolate, "inner ", {"sequence item"}, in, result);
+              out.push_back(result);
+              return status;
+            }));
+    EXPECT_TRUE(status.is_success());
+    ASSERT_EQ(3u, out.size());
+    EXPECT_THAT(out, ElementsAre(1.0, 2.0, 3.0));
+  }
+
+  {
+    // Sequence where conversion fails in the middle.
+    auto in_value =
+        MakeValueFromScript(context, "make = () => [1, 0.0/0.0, 3]");
+    std::vector<double> out;
+    IdlConvert::Status status = IdlConvert::ConvertSequence(
+        v8_helper_.get(), "test2 ", {"v1", "scalar"}, in_value,
+        base::BindLambdaForTesting(
+            [&](v8::Local<v8::Value> in) -> IdlConvert::Status {
+              double result = -1;
+              IdlConvert::Status status = IdlConvert::Convert(
+                  isolate, "inner2 ", {"a sequence item"}, in, result);
+              if (status.is_success()) {
+                out.push_back(result);
+              }
+              return status;
+            }));
+    EXPECT_FALSE(status.is_success());
+    EXPECT_EQ(
+        "inner2 Converting a sequence item to a Number did not produce a "
+        "finite double.",
+        status.ConvertToErrorString(isolate));
+    ASSERT_EQ(1u, out.size());
+    EXPECT_THAT(out, ElementsAre(1.0));
+  }
+}
+
 TEST_F(WebIDLCompatTest, BigIntOrLong) {
   v8::Local<v8::Context> context = v8_helper_->CreateContext();
   v8::Context::Scope ctx(context);
@@ -1580,10 +1635,11 @@
 
   EXPECT_TRUE(converter->GetOptionalSequence(
       "a", base::BindLambdaForTesting([&]() { saw_a = true; }),
-      base::BindLambdaForTesting([&](v8::Local<v8::Value> item) -> bool {
-        saw_a_item = true;
-        return false;
-      })));
+      base::BindLambdaForTesting(
+          [&](v8::Local<v8::Value> item) -> IdlConvert::Status {
+            saw_a_item = true;
+            return IdlConvert::Status::MakeErrorMessage("Badness");
+          })));
   EXPECT_TRUE(saw_a);
   EXPECT_FALSE(saw_a_item);
 
@@ -1592,10 +1648,11 @@
 
   EXPECT_TRUE(converter->GetOptionalSequence(
       "b", base::BindLambdaForTesting([&]() { saw_b = true; }),
-      base::BindLambdaForTesting([&](v8::Local<v8::Value> item) -> bool {
-        saw_b_item = true;
-        return false;
-      })));
+      base::BindLambdaForTesting(
+          [&](v8::Local<v8::Value> item) -> IdlConvert::Status {
+            saw_b_item = true;
+            return IdlConvert::Status::MakeErrorMessage("Badness");
+          })));
   EXPECT_FALSE(saw_b);
   EXPECT_FALSE(saw_b_item);
 }
@@ -1618,20 +1675,19 @@
 
   EXPECT_FALSE(converter->GetOptionalSequence(
       "f1", base::BindLambdaForTesting([&]() { saw_field = true; }),
-      base::BindLambdaForTesting([&](v8::Local<v8::Value> item) -> bool {
-        std::string str;
-        EXPECT_TRUE(gin::Converter<std::string>::FromV8(v8_helper_->isolate(),
-                                                        item, &str));
-        if (str == "error") {
-          return false;
-        }
-        out.push_back(item);
-        return true;
-      })));
+      base::BindLambdaForTesting(
+          [&](v8::Local<v8::Value> item) -> IdlConvert::Status {
+            std::string str;
+            EXPECT_TRUE(gin::Converter<std::string>::FromV8(
+                v8_helper_->isolate(), item, &str));
+            if (str == "error") {
+              return IdlConvert::Status::MakeErrorMessage("Helpful error");
+            }
+            out.push_back(item);
+            return IdlConvert::Status::MakeSuccess();
+          })));
   ExpectStringList({"a", "b"}, out);
-  EXPECT_EQ(
-      "<error prefix> Conversion for an item for sequence field 'f1' failed.",
-      converter->ErrorMessage());
+  EXPECT_EQ("Helpful error", converter->ErrorMessage());
   EXPECT_FALSE(converter->FailureIsTimeout());
 }
 
@@ -1654,19 +1710,19 @@
 
   EXPECT_FALSE(converter->GetOptionalSequence(
       "f1", base::BindLambdaForTesting([&]() { saw_field = true; }),
-      base::BindLambdaForTesting([&](v8::Local<v8::Value> item) -> bool {
-        DictConverter inner(v8_helper_.get(), *time_limit_scope_,
-                            "'f1' entry: ", item);
-        double entry;
-        bool ok = inner.GetRequired("f", entry);
-        if (ok) {
-          out.push_back(entry);
-          return true;
-        } else {
-          converter->SetStatus(inner.TakeStatus());
-          return false;
-        }
-      })));
+      base::BindLambdaForTesting(
+          [&](v8::Local<v8::Value> item) -> IdlConvert::Status {
+            DictConverter inner(v8_helper_.get(), *time_limit_scope_,
+                                "'f1' entry: ", item);
+            double entry;
+            bool ok = inner.GetRequired("f", entry);
+            if (ok) {
+              out.push_back(entry);
+              return IdlConvert::Status::MakeSuccess();
+            } else {
+              return inner.TakeStatus();
+            }
+          })));
   EXPECT_THAT(out, ElementsAre(1.0, 2.0));
   EXPECT_EQ("https://example.org/:3 Uncaught Ouch.", converter->ErrorMessage());
   v8::MaybeLocal<v8::Value> exception = converter->FailureException();
@@ -1697,19 +1753,19 @@
 
   EXPECT_FALSE(converter->GetOptionalSequence(
       "f1", base::BindLambdaForTesting([&]() { saw_field = true; }),
-      base::BindLambdaForTesting([&](v8::Local<v8::Value> item) -> bool {
-        DictConverter inner(v8_helper_.get(), *time_limit_scope_,
-                            "'f1' entry: ", item);
-        double entry;
-        bool ok = inner.GetRequired("f", entry);
-        if (ok) {
-          out.push_back(entry);
-          return true;
-        } else {
-          converter->SetStatus(inner.TakeStatus());
-          return false;
-        }
-      })));
+      base::BindLambdaForTesting(
+          [&](v8::Local<v8::Value> item) -> IdlConvert::Status {
+            DictConverter inner(v8_helper_.get(), *time_limit_scope_,
+                                "'f1' entry: ", item);
+            double entry;
+            bool ok = inner.GetRequired("f", entry);
+            if (ok) {
+              out.push_back(entry);
+              return IdlConvert::Status::MakeSuccess();
+            } else {
+              return inner.TakeStatus();
+            }
+          })));
   EXPECT_THAT(out, ElementsAre(1.0, 2.0));
   EXPECT_EQ("'f1' entry: Converting field 'f' to Number timed out.",
             converter->ErrorMessage());
@@ -1785,7 +1841,7 @@
   auto converter = MakeFromScript(context, kScript);
   v8::LocalVector<v8::Value> out(v8_helper_->isolate());
   EXPECT_FALSE(GetSequence(converter.get(), "a", out));
-  EXPECT_EQ("<error prefix> Trouble iterating over 'a'.",
+  EXPECT_EQ("<error prefix> Trouble iterating over field 'a'.",
             converter->ErrorMessage());
   EXPECT_FALSE(converter->FailureIsTimeout());
 }
@@ -1806,7 +1862,7 @@
   auto converter = MakeFromScript(context, kScript);
   v8::LocalVector<v8::Value> out(v8_helper_->isolate());
   EXPECT_FALSE(GetSequence(converter.get(), "a", out));
-  EXPECT_EQ("<error prefix> Trouble iterating over 'a'.",
+  EXPECT_EQ("<error prefix> Trouble iterating over field 'a'.",
             converter->ErrorMessage());
   EXPECT_FALSE(converter->FailureIsTimeout());
 }
@@ -1848,7 +1904,7 @@
   auto converter = MakeFromScript(context, kScript);
   v8::LocalVector<v8::Value> out(v8_helper_->isolate());
   EXPECT_FALSE(GetSequence(converter.get(), "a", out));
-  EXPECT_EQ("<error prefix> Trouble iterating over 'a'.",
+  EXPECT_EQ("<error prefix> Trouble iterating over field 'a'.",
             converter->ErrorMessage());
   EXPECT_FALSE(converter->FailureIsTimeout());
 }
@@ -1869,7 +1925,7 @@
   auto converter = MakeFromScript(context, kScript);
   v8::LocalVector<v8::Value> out(v8_helper_->isolate());
   EXPECT_FALSE(GetSequence(converter.get(), "a", out));
-  EXPECT_EQ("<error prefix> Trouble iterating over 'a'.",
+  EXPECT_EQ("<error prefix> Trouble iterating over field 'a'.",
             converter->ErrorMessage());
   EXPECT_FALSE(converter->FailureIsTimeout());
 }
@@ -2005,7 +2061,7 @@
   auto converter = MakeFromScript(context, kScript);
   v8::LocalVector<v8::Value> out(v8_helper_->isolate());
   EXPECT_FALSE(GetSequence(converter.get(), "a", out));
-  EXPECT_EQ("<error prefix> Timeout iterating over 'a'.",
+  EXPECT_EQ("<error prefix> Timeout iterating over field 'a'.",
             converter->ErrorMessage());
   EXPECT_TRUE(converter->FailureIsTimeout());
 }
diff --git a/docs/testing/web_platform_tests.md b/docs/testing/web_platform_tests.md
index 5d0e159f..0f52d9b 100644
--- a/docs/testing/web_platform_tests.md
+++ b/docs/testing/web_platform_tests.md
@@ -210,14 +210,11 @@
 notification mechanism. This includes new tests that fail in Chromium, as well
 as new failures introduced to an existing test. Test owners are encouraged to
 create an `DIR_METADATA` file in the appropriate `external/wpt/` subdirectory
-that contains at least the `monorail.component` and `buganizer_public.component_id`
-fields, which the importer will use to file bugs.
+that contains at least the `buganizer_public.component_id` field, which the
+importer will use to file bugs.
 For example, `external/wpt/css/css-grid/DIR_METADATA` looks like:
 
 ```
-monorail {
-  component: "Blink>Layout>Grid"
-}
 buganizer_public {
   component_id: 1415957
 }
@@ -225,9 +222,9 @@
 ```
 
 When tests under `external/wpt/css/css-grid/` newly fail in a WPT import, the
-importer will automatically file a bug against the `Blink>Layout>Grid` component
-in [crbug.com](https://crbug.com), with details of which tests failed and the
-outputs.
+importer will automatically file a bug against the `Chromium>Blink>Layout>Grid`
+component in [issues.chromium.org](https://issues.chromium.org/issues), with
+details of which tests failed and the outputs.
 The importer will also copy `layout-dev@chromium.org` (the `team_email`) and any
 `external/wpt/css/css-grid/OWNERS` on the bug.
 
@@ -240,8 +237,8 @@
 under the located directory:
 
 ```
-monorail {
-  component: "Blink>Layout>Grid"
+buganizer_public {
+  component_id: 1415957
 }
 team_email: "layout-dev@chromium.org"
 wpt {
diff --git a/extensions/common/api/automation.idl b/extensions/common/api/automation.idl
index 7945864..87a632dd 100644
--- a/extensions/common/api/automation.idl
+++ b/extensions/common/api/automation.idl
@@ -1548,12 +1548,12 @@
     // listen to changes across all trees. Pass a filter to determine what
     // specific tree changes to listen to, and note that listnening to all
     // tree changes can be expensive.
-    [nocompile, doesNotSupportPromises="Event callback"]
+    [nocompile, trailingCallbackIsFunctionParameter]
     static void addTreeChangeObserver(
         TreeChangeObserverFilter filter, TreeChangeObserver observer);
 
     // Remove a tree change observer.
-    [nocompile, doesNotSupportPromises="Event callback"]
+    [nocompile, trailingCallbackIsFunctionParameter]
     static void removeTreeChangeObserver(
         TreeChangeObserver observer);
 
diff --git a/extensions/common/api/events.json b/extensions/common/api/events.json
index 99c7cc87..5420a03 100644
--- a/extensions/common/api/events.json
+++ b/extensions/common/api/events.json
@@ -123,22 +123,22 @@
                 "type": "array",
                 "items": {"$ref": "Rule"},
                 "description": "Rules to be registered. These do not replace previously registered rules."
-              },
-              {
-                "name": "callback",
-                "optional": true,
-                "type": "function",
-                "parameters": [
-                  {
-                    "name": "rules",
-                    "type": "array",
-                    "items": {"$ref": "Rule"},
-                    "description": "Rules that were registered, the optional parameters are filled with values."
-                  }
-                ],
-                "description": "Called with registered rules."
               }
-            ]
+            ],
+            "returns_async": {
+              "name": "callback",
+              "optional": true,
+              "parameters": [
+                {
+                  "name": "rules",
+                  "type": "array",
+                  "items": {"$ref": "Rule"},
+                  "description": "Rules that were registered, the optional parameters are filled with values."
+                }
+              ],
+              "description": "Called with registered rules.",
+              "does_not_support_promises": "Related custom hooks do not handle promises crbug.com/1520656"
+            }
           },
           {
             "name": "getRules",
@@ -163,21 +163,21 @@
                 "type": "array",
                 "items": {"type": "string"},
                 "description": "If an array is passed, only rules with identifiers contained in this array are returned."
-              },
-              {
-                "name": "callback",
-                "type": "function",
-                "parameters": [
-                  {
-                    "name": "rules",
-                    "type": "array",
-                    "items": {"$ref": "Rule"},
-                    "description": "Rules that were registered, the optional parameters are filled with values."
-                  }
-                ],
-                "description": "Called with registered rules."
               }
-            ]
+            ],
+            "returns_async": {
+              "name": "callback",
+              "parameters": [
+                {
+                  "name": "rules",
+                  "type": "array",
+                  "items": {"$ref": "Rule"},
+                  "description": "Rules that were registered, the optional parameters are filled with values."
+                }
+              ],
+              "description": "Called with registered rules.",
+              "does_not_support_promises": "Related custom hooks do not handle promises crbug.com/1520656"
+            }
           },
           {
             "name": "removeRules",
@@ -202,15 +202,15 @@
                 "type": "array",
                 "items": {"type": "string"},
                 "description": "If an array is passed, only rules with identifiers contained in this array are unregistered."
-              },
-              {
-                "name": "callback",
-                "optional": true,
-                "type": "function",
-                "parameters": [],
-                "description": "Called when rules were unregistered."
               }
-            ]
+            ],
+            "returns_async": {
+              "name": "callback",
+              "optional": true,
+              "parameters": [],
+              "description": "Called when rules were unregistered.",
+              "does_not_support_promises": "Related custom hooks do not handle promises crbug.com/1520656"
+            }
           }
         ]
       },
diff --git a/extensions/common/api/web_view_internal.json b/extensions/common/api/web_view_internal.json
index 57dd3c3c..262f219 100644
--- a/extensions/common/api/web_view_internal.json
+++ b/extensions/common/api/web_view_internal.json
@@ -199,18 +199,17 @@
             "type": "integer",
             "name": "instanceId",
             "description": "The instance ID of the guest <webview> process."
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {
-                "name": "audible",
-                "type": "boolean"
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "audible",
+              "type": "boolean"
+            }
+          ]
+        }
       },
       {
         "name": "setAudioMuted",
@@ -238,18 +237,17 @@
             "type": "integer",
             "name": "instanceId",
             "description": "The instance ID of the guest <webview> process."
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {
-                "name": "muted",
-                "type": "boolean"
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "muted",
+              "type": "boolean"
+            }
+          ]
+        }
       },
       {
         "name": "executeScript",
@@ -270,23 +268,22 @@
             "$ref": "extensionTypes.InjectDetails",
             "name": "details",
             "description": "Details of the script to run."
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "description": "Called after all the JavaScript has been executed.",
-            "parameters": [
-              {
-                "name": "result",
-                "optional": true,
-                "type": "array",
-                "items": {"type": "any", "minimum": 0},
-                "description": "The result of the script in every injected frame."
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "description": "Called after all the JavaScript has been executed.",
+          "parameters": [
+            {
+              "name": "result",
+              "optional": true,
+              "type": "array",
+              "items": {"type": "any", "minimum": 0},
+              "description": "The result of the script in every injected frame."
+            }
+          ]
+        }
       },
       {
         "name": "insertCSS",
@@ -307,15 +304,14 @@
             "$ref": "extensionTypes.InjectDetails",
             "name": "details",
             "description": "Details of the CSS text to insert."
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "description": "Called when all the CSS has been inserted.",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "description": "Called when all the CSS has been inserted.",
+          "parameters": []
+        }
       },
       {
         "name": "addContentScripts",
@@ -374,15 +370,14 @@
             "type": "number",
             "name": "zoomFactor",
             "description" : "The new zoom factor."
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "description": "Called after the zoom message has been sent to the guest process.",
-            "optional": true,
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "description": "Called after the zoom message has been sent to the guest process.",
+          "optional": true,
+          "parameters": []
+        }
       },
       {
         "name": "getZoom",
@@ -392,20 +387,19 @@
             "type": "integer",
             "name": "instanceId",
             "description": "The instance ID of the guest <webview> process."
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "description": "Called after the current zoom factor is retrieved.",
-            "parameters": [
-              {
-                "type": "number",
-                "name": "zoomFactor",
-                "description": "The current zoom factor."
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "description": "Called after the current zoom factor is retrieved.",
+          "parameters": [
+            {
+              "type": "number",
+              "name": "zoomFactor",
+              "description": "The current zoom factor."
+            }
+          ]
+        }
       },
       {
         "name": "setZoomMode",
@@ -421,15 +415,14 @@
             "$ref": "ZoomMode",
             "name": "ZoomMode",
             "description": "Defines how zooming is handled in the webview."
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "description": "Called after the zoom mode has been changed.",
-            "optional": true,
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "description": "Called after the zoom mode has been changed.",
+          "optional": true,
+          "parameters": []
+        }
       },
       {
         "name": "getZoomMode",
@@ -440,20 +433,19 @@
             "type": "integer",
             "name": "instanceId",
             "description": "The instance ID of the guest <webview> process."
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "description": "Called with the webview's current zoom mode.",
-            "parameters": [
-              {
-                "$ref": "ZoomMode",
-                "name": "ZoomMode",
-                "description": "The webview's current zoom mode."
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "description": "Called with the webview's current zoom mode.",
+          "parameters": [
+            {
+              "$ref": "ZoomMode",
+              "name": "ZoomMode",
+              "description": "The webview's current zoom mode."
+            }
+          ]
+        }
       },
       {
         "name": "find",
@@ -486,53 +478,52 @@
                 "optional": true
               }
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "description": "Called after all find results have been returned for this find request.",
-            "optional": true,
-            "parameters": [
-              {
-                "type": "object",
-                "name": "results",
-                "optional": true,
-                "properties": {
-                  "numberOfMatches": {
-                    "type": "integer",
-                    "description": "The number of times |searchText| was matched on the page."
-                  },
-                  "activeMatchOrdinal": {
-                    "type": "integer",
-                    "description": "The ordinal number of the current match."
-                  },
-                  "selectionRect": {
-                    "type": "object",
-                    "description": "Describes a rectangle around the active match.",
-                    "properties": {
-                      "left": {
-                        "type": "integer"
-                      },
-                      "top": {
-                        "type": "integer"
-                      },
-                      "width": {
-                        "type": "integer"
-                      },
-                      "height": {
-                        "type": "integer"
-                      }
+          }
+        ],
+        "returns_async": {
+          "name": "callback",
+          "description": "Called after all find results have been returned for this find request.",
+          "optional": true,
+          "parameters": [
+            {
+              "type": "object",
+              "name": "results",
+              "optional": true,
+              "properties": {
+                "numberOfMatches": {
+                  "type": "integer",
+                  "description": "The number of times |searchText| was matched on the page."
+                },
+                "activeMatchOrdinal": {
+                  "type": "integer",
+                  "description": "The ordinal number of the current match."
+                },
+                "selectionRect": {
+                  "type": "object",
+                  "description": "Describes a rectangle around the active match.",
+                  "properties": {
+                    "left": {
+                      "type": "integer"
+                    },
+                    "top": {
+                      "type": "integer"
+                    },
+                    "width": {
+                      "type": "integer"
+                    },
+                    "height": {
+                      "type": "integer"
                     }
-                  },
-                  "canceled": {
-                    "type": "boolean",
-                    "description": "Indicates whether this find request was canceled."
                   }
+                },
+                "canceled": {
+                  "type": "boolean",
+                  "description": "Indicates whether this find request was canceled."
                 }
               }
-            ]
-          }
-        ]
+            }
+          ]
+        }
       },
       {
         "name": "stopFinding",
@@ -553,10 +544,10 @@
         ]
       },
       {
-	"name": "loadDataWithBaseUrl",
-	"type": "function",
-	"description": "Loads a data URL with a specified base URL used for relative links. Optionally, a virtual URL can be provided to be shown to the user instead of the data URL.",
-	"parameters": [
+        "name": "loadDataWithBaseUrl",
+        "type": "function",
+        "description": "Loads a data URL with a specified base URL used for relative links. Optionally, a virtual URL can be provided to be shown to the user instead of the data URL.",
+        "parameters": [
           {
             "type": "integer",
             "name": "instanceId",
@@ -572,19 +563,18 @@
             "name": "baseUrl",
             "description": "The base URL that will be used for relative links."
           },
-	  {
+          {
             "type": "string",
             "name": "virtualUrl",
             "description": "The URL that will be displayed to the user.",
-	    "optional": true
-          },
-	  {
-            "type": "function",
-            "name": "callback",
-            "description": "Called internally for the purpose of reporting errors to console.error().",
-            "parameters": []
+            "optional": true
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "description": "Called internally for the purpose of reporting errors to console.error().",
+          "parameters": []
+        }
       },
       {
         "name": "go",
@@ -597,20 +587,19 @@
           {
             "type": "integer",
             "name": "relativeIndex"
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "parameters": [
-              {
-                "name": "success",
-                "type": "boolean",
-                "description": "Indicates whether the navigation was successful."
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": [
+            {
+              "name": "success",
+              "type": "boolean",
+              "description": "Indicates whether the navigation was successful."
+            }
+          ]
+        }
       },
       {
         "name": "overrideUserAgent",
@@ -698,19 +687,18 @@
             "type": "string",
             "name": "userInput",
             "optional": true
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "parameters": [
-              {
-                "name": "allowed",
-                "type": "boolean"
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": [
+            {
+              "name": "allowed",
+              "type": "boolean"
+            }
+          ]
+        }
       },
       {
         "name": "navigate",
@@ -760,18 +748,17 @@
             "$ref": "extensionTypes.ImageDetails",
             "name": "options",
             "optional": true
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {"type": "string",
-                "name": "dataUrl",
-                "description": "A data URL which encodes an image of the visible area of the captured tab. May be assigned to the 'src' property of an HTML Image element for display."
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {"type": "string",
+              "name": "dataUrl",
+              "description": "A data URL which encodes an image of the visible area of the captured tab. May be assigned to the 'src' property of an HTML Image element for display."
+            }
+          ]
+        }
       },
       {
         "name": "setSpatialNavigationEnabled",
@@ -799,18 +786,17 @@
             "type": "integer",
             "name": "instanceId",
             "description": "The instance ID of the guest <webview> process."
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {
-                "name": "spatialNavEnabled",
-                "type": "boolean"
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "spatialNavEnabled",
+              "type": "boolean"
+            }
+          ]
+        }
       },
       {
         "name": "clearData",
@@ -830,15 +816,14 @@
             "name": "dataToRemove",
             "$ref": "DataTypeSet",
             "description": "The set of data types to remove."
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "description": "Called when deletion has completed.",
-            "optional": true,
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "description": "Called when deletion has completed.",
+          "optional": true,
+          "parameters": []
+        }
       }
     ]
   }
diff --git a/extensions/renderer/api/i18n_hooks_delegate.cc b/extensions/renderer/api/i18n_hooks_delegate.cc
index 038f465..313dc79 100644
--- a/extensions/renderer/api/i18n_hooks_delegate.cc
+++ b/extensions/renderer/api/i18n_hooks_delegate.cc
@@ -14,6 +14,7 @@
 #include "content/public/renderer/render_frame.h"
 #include "content/public/renderer/render_thread.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/common/message_bundle.h"
 #include "extensions/renderer/bindings/api_binding_types.h"
 #include "extensions/renderer/bindings/js_runner.h"
@@ -149,7 +150,7 @@
 // substitutions. This can result in a synchronous IPC being sent to the browser
 // for the first call related to an extension in this process.
 v8::Local<v8::Value> GetI18nMessage(const std::string& message_name,
-                                    const std::string& extension_id,
+                                    const ExtensionId& extension_id,
                                     v8::Local<v8::Value> v8_substitutions,
                                     v8::Local<v8::Value> v8_options,
                                     SharedL10nMap::IPCTarget* ipc_target,
diff --git a/extensions/renderer/bindings/api_binding.cc b/extensions/renderer/bindings/api_binding.cc
index b6d2064d..817a9ba 100644
--- a/extensions/renderer/bindings/api_binding.cc
+++ b/extensions/renderer/bindings/api_binding.cc
@@ -66,8 +66,7 @@
 
 std::unique_ptr<APISignature> GetAPISignatureFromDictionary(
     const base::Value::Dict* dict,
-    BindingAccessChecker* access_checker,
-    const std::string& api_name) {
+    BindingAccessChecker* access_checker) {
   const base::Value* params = dict->Find("parameters");
   if (params && !params->is_list())
     params = nullptr;
@@ -77,8 +76,7 @@
   if (returns_async && !returns_async->is_dict())
     returns_async = nullptr;
 
-  return APISignature::CreateFromValues(*params, returns_async, access_checker,
-                                        api_name, false /*is_event_signature*/);
+  return APISignature::CreateFromValues(*params, returns_async, access_checker);
 }
 
 void RunAPIBindingHandlerCallback(
@@ -222,8 +220,7 @@
       std::string full_name =
           base::StringPrintf("%s.%s", api_name_.c_str(), name->c_str());
 
-      auto signature =
-          GetAPISignatureFromDictionary(func_dict, access_checker, full_name);
+      auto signature = GetAPISignatureFromDictionary(func_dict, access_checker);
 
       methods_[*name] =
           std::make_unique<MethodData>(full_name, signature.get());
@@ -265,8 +262,8 @@
           std::string full_name =
               base::StringPrintf("%s.%s", id->c_str(), function_name->c_str());
 
-          auto signature = GetAPISignatureFromDictionary(
-              func_dict, access_checker, full_name);
+          auto signature =
+              GetAPISignatureFromDictionary(func_dict, access_checker);
 
           type_refs->AddTypeMethodSignature(full_name, std::move(signature));
         }
@@ -345,9 +342,9 @@
         // TODO(devlin): Track this down and CHECK(params).
         base::Value empty_params(base::Value::Type::LIST);
         std::unique_ptr<APISignature> event_signature =
-            APISignature::CreateFromValues(
-                params ? *params : empty_params, nullptr /*returns_async*/,
-                access_checker, *name, true /*is_event_signature*/);
+            APISignature::CreateFromValues(params ? *params : empty_params,
+                                           nullptr /*returns_async*/,
+                                           access_checker);
         DCHECK(!event_signature->has_async_return());
         type_refs_->AddEventSignature(full_name, std::move(event_signature));
       }
diff --git a/extensions/renderer/bindings/api_binding_bridge_unittest.cc b/extensions/renderer/bindings/api_binding_bridge_unittest.cc
index 17e6562..40388544 100644
--- a/extensions/renderer/bindings/api_binding_bridge_unittest.cc
+++ b/extensions/renderer/bindings/api_binding_bridge_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "extensions/renderer/bindings/api_binding_bridge.h"
 
+#include "extensions/common/extension_id.h"
 #include "extensions/renderer/bindings/api_binding_hooks.h"
 #include "extensions/renderer/bindings/api_binding_test.h"
 #include "extensions/renderer/bindings/api_binding_test_util.h"
@@ -18,7 +19,7 @@
   v8::Local<v8::Context> context = MainContext();
   v8::Context::Scope context_scope(context);
 
-  std::string extension_id(32, 'a');
+  ExtensionId extension_id = std::string(32, 'a');
   std::string context_type = "context type";
   v8::Local<v8::Object> api_object = v8::Object::New(isolate());
 
diff --git a/extensions/renderer/bindings/api_binding_js_util.cc b/extensions/renderer/bindings/api_binding_js_util.cc
index e2730b28..43c8d321 100644
--- a/extensions/renderer/bindings/api_binding_js_util.cc
+++ b/extensions/renderer/bindings/api_binding_js_util.cc
@@ -320,9 +320,7 @@
   type_refs_->AddCustomSignature(
       custom_signature_name,
       APISignature::CreateFromValues(*base_signature, nullptr /*returns_async*/,
-                                     nullptr /*access_checker*/,
-                                     custom_signature_name,
-                                     false /*is_event_signature*/));
+                                     nullptr /*access_checker*/));
 }
 
 void APIBindingJSUtil::ValidateCustomSignature(
diff --git a/extensions/renderer/bindings/api_binding_unittest.cc b/extensions/renderer/bindings/api_binding_unittest.cc
index 2df38e8f..518ea35 100644
--- a/extensions/renderer/bindings/api_binding_unittest.cc
+++ b/extensions/renderer/bindings/api_binding_unittest.cc
@@ -75,10 +75,11 @@
     "  'parameters': [{"
     "    'name': 'int',"
     "    'type': 'integer'"
-    "  }, {"
+    "  }],"
+    "  'returns_async': {"
     "    'name': 'callback',"
     "    'type': 'function'"
-    "  }]"
+    "  }"
     "}]";
 
 constexpr char kFunctionsWithCallbackSignatures[] = R"(
@@ -90,21 +91,23 @@
        }]
      }, {
        "name": "intCallback",
-       "parameters": [{
+       "parameters": [],
+       "returns_async": {
          "name": "callback",
-         "type": "function",
+         "does_not_support_promises": "Test",
          "parameters": [{
            "name": "int",
            "type": "integer"
          }]
-       }]
+       }
      }, {
        "name": "noParamCallback",
-       "parameters": [{
+       "parameters": [],
+       "returns_async": {
          "name": "callback",
-         "type": "function",
+         "does_not_support_promises": "Test",
          "parameters": []
-       }]
+       }
      }])";
 
 constexpr char kFunctionsWithPromiseSignatures[] =
diff --git a/extensions/renderer/bindings/api_signature.cc b/extensions/renderer/bindings/api_signature.cc
index 8dfadcd..ede8ba1f 100644
--- a/extensions/renderer/bindings/api_signature.cc
+++ b/extensions/renderer/bindings/api_signature.cc
@@ -21,18 +21,6 @@
 
 namespace {
 
-// A list of API's which have a trailing function parameter that is not actually
-// meant to be treated like a traditional API success callback. For more details
-// see the comment where this is used in APISignature::CreateFromValues.
-constexpr const char* const kNonCallbackTrailingFunctionAPINames[] = {
-    "test.listenForever",
-    "test.listenOnce",
-    "test.callbackPass",
-    "test.callbackFail",
-    "automation.addTreeChangeObserver",
-    "automation.removeTreeChangeObserver",
-};
-
 std::vector<std::unique_ptr<ArgumentSpec>> ValueListToArgumentSpecs(
     const base::Value::List& specification_list,
     bool uses_returns_async) {
@@ -52,11 +40,13 @@
 }
 
 std::unique_ptr<APISignature::ReturnsAsync> BuildReturnsAsyncFromValues(
-    const base::Value::Dict& returns_async_spec,
-    bool api_supports_promises) {
+    const base::Value::Dict& returns_async_spec) {
   auto returns_async = std::make_unique<APISignature::ReturnsAsync>();
-  if (api_supports_promises)
-    returns_async->promise_support = binding::APIPromiseSupport::kSupported;
+
+  returns_async->promise_support =
+      returns_async_spec.Find("does_not_support_promises")
+          ? binding::APIPromiseSupport::kUnsupported
+          : binding::APIPromiseSupport::kSupported;
   std::optional<bool> callback_optional =
       returns_async_spec.FindBool("optional");
   returns_async->optional = callback_optional.value_or(false);
@@ -560,35 +550,17 @@
 std::unique_ptr<APISignature> APISignature::CreateFromValues(
     const base::Value& spec_list,
     const base::Value* returns_async,
-    BindingAccessChecker* access_checker,
-    const std::string& api_name,
-    bool is_event_signature) {
+    BindingAccessChecker* access_checker) {
   bool uses_returns_async = returns_async != nullptr;
   auto argument_specs =
       ValueListToArgumentSpecs(spec_list.GetList(), uses_returns_async);
 
-  // Asynchronous returns for an API are either defined in the returns_async
-  // part of the specification or as a trailing function argument.
-  // TODO(crbug.com/1288583): There are a handful of APIs which have a trailing
-  // function argument that is not a traditional callback. Once we have moved
-  // all the asynchronous API schemas to use the returns_async format it will be
-  // clear when this is the case, but for now we keep a list of the names of
-  // these APIs to ensure we are handling them correctly.
-  // This is irrelevant for event signatures.
-  const base::Value* returns_async_spec = returns_async;
-  if (!is_event_signature && !argument_specs.empty() &&
-      argument_specs.back()->type() == ArgumentType::FUNCTION &&
-      !base::Contains(kNonCallbackTrailingFunctionAPINames, api_name)) {
-    DCHECK(!returns_async_spec);
-    returns_async_spec = &spec_list.GetList().back();
-    argument_specs.pop_back();
-  }
-
+  // Asynchronous returns for an API are defined in the returns_async part of
+  // the specification.
   std::unique_ptr<APISignature::ReturnsAsync> returns_async_struct;
-  if (returns_async_spec) {
-    bool api_supports_promises = returns_async != nullptr;
-    returns_async_struct = BuildReturnsAsyncFromValues(
-        returns_async_spec->GetDict(), api_supports_promises);
+  if (returns_async) {
+    returns_async_struct =
+        BuildReturnsAsyncFromValues(returns_async->GetDict());
   }
 
   return std::make_unique<APISignature>(std::move(argument_specs),
diff --git a/extensions/renderer/bindings/api_signature.h b/extensions/renderer/bindings/api_signature.h
index 132cd10..ce848bad 100644
--- a/extensions/renderer/bindings/api_signature.h
+++ b/extensions/renderer/bindings/api_signature.h
@@ -67,9 +67,7 @@
   static std::unique_ptr<APISignature> CreateFromValues(
       const base::Value& specification_list,
       const base::Value* returns_async,
-      BindingAccessChecker* access_checker,
-      const std::string& api_name,
-      bool is_event_signature);
+      BindingAccessChecker* access_checker);
 
   struct V8ParseResult {
     // Appease the Chromium style plugin (out of line ctor/dtor).
diff --git a/extensions/renderer/dispatcher.cc b/extensions/renderer/dispatcher.cc
index f4982b8cf..1231e60 100644
--- a/extensions/renderer/dispatcher.cc
+++ b/extensions/renderer/dispatcher.cc
@@ -40,6 +40,7 @@
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_api.h"
 #include "extensions/common/extension_features.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/common/extension_urls.h"
 #include "extensions/common/extensions_client.h"
 #include "extensions/common/features/behavior_feature.h"
@@ -336,7 +337,7 @@
 
   // Initialize host permissions for any extensions that were activated before
   // WebKit was initialized.
-  for (const std::string& extension_id : active_extension_ids_) {
+  for (const ExtensionId& extension_id : active_extension_ids_) {
     const Extension* extension =
         RendererExtensionRegistry::Get()->GetByID(extension_id);
     CHECK(extension);
@@ -369,7 +370,7 @@
   RunScriptsAtDocumentStart(render_frame);
 }
 
-bool Dispatcher::IsExtensionActive(const std::string& extension_id) const {
+bool Dispatcher::IsExtensionActive(const ExtensionId& extension_id) const {
   const bool is_active = base::Contains(active_extension_ids_, extension_id);
   if (is_active)
     CHECK(RendererExtensionRegistry::Get()->Contains(extension_id));
@@ -721,7 +722,7 @@
     g_worker_script_context_set.Get().Remove(v8_context, script_url);
   }
 
-  std::string extension_id =
+  ExtensionId extension_id =
       RendererExtensionRegistry::Get()->GetExtensionOrAppIDByURL(script_url);
   {
     base::AutoLock lock(service_workers_paused_for_on_loaded_message_lock_);
@@ -816,7 +817,7 @@
 }
 
 void Dispatcher::InvokeModuleSystemMethod(content::RenderFrame* render_frame,
-                                          const std::string& extension_id,
+                                          const ExtensionId& extension_id,
                                           const std::string& module_name,
                                           const std::string& function_name,
                                           const base::Value::List& args) {
@@ -1035,7 +1036,7 @@
   dispatcher_.Bind(std::move(dispatcher));
 }
 
-void Dispatcher::ActivateExtension(const std::string& extension_id) {
+void Dispatcher::ActivateExtension(const ExtensionId& extension_id) {
   TRACE_RENDERER_EXTENSION_EVENT("Dispatcher::ActivateExtension", extension_id);
 
   const Extension* extension =
@@ -1078,7 +1079,7 @@
     std::vector<mojom::ExtensionLoadedParamsPtr> loaded_extensions) {
   for (auto& param : loaded_extensions) {
     std::string error;
-    std::string id = param->id;
+    ExtensionId id = param->id;
     std::optional<base::UnguessableToken> worker_activation_token =
         param->worker_activation_token;
 
@@ -1139,7 +1140,7 @@
   UpdateAllBindings(/*api_permissions_changed=*/false);
 }
 
-void Dispatcher::UnloadExtension(const std::string& extension_id) {
+void Dispatcher::UnloadExtension(const ExtensionId& extension_id) {
   TRACE_RENDERER_EXTENSION_EVENT("Dispatcher::UnloadExtension", extension_id);
 
   // See comment in OnLoaded for why it would be nice, but perhaps incorrect,
@@ -1196,7 +1197,7 @@
 }
 
 void Dispatcher::SuspendExtension(
-    const std::string& extension_id,
+    const ExtensionId& extension_id,
     mojom::Renderer::SuspendExtensionCallback callback) {
   TRACE_RENDERER_EXTENSION_EVENT("Dispatcher::SuspendExtension", extension_id);
 
@@ -1210,7 +1211,7 @@
   std::move(callback).Run();
 }
 
-void Dispatcher::CancelSuspendExtension(const std::string& extension_id) {
+void Dispatcher::CancelSuspendExtension(const ExtensionId& extension_id) {
   DispatchEventHelper(GenerateHostIdFromExtensionId(extension_id),
                       kOnSuspendCanceledEvent, base::Value::List(), nullptr);
 }
@@ -1228,7 +1229,7 @@
 }
 
 void Dispatcher::SetScriptingAllowlist(
-    const std::vector<std::string>& extension_ids) {
+    const std::vector<ExtensionId>& extension_ids) {
   ExtensionsClient::Get()->SetScriptingAllowlist(extension_ids);
 }
 
@@ -1240,7 +1241,7 @@
       default_policy_allowed_hosts);
   // Update blink host permission allowlist exceptions for all loaded
   // extensions.
-  for (const std::string& extension_id :
+  for (const ExtensionId& extension_id :
        RendererExtensionRegistry::Get()->GetIDs()) {
     const Extension* extension =
         RendererExtensionRegistry::Get()->GetByID(extension_id);
@@ -1268,7 +1269,7 @@
   // point in updating it.
 }
 
-void Dispatcher::UpdateTabSpecificPermissions(const std::string& extension_id,
+void Dispatcher::UpdateTabSpecificPermissions(const ExtensionId& extension_id,
                                               URLPatternSet new_hosts,
                                               int tab_id,
                                               bool update_origin_allowlist) {
@@ -1294,10 +1295,10 @@
 }
 
 void Dispatcher::ClearTabSpecificPermissions(
-    const std::vector<std::string>& extension_ids,
+    const std::vector<ExtensionId>& extension_ids,
     int tab_id,
     bool update_origin_allowlist) {
-  for (const std::string& id : extension_ids) {
+  for (const ExtensionId& id : extension_ids) {
     const Extension* extension = RendererExtensionRegistry::Get()->GetByID(id);
     if (extension) {
       extension->permissions_data()->ClearTabSpecificPermissions(tab_id);
@@ -1374,7 +1375,7 @@
   std::move(callback).Run();
 }
 
-void Dispatcher::UpdatePermissions(const std::string& extension_id,
+void Dispatcher::UpdatePermissions(const ExtensionId& extension_id,
                                    PermissionSet active_permissions,
                                    PermissionSet withheld_permissions,
                                    URLPatternSet policy_blocked_hosts,
@@ -1404,8 +1405,9 @@
 void Dispatcher::SetActivityLoggingEnabled(bool enabled) {
   activity_logging_enabled_ = enabled;
   if (enabled) {
-    for (const std::string& id : active_extension_ids_)
+    for (const ExtensionId& id : active_extension_ids_) {
       DOMActivityLogger::AttachToWorld(DOMActivityLogger::kMainWorldId, id);
+    }
   }
   script_injection_manager_->set_activity_logging_enabled(enabled);
   user_script_set_manager_->set_activity_logging_enabled(enabled);
@@ -1423,7 +1425,7 @@
 }
 
 void Dispatcher::UpdateActiveExtensions() {
-  std::set<std::string> active_extensions = active_extension_ids_;
+  std::set<ExtensionId> active_extensions = active_extension_ids_;
   user_script_set_manager_->GetAllActiveExtensionIds(&active_extensions);
   delegate_->OnActiveExtensionsUpdated(active_extensions);
 }
diff --git a/extensions/renderer/dispatcher.h b/extensions/renderer/dispatcher.h
index 67dfbf48..572c95b 100644
--- a/extensions/renderer/dispatcher.h
+++ b/extensions/renderer/dispatcher.h
@@ -114,7 +114,7 @@
 
   void OnRenderFrameCreated(content::RenderFrame* render_frame);
 
-  bool IsExtensionActive(const std::string& extension_id) const;
+  bool IsExtensionActive(const ExtensionId& extension_id) const;
 
   void DidCreateScriptContext(blink::WebLocalFrame* frame,
                               const v8::Local<v8::Context>& context,
@@ -182,7 +182,7 @@
 
   // Shared implementation of the various MessageInvoke IPCs.
   void InvokeModuleSystemMethod(content::RenderFrame* render_frame,
-                                const std::string& extension_id,
+                                const ExtensionId& extension_id,
                                 const std::string& module_name,
                                 const std::string& function_name,
                                 const base::Value::List& args);
@@ -229,15 +229,15 @@
       blink::AssociatedInterfaceRegistry* associated_interfaces) override;
 
   // mojom::Renderer implementation:
-  void ActivateExtension(const std::string& extension_id) override;
+  void ActivateExtension(const ExtensionId& extension_id) override;
   void SetActivityLoggingEnabled(bool enabled) override;
   void LoadExtensions(
       std::vector<mojom::ExtensionLoadedParamsPtr> loaded_extensions) override;
-  void UnloadExtension(const std::string& extension_id) override;
+  void UnloadExtension(const ExtensionId& extension_id) override;
   void SuspendExtension(
-      const std::string& extension_id,
+      const ExtensionId& extension_id,
       mojom::Renderer::SuspendExtensionCallback callback) override;
-  void CancelSuspendExtension(const std::string& extension_id) override;
+  void CancelSuspendExtension(const ExtensionId& extension_id) override;
   void SetDeveloperMode(bool current_developer_mode) override;
   void SetSessionInfo(version_info::Channel channel,
                       mojom::FeatureSessionType session_type,
@@ -246,11 +246,11 @@
                      const std::string& font_size) override;
   void SetWebViewPartitionID(const std::string& partition_id) override;
   void SetScriptingAllowlist(
-      const std::vector<std::string>& extension_ids) override;
+      const std::vector<ExtensionId>& extension_ids) override;
   void UpdateUserScriptWorld(mojom::UserScriptWorldInfoPtr info) override;
   void ShouldSuspend(ShouldSuspendCallback callback) override;
   void TransferBlobs(TransferBlobsCallback callback) override;
-  void UpdatePermissions(const std::string& extension_id,
+  void UpdatePermissions(const ExtensionId& extension_id,
                          PermissionSet active_permissions,
                          PermissionSet withheld_permissions,
                          URLPatternSet policy_blocked_hosts,
@@ -261,14 +261,14 @@
       URLPatternSet default_policy_allowed_hosts) override;
   void UpdateUserHostRestrictions(URLPatternSet user_blocked_hosts,
                                   URLPatternSet user_allowed_hosts) override;
-  void UpdateTabSpecificPermissions(const std::string& extension_id,
+  void UpdateTabSpecificPermissions(const ExtensionId& extension_id,
                                     URLPatternSet new_hosts,
                                     int tab_id,
                                     bool update_origin_allowlist) override;
   void UpdateUserScripts(base::ReadOnlySharedMemoryRegion shared_memory,
                          mojom::HostIDPtr host_id) override;
   void ClearTabSpecificPermissions(
-      const std::vector<std::string>& extension_ids,
+      const std::vector<ExtensionId>& extension_ids,
       int tab_id,
       bool update_origin_allowlist) override;
   void WatchPages(const std::vector<std::string>& css_selectors) override;
@@ -346,7 +346,7 @@
 
   // The IDs of extensions that failed to load, mapped to the error message
   // generated on failure.
-  std::map<std::string, std::string> extension_load_errors_;
+  std::map<ExtensionId, std::string> extension_load_errors_;
 
   // All the bindings contexts that are currently loaded for this renderer.
   // There is zero or one for each v8 context.
diff --git a/extensions/renderer/dom_activity_logger.cc b/extensions/renderer/dom_activity_logger.cc
index 01e23e9c..dab2653 100644
--- a/extensions/renderer/dom_activity_logger.cc
+++ b/extensions/renderer/dom_activity_logger.cc
@@ -10,6 +10,7 @@
 #include "content/public/renderer/render_frame.h"
 #include "content/public/renderer/v8_value_converter.h"
 #include "extensions/common/dom_action_types.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/renderer/activity_log_converter_strategy.h"
 #include "extensions/renderer/extension_frame_helper.h"
 #include "extensions/renderer/script_context.h"
@@ -46,13 +47,13 @@
 
 }  // namespace
 
-DOMActivityLogger::DOMActivityLogger(const std::string& extension_id)
+DOMActivityLogger::DOMActivityLogger(const ExtensionId& extension_id)
     : extension_id_(extension_id) {}
 
 DOMActivityLogger::~DOMActivityLogger() = default;
 
 void DOMActivityLogger::AttachToWorld(int32_t world_id,
-                                      const std::string& extension_id) {
+                                      const ExtensionId& extension_id) {
   // If there is no logger registered for world_id, construct a new logger
   // and register it with world_id.
   if (!blink::HasDOMActivityLogger(world_id,
diff --git a/extensions/renderer/dom_activity_logger.h b/extensions/renderer/dom_activity_logger.h
index c179879..6242339 100644
--- a/extensions/renderer/dom_activity_logger.h
+++ b/extensions/renderer/dom_activity_logger.h
@@ -5,9 +5,6 @@
 #ifndef EXTENSIONS_RENDERER_DOM_ACTIVITY_LOGGER_H_
 #define EXTENSIONS_RENDERER_DOM_ACTIVITY_LOGGER_H_
 
-#include <memory>
-#include <string>
-
 #include "base/values.h"
 #include "extensions/common/dom_action_types.h"
 #include "extensions/common/extension_id.h"
@@ -28,7 +25,7 @@
 class DOMActivityLogger: public blink::WebDOMActivityLogger {
  public:
   static const int kMainWorldId = 0;
-  explicit DOMActivityLogger(const std::string& extension_id);
+  explicit DOMActivityLogger(const ExtensionId& extension_id);
 
   DOMActivityLogger(const DOMActivityLogger&) = delete;
   DOMActivityLogger& operator=(const DOMActivityLogger&) = delete;
@@ -38,7 +35,7 @@
   // Check (using the WebKit API) if there is no logger attached to the world
   // corresponding to world_id, and if so, construct a new logger and attach it.
   // world_id = 0 indicates the main world.
-  static void AttachToWorld(int32_t world_id, const std::string& extension_id);
+  static void AttachToWorld(int32_t world_id, const ExtensionId& extension_id);
 
  private:
   // blink::WebDOMActivityLogger implementation.
diff --git a/extensions/renderer/extension_frame_helper.cc b/extensions/renderer/extension_frame_helper.cc
index 6a2f779..fd064c3 100644
--- a/extensions/renderer/extension_frame_helper.cc
+++ b/extensions/renderer/extension_frame_helper.cc
@@ -16,6 +16,7 @@
 #include "extensions/common/api/messaging/port_id.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension_features.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/common/manifest_handlers/background_info.h"
 #include "extensions/renderer/api/messaging/native_renderer_messaging_service.h"
 #include "extensions/renderer/console.h"
@@ -57,7 +58,7 @@
                         mojom::ViewType match_view_type,
                         int match_window_id,
                         int match_tab_id,
-                        const std::string& match_extension_id) {
+                        const ExtensionId& match_extension_id) {
   if (match_view_type != mojom::ViewType::kInvalid &&
       frame_helper->view_type() != match_view_type)
     return false;
@@ -156,7 +157,7 @@
 
 // static
 std::vector<content::RenderFrame*> ExtensionFrameHelper::GetExtensionFrames(
-    const std::string& extension_id,
+    const ExtensionId& extension_id,
     int browser_window_id,
     int tab_id,
     mojom::ViewType view_type) {
@@ -172,7 +173,7 @@
 // static
 v8::Local<v8::Array> ExtensionFrameHelper::GetV8MainFrames(
     v8::Local<v8::Context> context,
-    const std::string& extension_id,
+    const ExtensionId& extension_id,
     int browser_window_id,
     int tab_id,
     mojom::ViewType view_type) {
@@ -208,7 +209,7 @@
 
 // static
 content::RenderFrame* ExtensionFrameHelper::GetBackgroundPageFrame(
-    const std::string& extension_id) {
+    const ExtensionId& extension_id) {
   for (const ExtensionFrameHelper* helper : g_frame_helpers.Get()) {
     if (RenderFrameMatches(helper, mojom::ViewType::kExtensionBackgroundPage,
                            extension_misc::kUnknownWindowId,
@@ -225,7 +226,7 @@
 
 v8::Local<v8::Value> ExtensionFrameHelper::GetV8BackgroundPageMainFrame(
     v8::Isolate* isolate,
-    const std::string& extension_id) {
+    const ExtensionId& extension_id) {
   content::RenderFrame* main_frame = GetBackgroundPageFrame(extension_id);
   blink::WebLocalFrame* web_frame =
       main_frame ? main_frame->GetWebFrame() : nullptr;
@@ -453,7 +454,7 @@
   view_type_ = type;
 }
 
-void ExtensionFrameHelper::MessageInvoke(const std::string& extension_id,
+void ExtensionFrameHelper::MessageInvoke(const ExtensionId& extension_id,
                                          const std::string& module_name,
                                          const std::string& function_name,
                                          base::Value::List args) {
@@ -524,7 +525,7 @@
 
 void ExtensionFrameHelper::ExecuteDeclarativeScript(
     int32_t tab_id,
-    const std::string& extension_id,
+    const ExtensionId& extension_id,
     const std::string& script_id,
     const GURL& url) {
   // TODO(https://crbug.com/1186220): URL-checking isn't the best approach to
diff --git a/extensions/renderer/extension_frame_helper.h b/extensions/renderer/extension_frame_helper.h
index 0594534..72f693a 100644
--- a/extensions/renderer/extension_frame_helper.h
+++ b/extensions/renderer/extension_frame_helper.h
@@ -14,6 +14,7 @@
 #include "base/values.h"
 #include "content/public/renderer/render_frame_observer.h"
 #include "content/public/renderer/render_frame_observer_tracker.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/common/mojom/automation_registry.mojom.h"
 #include "extensions/common/mojom/event_router.mojom.h"
 #include "extensions/common/mojom/frame.mojom.h"
@@ -49,7 +50,7 @@
   // criteria. A |browser_window_id| of extension_misc::kUnknownWindowId
   // specifies "all", as does a |view_type| of mojom::ViewType::kInvalid.
   static std::vector<content::RenderFrame*> GetExtensionFrames(
-      const std::string& extension_id,
+      const ExtensionId& extension_id,
       int browser_window_id,
       int tab_id,
       mojom::ViewType view_type);
@@ -59,7 +60,7 @@
   // current context.
   // Returns an empty v8::Array if no frames are found.
   static v8::Local<v8::Array> GetV8MainFrames(v8::Local<v8::Context> context,
-                                              const std::string& extension_id,
+                                              const ExtensionId& extension_id,
                                               int browser_window_id,
                                               int tab_id,
                                               mojom::ViewType view_type);
@@ -67,14 +68,14 @@
   // Returns the main frame of the extension's background page, or null if there
   // isn't one in this process.
   static content::RenderFrame* GetBackgroundPageFrame(
-      const std::string& extension_id);
+      const ExtensionId& extension_id);
   // Same as above, but returns the background page's main frame, or
   // v8::Undefined if there is none. Note: This will assert that the
   // isolate's current context can access the returned object; callers should
   // ensure that the current context is correct.
   static v8::Local<v8::Value> GetV8BackgroundPageMainFrame(
       v8::Isolate* isolate,
-      const std::string& extension_id);
+      const ExtensionId& extension_id);
 
   // Finds a neighboring extension frame with the same extension as the one
   // owning |relative_to_frame| (if |relative_to_frame| is not an extension
@@ -112,14 +113,14 @@
   void SetTabId(int32_t id) override;
   void AppWindowClosed(bool send_onclosed) override;
   void NotifyRenderViewType(mojom::ViewType view_type) override;
-  void MessageInvoke(const std::string& extension_id,
+  void MessageInvoke(const ExtensionId& extension_id,
                      const std::string& module_name,
                      const std::string& function_name,
                      base::Value::List args) override;
   void ExecuteCode(mojom::ExecuteCodeParamsPtr param,
                    ExecuteCodeCallback callback) override;
   void ExecuteDeclarativeScript(int32_t tab_id,
-                                const std::string& extension_id,
+                                const ExtensionId& extension_id,
                                 const std::string& script_id,
                                 const GURL& url) override;
   void UpdateBrowserWindowId(int32_t window_id) override;
diff --git a/extensions/renderer/extension_injection_host.cc b/extensions/renderer/extension_injection_host.cc
index d1cf50d7..7c8cbd2 100644
--- a/extensions/renderer/extension_injection_host.cc
+++ b/extensions/renderer/extension_injection_host.cc
@@ -6,6 +6,7 @@
 
 #include "content/public/renderer/render_frame.h"
 #include "extensions/common/constants.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/common/manifest_handlers/csp_info.h"
 #include "extensions/common/mojom/host_id.mojom.h"
 #include "extensions/renderer/extension_web_view_helper.h"
@@ -24,7 +25,7 @@
 
 // static
 std::unique_ptr<const InjectionHost> ExtensionInjectionHost::Create(
-    const std::string& extension_id) {
+    const ExtensionId& extension_id) {
   const Extension* extension =
       RendererExtensionRegistry::Get()->GetByID(extension_id);
   if (!extension)
diff --git a/extensions/renderer/extension_injection_host.h b/extensions/renderer/extension_injection_host.h
index dd051c6f..2978229 100644
--- a/extensions/renderer/extension_injection_host.h
+++ b/extensions/renderer/extension_injection_host.h
@@ -7,6 +7,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/renderer/injection_host.h"
 
 namespace extensions {
@@ -25,7 +26,7 @@
   // Create an ExtensionInjectionHost object. If the extension is gone, returns
   // a null scoped ptr.
   static std::unique_ptr<const InjectionHost> Create(
-      const std::string& extension_id);
+      const ExtensionId& extension_id);
 
  private:
   // InjectionHost:
diff --git a/extensions/renderer/extension_localization_throttle.cc b/extensions/renderer/extension_localization_throttle.cc
index 21ccd79..8b01618 100644
--- a/extensions/renderer/extension_localization_throttle.cc
+++ b/extensions/renderer/extension_localization_throttle.cc
@@ -37,7 +37,7 @@
  public:
   ExtensionLocalizationURLLoader(
       const std::optional<blink::LocalFrameToken>& frame_token,
-      const std::string& extension_id,
+      const ExtensionId& extension_id,
       mojo::PendingRemote<network::mojom::URLLoaderClient>
           destination_url_loader_client)
       : frame_token_(frame_token),
diff --git a/extensions/renderer/native_extension_bindings_system.cc b/extensions/renderer/native_extension_bindings_system.cc
index 59f5d58..5496e887 100644
--- a/extensions/renderer/native_extension_bindings_system.cc
+++ b/extensions/renderer/native_extension_bindings_system.cc
@@ -18,6 +18,7 @@
 #include "content/public/renderer/render_thread.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension_api.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/common/features/feature.h"
 #include "extensions/common/features/feature_provider.h"
 #include "extensions/common/manifest_constants.h"
@@ -381,7 +382,7 @@
 
 std::string GetContextOwner(v8::Local<v8::Context> context) {
   ScriptContext* script_context = GetScriptContextFromV8ContextChecked(context);
-  const std::string& extension_id = script_context->GetExtensionID();
+  const ExtensionId& extension_id = script_context->GetExtensionID();
   bool id_is_valid = crx_file::id_util::IdIsValid(extension_id);
   CHECK(id_is_valid || script_context->url().is_valid());
   // Use only origin for URLs to match browser logic in EventListener::ForURL().
diff --git a/extensions/renderer/native_extension_bindings_system.h b/extensions/renderer/native_extension_bindings_system.h
index 549efff6..d6f8254 100644
--- a/extensions/renderer/native_extension_bindings_system.h
+++ b/extensions/renderer/native_extension_bindings_system.h
@@ -10,6 +10,7 @@
 
 #include "base/memory/weak_ptr.h"
 #include "base/values.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/common/mojom/event_dispatcher.mojom-forward.h"
 #include "extensions/renderer/api/messaging/native_renderer_messaging_service.h"
 #include "extensions/renderer/bindings/api_binding_types.h"
diff --git a/extensions/renderer/process_info_native_handler.cc b/extensions/renderer/process_info_native_handler.cc
index e9e1869..d898d1f 100644
--- a/extensions/renderer/process_info_native_handler.cc
+++ b/extensions/renderer/process_info_native_handler.cc
@@ -5,6 +5,7 @@
 #include "extensions/renderer/process_info_native_handler.h"
 
 #include "base/functional/bind.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/renderer/script_context.h"
 #include "gin/converter.h"
 
@@ -16,7 +17,7 @@
 ProcessInfoNativeHandler::~ProcessInfoNativeHandler() = default;
 
 void ProcessInfoNativeHandler::AddRoutes() {
-  auto get_extension_id = [](const std::string& extension_id,
+  auto get_extension_id = [](const ExtensionId& extension_id,
                              const v8::FunctionCallbackInfo<v8::Value>& args) {
     args.GetReturnValue().Set(gin::StringToV8(args.GetIsolate(), extension_id));
   };
diff --git a/extensions/renderer/renderer_extension_registry.cc b/extensions/renderer/renderer_extension_registry.cc
index 841b8e3..82c96eba 100644
--- a/extensions/renderer/renderer_extension_registry.cc
+++ b/extensions/renderer/renderer_extension_registry.cc
@@ -8,6 +8,7 @@
 #include "base/lazy_instance.h"
 #include "base/unguessable_token.h"
 #include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/common/manifest_handlers/background_info.h"
 
 namespace extensions {
@@ -41,7 +42,7 @@
 }
 
 bool RendererExtensionRegistry::Contains(
-    const std::string& extension_id) const {
+    const ExtensionId& extension_id) const {
   base::AutoLock lock(lock_);
   return extensions_.Contains(extension_id);
 }
@@ -53,13 +54,13 @@
   return extensions_.Insert(extension);
 }
 
-bool RendererExtensionRegistry::Remove(const std::string& id) {
+bool RendererExtensionRegistry::Remove(const ExtensionId& id) {
   DCHECK(content::RenderThread::Get());
   base::AutoLock lock(lock_);
   return extensions_.Remove(id);
 }
 
-std::string RendererExtensionRegistry::GetExtensionOrAppIDByURL(
+ExtensionId RendererExtensionRegistry::GetExtensionOrAppIDByURL(
     const GURL& url) const {
   base::AutoLock lock(lock_);
   return extensions_.GetExtensionOrAppIDByURL(url);
@@ -79,7 +80,7 @@
 }
 
 const Extension* RendererExtensionRegistry::GetByID(
-    const std::string& id) const {
+    const ExtensionId& id) const {
   base::AutoLock lock(lock_);
   return extensions_.GetByID(id);
 }
diff --git a/extensions/renderer/renderer_extension_registry.h b/extensions/renderer/renderer_extension_registry.h
index f9a8898..4e82ed6c 100644
--- a/extensions/renderer/renderer_extension_registry.h
+++ b/extensions/renderer/renderer_extension_registry.h
@@ -8,7 +8,6 @@
 #include <stddef.h>
 
 #include <optional>
-#include <string>
 #include "base/synchronization/lock.h"
 #include "extensions/common/extension_id.h"
 #include "extensions/common/extension_set.h"
@@ -45,14 +44,14 @@
   const ExtensionSet* GetMainThreadExtensionSet() const;
 
   // Forwards to the ExtensionSet methods by the same name.
-  bool Contains(const std::string& id) const;
+  bool Contains(const ExtensionId& id) const;
   bool Insert(const scoped_refptr<const Extension>& extension);
-  bool Remove(const std::string& id);
-  std::string GetExtensionOrAppIDByURL(const GURL& url) const;
+  bool Remove(const ExtensionId& id);
+  ExtensionId GetExtensionOrAppIDByURL(const GURL& url) const;
   const Extension* GetExtensionOrAppByURL(const GURL& url,
                                           bool include_guid = false) const;
   const Extension* GetHostedAppByURL(const GURL& url) const;
-  const Extension* GetByID(const std::string& id) const;
+  const Extension* GetByID(const ExtensionId& id) const;
   ExtensionIdSet GetIDs() const;
   bool ExtensionBindingsAllowed(const GURL& url) const;
 
diff --git a/extensions/renderer/runtime_custom_bindings.cc b/extensions/renderer/runtime_custom_bindings.cc
index 499b10c..e60ce19d 100644
--- a/extensions/renderer/runtime_custom_bindings.cc
+++ b/extensions/renderer/runtime_custom_bindings.cc
@@ -10,6 +10,7 @@
 
 #include "base/functional/bind.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/common/mojom/view_type.mojom.h"
 #include "extensions/common/view_type_util.h"
 #include "extensions/renderer/extension_frame_helper.h"
@@ -53,7 +54,7 @@
   if (!parsed_view_type)
     CHECK_EQ("ALL", view_type_string);
 
-  const std::string& extension_id = context()->GetExtensionID();
+  const ExtensionId& extension_id = context()->GetExtensionID();
   if (extension_id.empty())
     return;
 
diff --git a/extensions/renderer/script_context_set.cc b/extensions/renderer/script_context_set.cc
index 045f6a5..f4e6245 100644
--- a/extensions/renderer/script_context_set.cc
+++ b/extensions/renderer/script_context_set.cc
@@ -13,6 +13,7 @@
 #include "content/public/renderer/worker_thread.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/common/mojom/context_type.mojom.h"
 #include "extensions/common/mojom/host_id.mojom.h"
 #include "extensions/common/utils/extension_utils.h"
@@ -203,7 +204,7 @@
   }
 }
 
-void ScriptContextSet::OnExtensionUnloaded(const std::string& extension_id) {
+void ScriptContextSet::OnExtensionUnloaded(const ExtensionId& extension_id) {
   ScriptContextSetIterable::ForEach(
       GenerateHostIdFromExtensionId(extension_id),
       base::BindRepeating(&ScriptContextSet::Remove, base::Unretained(this)));
@@ -217,7 +218,7 @@
     blink::WebLocalFrame* frame,
     int32_t world_id,
     bool use_effective_url) {
-  std::string extension_id;
+  ExtensionId extension_id;
   if (world_id != 0) {
     // Isolated worlds (content script).
     extension_id =
diff --git a/extensions/renderer/script_context_set.h b/extensions/renderer/script_context_set.h
index 82aeca6c..5f6bb91 100644
--- a/extensions/renderer/script_context_set.h
+++ b/extensions/renderer/script_context_set.h
@@ -9,7 +9,6 @@
 
 #include <memory>
 #include <set>
-#include <string>
 
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
@@ -109,7 +108,7 @@
       const base::RepeatingCallback<void(ScriptContext*)>& callback);
 
   // Cleans up contexts belonging to an unloaded extension.
-  void OnExtensionUnloaded(const std::string& extension_id);
+  void OnExtensionUnloaded(const ExtensionId& extension_id);
 
   void set_is_lock_screen_context(bool is_lock_screen_context) {
     is_lock_screen_context_ = is_lock_screen_context;
diff --git a/extensions/renderer/script_injection_manager.cc b/extensions/renderer/script_injection_manager.cc
index 78960a2..a9cc7392 100644
--- a/extensions/renderer/script_injection_manager.cc
+++ b/extensions/renderer/script_injection_manager.cc
@@ -20,6 +20,7 @@
 #include "content/public/renderer/render_thread.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_features.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/common/extension_set.h"
 #include "extensions/common/mojom/host_id.mojom.h"
 #include "extensions/renderer/extension_frame_helper.h"
@@ -263,7 +264,7 @@
 }
 
 void ScriptInjectionManager::OnExtensionUnloaded(
-    const std::string& extension_id) {
+    const ExtensionId& extension_id) {
   for (auto iter = pending_injections_.begin();
       iter != pending_injections_.end();) {
     if ((*iter)->host_id().id == extension_id) {
diff --git a/extensions/renderer/script_injection_manager.h b/extensions/renderer/script_injection_manager.h
index e0e9cd3..f9738b4d 100644
--- a/extensions/renderer/script_injection_manager.h
+++ b/extensions/renderer/script_injection_manager.h
@@ -15,6 +15,7 @@
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "base/scoped_observation.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/common/mojom/frame.mojom.h"
 #include "extensions/common/mojom/host_id.mojom-forward.h"
 #include "extensions/common/mojom/run_location.mojom-shared.h"
@@ -46,7 +47,7 @@
   void OnRenderFrameCreated(content::RenderFrame* render_frame);
 
   // Removes pending injections of the unloaded extension.
-  void OnExtensionUnloaded(const std::string& extension_id);
+  void OnExtensionUnloaded(const ExtensionId& extension_id);
 
   // Handle the ExecuteCode extension message.
   void HandleExecuteCode(mojom::ExecuteCodeParamsPtr params,
diff --git a/extensions/renderer/user_script_set_manager.cc b/extensions/renderer/user_script_set_manager.cc
index 6267e8d..d4c3efd 100644
--- a/extensions/renderer/user_script_set_manager.cc
+++ b/extensions/renderer/user_script_set_manager.cc
@@ -7,6 +7,7 @@
 #include "base/observer_list.h"
 #include "components/crx_file/id_util.h"
 #include "content/public/renderer/render_thread.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/common/mojom/host_id.mojom.h"
 #include "extensions/renderer/dispatcher.h"
 #include "extensions/renderer/script_injection.h"
@@ -35,7 +36,7 @@
     content::RenderFrame* render_frame,
     int tab_id,
     const GURL& url,
-    const std::string& extension_id) {
+    const ExtensionId& extension_id) {
   UserScriptSet* user_script_set = GetScriptsByHostID(
       mojom::HostID(mojom::HostID::HostType::kExtensions, extension_id));
   if (!user_script_set)
@@ -58,7 +59,7 @@
 }
 
 void UserScriptSetManager::GetAllActiveExtensionIds(
-    std::set<std::string>* ids) const {
+    std::set<ExtensionId>* ids) const {
   DCHECK(ids);
   for (auto it = scripts_.cbegin(); it != scripts_.cend(); ++it) {
     if (it->first.type == mojom::HostID::HostType::kExtensions &&
@@ -99,7 +100,7 @@
 }
 
 void UserScriptSetManager::OnExtensionUnloaded(
-    const std::string& extension_id) {
+    const ExtensionId& extension_id) {
   auto it = scripts_.find(
       mojom::HostID(mojom::HostID::HostType::kExtensions, extension_id));
   if (it != scripts_.end()) {
diff --git a/extensions/renderer/user_script_set_manager.h b/extensions/renderer/user_script_set_manager.h
index 825dc7c..8d3f4e9 100644
--- a/extensions/renderer/user_script_set_manager.h
+++ b/extensions/renderer/user_script_set_manager.h
@@ -14,6 +14,7 @@
 #include "base/observer_list.h"
 #include "content/public/renderer/render_thread_observer.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/extension_id.h"
 #include "extensions/common/mojom/host_id.mojom-forward.h"
 #include "extensions/common/mojom/run_location.mojom-shared.h"
 #include "extensions/common/user_script.h"
@@ -62,7 +63,7 @@
       content::RenderFrame* render_frame,
       int tab_id,
       const GURL& url,
-      const std::string& extension_id);
+      const ExtensionId& extension_id);
 
   // Append all injections from |static_scripts| and each of
   // |programmatic_scripts_| to |injections|.
@@ -73,7 +74,7 @@
       mojom::RunLocation run_location);
 
   // Get active extension IDs from `static_scripts_`.
-  void GetAllActiveExtensionIds(std::set<std::string>* ids) const;
+  void GetAllActiveExtensionIds(std::set<ExtensionId>* ids) const;
 
   // Handle the UpdateUserScripts extension message.
   void OnUpdateUserScripts(base::ReadOnlySharedMemoryRegion shared_memory,
@@ -81,7 +82,7 @@
 
   // Invalidates script injections for the UserScriptSet in `scripts_`
   // corresponding to `extension_id` and deletes the script set.
-  void OnExtensionUnloaded(const std::string& extension_id);
+  void OnExtensionUnloaded(const ExtensionId& extension_id);
 
   void set_activity_logging_enabled(bool enabled) {
     activity_logging_enabled_ = enabled;
diff --git a/extensions/test/extension_test_message_listener.cc b/extensions/test/extension_test_message_listener.cc
index 0c9b8d1..4b5e612 100644
--- a/extensions/test/extension_test_message_listener.cc
+++ b/extensions/test/extension_test_message_listener.cc
@@ -9,6 +9,7 @@
 #include "base/strings/string_util.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/api/test/test_api.h"
+#include "extensions/common/extension_id.h"
 
 ExtensionTestMessageListener::ExtensionTestMessageListener(
     const std::string& expected_message,
@@ -72,7 +73,7 @@
     const std::string& message) {
   // Return immediately if we're already satisfied or it's not the right
   // extension.
-  std::string sender_extension_id;
+  extensions::ExtensionId sender_extension_id;
   if (function->extension())
     sender_extension_id = function->extension_id();
 
diff --git a/extensions/test/extension_test_message_listener.h b/extensions/test/extension_test_message_listener.h
index e5b8fb6..c0ceaf2 100644
--- a/extensions/test/extension_test_message_listener.h
+++ b/extensions/test/extension_test_message_listener.h
@@ -164,7 +164,7 @@
     on_repeatedly_satisfied_ = on_repeatedly_satisfied;
   }
 
-  void set_extension_id(const std::string& extension_id) {
+  void set_extension_id(const extensions::ExtensionId& extension_id) {
     extension_id_ = extension_id;
   }
 
diff --git a/extensions/test/extension_test_notification_observer.cc b/extensions/test/extension_test_notification_observer.cc
index 126982e..73ed6eb 100644
--- a/extensions/test/extension_test_notification_observer.cc
+++ b/extensions/test/extension_test_notification_observer.cc
@@ -16,6 +16,7 @@
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/common/extension.h"
+#include "extensions/common/extension_id.h"
 
 namespace extensions {
 
@@ -63,7 +64,7 @@
     default;
 
 void ExtensionTestNotificationObserver::NotificationSet::
-    OnExtensionFrameUnregistered(const std::string& extension_id,
+    OnExtensionFrameUnregistered(const ExtensionId& extension_id,
                                  content::RenderFrameHost* render_frame_host) {
   closure_list_.Notify();
 }
diff --git a/extensions/test/extension_test_notification_observer.h b/extensions/test/extension_test_notification_observer.h
index 63f285b..91cfc548 100644
--- a/extensions/test/extension_test_notification_observer.h
+++ b/extensions/test/extension_test_notification_observer.h
@@ -16,6 +16,7 @@
 #include "content/public/test/browser_test_utils.h"
 #include "extensions/browser/process_manager.h"
 #include "extensions/browser/process_manager_observer.h"
+#include "extensions/common/extension_id.h"
 
 namespace content {
 class BrowserContext;
@@ -55,7 +56,7 @@
 
     // extensions::ProcessManagerObserver:
     void OnExtensionFrameUnregistered(
-        const std::string& extension_id,
+        const ExtensionId& extension_id,
         content::RenderFrameHost* render_frame_host) override;
 
     void OnWebContentsCreated(content::WebContents* web_contents);
diff --git a/gpu/command_buffer/service/shared_context_state.cc b/gpu/command_buffer/service/shared_context_state.cc
index 126f300..d8b78ad49 100644
--- a/gpu/command_buffer/service/shared_context_state.cc
+++ b/gpu/command_buffer/service/shared_context_state.cc
@@ -404,11 +404,6 @@
     CHECK_EQ(gr_context_type_, GrContextType::kVulkan);
 #if BUILDFLAG(ENABLE_VULKAN)
     if (vk_context_provider_) {
-      // TODO(vasilyt): Remove this if there is no problem with caching.
-      if (!base::FeatureList::IsEnabled(
-              features::kEnableGrShaderCacheForVulkan))
-        options.fPersistentCache = nullptr;
-
       if (!vk_context_provider_->InitializeGrContext(options)) {
         LOG(ERROR) << "Failed to initialize GrContext for Vulkan.";
         return false;
diff --git a/gpu/command_buffer/service/shared_image/shared_image_format_service_utils.cc b/gpu/command_buffer/service/shared_image/shared_image_format_service_utils.cc
index 1e968d3..36ab11f 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_format_service_utils.cc
+++ b/gpu/command_buffer/service/shared_image/shared_image_format_service_utils.cc
@@ -575,6 +575,7 @@
 skgpu::graphite::TextureInfo GraphiteBackendTextureInfo(
     GrContextType gr_context_type,
     viz::SharedImageFormat format,
+    bool readonly,
     int plane_index,
     bool is_yuv_plane,
     bool mipmapped,
@@ -589,9 +590,10 @@
   } else {
     CHECK_EQ(gr_context_type, GrContextType::kGraphiteDawn);
 #if BUILDFLAG(SKIA_USE_DAWN)
-    return DawnBackendTextureInfo(
-        format, is_yuv_plane, plane_index, mipmapped, scanout_dcomp_surface,
-        supports_multiplanar_rendering, supports_multiplanar_copy);
+    return DawnBackendTextureInfo(format, readonly, is_yuv_plane, plane_index,
+                                  mipmapped, scanout_dcomp_surface,
+                                  supports_multiplanar_rendering,
+                                  supports_multiplanar_copy);
 #endif
   }
   NOTREACHED_NORETURN();
@@ -601,8 +603,7 @@
     GrContextType gr_context_type,
     viz::SharedImageFormat format,
     int plane_index,
-    bool mipmapped,
-    bool scanout_dcomp_surface) {
+    bool mipmapped) {
   if (gr_context_type == GrContextType::kGraphiteMetal) {
 #if BUILDFLAG(SKIA_USE_METAL)
     return GraphiteMetalTextureInfo(format, plane_index,
@@ -625,16 +626,6 @@
     // For promise textures, just need TextureBinding usage for sampling
     // except for dcomp scanout which needs rendering and copy usages as well.
     dawn_texture_info.fUsage = wgpu::TextureUsage::TextureBinding;
-    if (scanout_dcomp_surface) {
-      // Textures from DComp surfaces cannot be used as TextureBinding, however
-      // DCompSurfaceImageBacking creates a textureable intermediate texture.
-      // TODO(crbug.com/1468844): Remove TextureBinding usage when the
-      // intermediate workaround is remove.
-      dawn_texture_info.fUsage = wgpu::TextureUsage::TextureBinding |
-                                 wgpu::TextureUsage::RenderAttachment |
-                                 wgpu::TextureUsage::CopySrc |
-                                 wgpu::TextureUsage::CopyDst;
-    }
     dawn_texture_info.fMipmapped =
         mipmapped ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo;
     return dawn_texture_info;
@@ -646,6 +637,7 @@
 #if BUILDFLAG(SKIA_USE_DAWN)
 skgpu::graphite::DawnTextureInfo DawnBackendTextureInfo(
     viz::SharedImageFormat format,
+    bool readonly,
     bool is_yuv_plane,
     int plane_index,
     bool mipmapped,
@@ -664,6 +656,11 @@
   dawn_texture_info.fUsage = SupportedDawnTextureUsage(
       is_yuv_plane, scanout_dcomp_surface, supports_multiplanar_rendering,
       supports_multiplanar_copy);
+  if (readonly) {
+    constexpr wgpu::TextureUsage kReadOnlyTextureUsage =
+        wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding;
+    dawn_texture_info.fUsage &= kReadOnlyTextureUsage;
+  }
   dawn_texture_info.fMipmapped =
       mipmapped ? skgpu::Mipmapped::kYes : skgpu::Mipmapped::kNo;
   return dawn_texture_info;
diff --git a/gpu/command_buffer/service/shared_image/shared_image_format_service_utils.h b/gpu/command_buffer/service/shared_image/shared_image_format_service_utils.h
index 03e86bc..8cc1a790 100644
--- a/gpu/command_buffer/service/shared_image/shared_image_format_service_utils.h
+++ b/gpu/command_buffer/service/shared_image/shared_image_format_service_utils.h
@@ -176,29 +176,30 @@
 GPU_GLES2_EXPORT skgpu::graphite::TextureInfo GraphiteBackendTextureInfo(
     GrContextType gr_context_type,
     viz::SharedImageFormat format,
-    int plane_index = 0,
-    bool is_yuv_plane = false,
-    bool mipmapped = false,
-    bool scanout_dcomp_surface = false,
-    bool supports_multiplanar_rendering = false,
-    bool supports_multiplanar_copy = false);
+    bool readonly,
+    int plane_index,
+    bool is_yuv_plane,
+    bool mipmapped,
+    bool scanout_dcomp_surface,
+    bool supports_multiplanar_rendering,
+    bool supports_multiplanar_copy);
 
 GPU_GLES2_EXPORT skgpu::graphite::TextureInfo GraphitePromiseTextureInfo(
     GrContextType gr_context_type,
     viz::SharedImageFormat format,
     int plane_index = 0,
-    bool mipmapped = false,
-    bool scanout_dcomp_surface = false);
+    bool mipmapped = false);
 
 #if BUILDFLAG(SKIA_USE_DAWN)
 GPU_GLES2_EXPORT skgpu::graphite::DawnTextureInfo DawnBackendTextureInfo(
     viz::SharedImageFormat format,
-    bool is_yuv_plane = false,
-    int plane_index = 0,
-    bool mipmapped = false,
-    bool scanout_dcomp_surface = false,
-    bool supports_multiplanar_rendering = false,
-    bool support_multiplanar_copy = false);
+    bool readonly,
+    bool is_yuv_plane,
+    int plane_index,
+    bool mipmapped,
+    bool scanout_dcomp_surface,
+    bool supports_multiplanar_rendering,
+    bool support_multiplanar_copy);
 #endif
 
 #if BUILDFLAG(SKIA_USE_METAL)
diff --git a/gpu/command_buffer/service/shared_image/skia_graphite_dawn_image_representation.cc b/gpu/command_buffer/service/shared_image/skia_graphite_dawn_image_representation.cc
index 2dc5483..d2701c2 100644
--- a/gpu/command_buffer/service/shared_image/skia_graphite_dawn_image_representation.cc
+++ b/gpu/command_buffer/service/shared_image/skia_graphite_dawn_image_representation.cc
@@ -20,6 +20,7 @@
 namespace gpu {
 
 namespace {
+
 bool SupportsMultiplanarRendering(SharedContextState* context_state) {
   auto* dawn_context_provider = context_state->dawn_context_provider();
   if (!dawn_context_provider) {
@@ -96,7 +97,8 @@
 
 std::vector<skgpu::graphite::BackendTexture>
 SkiaGraphiteDawnImageRepresentation::CreateBackendTextures(
-    wgpu::Texture texture) {
+    wgpu::Texture texture,
+    bool readonly) {
   std::vector<skgpu::graphite::BackendTexture> backend_textures;
   const bool supports_multiplanar_rendering =
       SupportsMultiplanarRendering(context_state_.get());
@@ -112,7 +114,8 @@
       SkISize plane_size =
           gfx::SizeToSkISize(format().GetPlaneSize(plane_index, size()));
       skgpu::graphite::DawnTextureInfo plane_info = DawnBackendTextureInfo(
-          format(), /*is_yuv_plane=*/true, plane_index, /*mipmapped=*/false,
+          format(), readonly, /*is_yuv_plane=*/true, plane_index,
+          /*mipmapped=*/false,
           /*scanout_dcomp_surface=*/false, supports_multiplanar_rendering,
           supports_multiplanar_copy);
       backend_textures.emplace_back(plane_size, plane_info, texture.Get());
@@ -121,7 +124,7 @@
     // Legacy multi-planar NV12 - format() is either R8 or RG8.
     SkISize plane_size = gfx::SizeToSkISize(size());
     skgpu::graphite::DawnTextureInfo plane_info = DawnBackendTextureInfo(
-        format(), /*is_yuv_plane=*/true, legacy_plane_index_,
+        format(), readonly, /*is_yuv_plane=*/true, legacy_plane_index_,
         /*mipmapped=*/false, /*scanout_dcomp_surface=*/false,
         supports_multiplanar_rendering, supports_multiplanar_copy);
     backend_textures = {
@@ -148,7 +151,7 @@
   }
 
   std::vector<skgpu::graphite::BackendTexture> backend_textures =
-      CreateBackendTextures(dawn_scoped_access_->texture());
+      CreateBackendTextures(dawn_scoped_access_->texture(), /*readonly=*/false);
 
   std::vector<sk_sp<SkSurface>> surfaces;
   surfaces.reserve(format().NumberOfPlanes());
@@ -189,7 +192,8 @@
   }
 
   mode_ = RepresentationAccessMode::kWrite;
-  return CreateBackendTextures(dawn_scoped_access_->texture());
+  return CreateBackendTextures(dawn_scoped_access_->texture(),
+                               /*readonly=*/false);
 }
 
 void SkiaGraphiteDawnImageRepresentation::EndWriteAccess() {
@@ -202,16 +206,18 @@
 SkiaGraphiteDawnImageRepresentation::BeginReadAccess() {
   CHECK_EQ(mode_, RepresentationAccessMode::kNone);
   CHECK(!dawn_scoped_access_);
-
+  constexpr wgpu::TextureUsage kReadOnlyTextureUsage =
+      wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::TextureBinding;
   dawn_scoped_access_ = dawn_representation_->BeginScopedAccess(
-      supported_tex_usages_, AllowUnclearedAccess::kNo);
+      supported_tex_usages_ & kReadOnlyTextureUsage, AllowUnclearedAccess::kNo);
   if (!dawn_scoped_access_) {
     DLOG(ERROR) << "Could not create DawnImageRepresentation::ScopedAccess";
     return {};
   }
 
   mode_ = RepresentationAccessMode::kRead;
-  return CreateBackendTextures(dawn_scoped_access_->texture());
+  return CreateBackendTextures(dawn_scoped_access_->texture(),
+                               /*readonly=*/true);
 }
 
 void SkiaGraphiteDawnImageRepresentation::EndReadAccess() {
diff --git a/gpu/command_buffer/service/shared_image/skia_graphite_dawn_image_representation.h b/gpu/command_buffer/service/shared_image/skia_graphite_dawn_image_representation.h
index 6fb2b374..19eda403 100644
--- a/gpu/command_buffer/service/shared_image/skia_graphite_dawn_image_representation.h
+++ b/gpu/command_buffer/service/shared_image/skia_graphite_dawn_image_representation.h
@@ -50,7 +50,8 @@
       wgpu::TextureUsage supported_tex_usages);
 
   std::vector<skgpu::graphite::BackendTexture> CreateBackendTextures(
-      wgpu::Texture texture);
+      wgpu::Texture texture,
+      bool readonly);
 
   std::unique_ptr<DawnImageRepresentation> dawn_representation_;
   std::unique_ptr<DawnImageRepresentation::ScopedAccess> dawn_scoped_access_;
diff --git a/gpu/command_buffer/service/shared_image/wrapped_graphite_texture_backing.cc b/gpu/command_buffer/service/shared_image/wrapped_graphite_texture_backing.cc
index 2d6e7b61..71d4bd8f 100644
--- a/gpu/command_buffer/service/shared_image/wrapped_graphite_texture_backing.cc
+++ b/gpu/command_buffer/service/shared_image/wrapped_graphite_texture_backing.cc
@@ -157,8 +157,10 @@
     // textures, not planes of a multi-planar YUV texture.
     constexpr bool is_yuv_plane = false;
     skgpu::graphite::TextureInfo texture_info = gpu::GraphiteBackendTextureInfo(
-        context_state_->gr_context_type(), format(), plane, is_yuv_plane,
-        mipmapped);
+        context_state_->gr_context_type(), format(), /*readonly=*/false, plane,
+        is_yuv_plane, mipmapped, /*scanout_dcomp_surface=*/false,
+        /*supports_multiplanar_rendering=*/false,
+        /*supports_multiplanar_copy=*/false);
     auto sk_size = gfx::SizeToSkISize(format().GetPlaneSize(plane, size()));
     auto texture = recorder()->createBackendTexture(sk_size, texture_info);
     if (!texture.isValid()) {
@@ -193,7 +195,11 @@
 
   auto& texture = graphite_textures_[0];
   skgpu::graphite::TextureInfo texture_info = gpu::GraphiteBackendTextureInfo(
-      context_state_->gr_context_type(), format());
+      context_state_->gr_context_type(), format(), /*readonly=*/false,
+      /*plane_index=*/0, /*is_yuv_plane=*/false,
+      /*mipmapped=*/false, /*scanout_dcomp_surface=*/false,
+      /*supports_multiplanar_rendering=*/false,
+      /*supports_multiplanar_copy=*/false);
   texture = recorder()->createBackendTexture(gfx::SizeToSkISize(size()),
                                              texture_info);
   if (!texture.isValid()) {
diff --git a/gpu/config/gpu_finch_features.cc b/gpu/config/gpu_finch_features.cc
index 7689a05..dbae62ad 100644
--- a/gpu/config/gpu_finch_features.cc
+++ b/gpu/config/gpu_finch_features.cc
@@ -219,13 +219,6 @@
              "GenGpuDiskCacheKeyPrefixInGpuService",
              base::FEATURE_DISABLED_BY_DEFAULT);
 
-// Causes us to use the SharedImageManager, removing support for the old
-// mailbox system. Any consumers of the GPU process using the old mailbox
-// system will experience undefined results.
-BASE_FEATURE(kSharedImageManager,
-             "SharedImageManager",
-             base::FEATURE_DISABLED_BY_DEFAULT);
-
 // Controls the decode acceleration of JPEG images (as opposed to camera
 // captures) in Chrome OS using the VA-API.
 // TODO(andrescj): remove or enable by default in Chrome OS once
@@ -384,11 +377,6 @@
              base::FEATURE_DISABLED_BY_DEFAULT);
 #endif
 
-// Enable GrShaderCache to use with Vulkan backend.
-BASE_FEATURE(kEnableGrShaderCacheForVulkan,
-             "EnableGrShaderCacheForVulkan",
-             base::FEATURE_ENABLED_BY_DEFAULT);
-
 // Enable report only mode on the GPU watchdog instead of pausing the watchdog
 // thread during GPU startup.
 BASE_FEATURE(kEnableWatchdogReportOnlyModeOnGpuInit,
diff --git a/gpu/config/gpu_finch_features.h b/gpu/config/gpu_finch_features.h
index d50ad3197..fad78b9 100644
--- a/gpu/config/gpu_finch_features.h
+++ b/gpu/config/gpu_finch_features.h
@@ -66,8 +66,6 @@
 
 GPU_EXPORT BASE_DECLARE_FEATURE(kGenGpuDiskCacheKeyPrefixInGpuService);
 
-GPU_EXPORT BASE_DECLARE_FEATURE(kSharedImageManager);
-
 GPU_EXPORT BASE_DECLARE_FEATURE(kVaapiJpegImageDecodeAcceleration);
 
 GPU_EXPORT BASE_DECLARE_FEATURE(kVaapiWebPImageDecodeAcceleration);
@@ -85,8 +83,6 @@
 GPU_EXPORT BASE_DECLARE_FEATURE(kSkiaGraphiteDawnUseD3D12);
 #endif
 
-GPU_EXPORT BASE_DECLARE_FEATURE(kEnableGrShaderCacheForVulkan);
-
 GPU_EXPORT BASE_DECLARE_FEATURE(kEnableWatchdogReportOnlyModeOnGpuInit);
 
 GPU_EXPORT BASE_DECLARE_FEATURE(kEnableVkPipelineCache);
diff --git a/infra/config/generated/testing/gn_isolate_map.pyl b/infra/config/generated/testing/gn_isolate_map.pyl
index ec18e441..1bdbc04f 100644
--- a/infra/config/generated/testing/gn_isolate_map.pyl
+++ b/infra/config/generated/testing/gn_isolate_map.pyl
@@ -1249,6 +1249,10 @@
     "label": "//components/optimization_guide/internal:ondevice_model_example",
     "type": "additional_compile_target",
   },
+  "ondevice_stability_tests": {
+    "label": "//components/optimization_guide/internal/testing:ondevice_stability_tests",
+    "type": "generated_script",
+  },
   "openscreen_unittests": {
     "label": "//chrome/browser/media/router:openscreen_unittests",
     "type": "console_test_launcher",
diff --git a/infra/config/generated/testing/test_suites.pyl b/infra/config/generated/testing/test_suites.pyl
index 9f9d564..4b4d34e 100644
--- a/infra/config/generated/testing/test_suites.pyl
+++ b/infra/config/generated/testing/test_suites.pyl
@@ -108,7 +108,11 @@
 
     'android_pie_rel_reduced_capacity_gtests': {
       'android_browsertests': {},
-      'blink_platform_unittests': {},
+      'blink_platform_unittests': {
+        'mixins': [
+          'skia_gold_test',
+        ],
+      },
       'cc_unittests': {},
       'content_browsertests': {
         'swarming': {
@@ -835,7 +839,11 @@
       'base_unittests': {},
       'blink_common_unittests': {},
       'blink_heap_unittests': {},
-      'blink_platform_unittests': {},
+      'blink_platform_unittests': {
+        'mixins': [
+          'skia_gold_test',
+        ],
+      },
       'boringssl_crypto_tests': {},
       'boringssl_ssl_tests': {},
       'capture_unittests': {
@@ -2185,7 +2193,11 @@
       'blink_common_unittests': {},
       'blink_fuzzer_unittests': {},
       'blink_heap_unittests': {},
-      'blink_platform_unittests': {},
+      'blink_platform_unittests': {
+        'mixins': [
+          'skia_gold_test',
+        ],
+      },
       'blink_unittests': {},
       'blink_unittests_v2': {},
       'boringssl_crypto_tests': {},
@@ -4029,6 +4041,9 @@
       'blink_fuzzer_unittests': {},
       'blink_heap_unittests': {},
       'blink_platform_unittests': {
+        'mixins': [
+          'skia_gold_test',
+        ],
         'args': [
           '--test-launcher-bot-mode',
           '--test-launcher-filter-file=testing/buildbot/filters/ios.blink_platform_unittests.filter',
@@ -4537,7 +4552,11 @@
       'app_shell_unittests': {},
       'base_unittests': {},
       'blink_heap_unittests': {},
-      'blink_platform_unittests': {},
+      'blink_platform_unittests': {
+        'mixins': [
+          'skia_gold_test',
+        ],
+      },
       'blink_unittests': {},
       'blink_unittests_v2': {},
       'cc_unittests': {},
@@ -4756,6 +4775,32 @@
       },
     },
 
+    'ondevice_stability_tests': {
+      'ondevice_stability_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',
+        ],
+      },
+    },
+
     'optimization_guide_android_gtests': {
       'optimization_guide_components_unittests': {
         'test': 'components_unittests',
@@ -7882,15 +7927,6 @@
       },
     },
 
-    'model_validation_tests_full': {
-      'model_validation_tests': {
-        'variants': [
-          'MODEL_VALIDATION_BASE',
-          'MODEL_VALIDATION_TRUNK',
-        ],
-      },
-    },
-
     'optimization_guide_linux_gtests': {
       'optimization_guide_gpu_gtests': {
         'variants': [
@@ -7900,11 +7936,35 @@
       'optimization_guide_nogpu_gtests': {},
     },
 
+    'optimization_guide_linux_script_tests': {
+      'model_validation_tests': {
+        'variants': [
+          'MODEL_VALIDATION_BASE',
+          'MODEL_VALIDATION_TRUNK',
+        ],
+      },
+      'ondevice_stability_tests': {
+        'variants': [
+          'INTEL_UHD_630',
+        ],
+      },
+    },
+
     'optimization_guide_mac_gtests': {
       'optimization_guide_gpu_gtests': {},
       'optimization_guide_nogpu_gtests': {},
     },
 
+    'optimization_guide_mac_script_tests': {
+      'model_validation_tests': {
+        'variants': [
+          'MODEL_VALIDATION_BASE',
+          'MODEL_VALIDATION_TRUNK',
+        ],
+      },
+      'ondevice_stability_tests': {},
+    },
+
     'optimization_guide_win_gtests': {
       'optimization_guide_gpu_gtests': {
         'variants': [
@@ -7915,6 +7975,15 @@
       'optimization_guide_nogpu_gtests': {},
     },
 
+    'optimization_guide_win_script_tests': {
+      'model_validation_tests': {
+        'variants': [
+          'MODEL_VALIDATION_BASE',
+          'MODEL_VALIDATION_TRUNK',
+        ],
+      },
+    },
+
     'webview_trichrome_10_cts_tests_gtest': {
       'webview_trichrome_cts_tests': {
         'variants': [
diff --git a/infra/config/generated/testing/variants.pyl b/infra/config/generated/testing/variants.pyl
index 4119c17e..8aa7644 100644
--- a/infra/config/generated/testing/variants.pyl
+++ b/infra/config/generated/testing/variants.pyl
@@ -307,16 +307,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 123.0.6282.0',
+    'description': 'Run with ash-chrome version 123.0.6283.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6282.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6283.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v123.0.6282.0',
-          'revision': 'version:123.0.6282.0',
+          'location': 'lacros_version_skew_tests_v123.0.6283.0',
+          'revision': 'version:123.0.6283.0',
         },
       ],
     },
diff --git a/infra/config/targets/basic_suites.star b/infra/config/targets/basic_suites.star
index 9587e6bc..ed636756 100644
--- a/infra/config/targets/basic_suites.star
+++ b/infra/config/targets/basic_suites.star
@@ -4407,6 +4407,35 @@
 )
 
 targets.legacy_basic_suite(
+    name = "ondevice_stability_tests",
+    tests = {
+        "ondevice_stability_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",
+            ],
+        ),
+    },
+)
+
+targets.legacy_basic_suite(
     name = "optimization_guide_android_gtests",
     tests = {
         "optimization_guide_components_unittests": targets.legacy_test_config(),
diff --git a/infra/config/targets/binaries.star b/infra/config/targets/binaries.star
index 26c1225..8a25109 100644
--- a/infra/config/targets/binaries.star
+++ b/infra/config/targets/binaries.star
@@ -1330,6 +1330,11 @@
     label = "//chrome/notification_helper:notification_helper_unittests",
 )
 
+targets.binaries.generated_script(
+    name = "ondevice_stability_tests",
+    label = "//components/optimization_guide/internal/testing:ondevice_stability_tests",
+)
+
 targets.binaries.console_test_launcher(
     name = "openscreen_unittests",
     label = "//chrome/browser/media/router:openscreen_unittests",
diff --git a/infra/config/targets/lacros-version-skew-variants.json b/infra/config/targets/lacros-version-skew-variants.json
index c1f3a22..ad2be9e 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_v123.0.6282.0/test_ash_chrome"
+      "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6283.0/test_ash_chrome"
     ],
-    "description": "Run with ash-chrome version 123.0.6282.0",
+    "description": "Run with ash-chrome version 123.0.6283.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_v123.0.6282.0",
-          "revision": "version:123.0.6282.0"
+          "location": "lacros_version_skew_tests_v123.0.6283.0",
+          "revision": "version:123.0.6283.0"
         }
       ]
     }
diff --git a/infra/config/targets/matrix_compound_suites.star b/infra/config/targets/matrix_compound_suites.star
index f7c9a9a7..798a1ef 100644
--- a/infra/config/targets/matrix_compound_suites.star
+++ b/infra/config/targets/matrix_compound_suites.star
@@ -1335,7 +1335,21 @@
 )
 
 targets.legacy_matrix_compound_suite(
-    name = "model_validation_tests_full",
+    name = "optimization_guide_linux_gtests",
+    basic_suites = {
+        "optimization_guide_nogpu_gtests": None,
+        "optimization_guide_gpu_gtests": targets.legacy_matrix_config(
+            # TODO(b:322815244): Add AMD and NVIDIA variants once driver issues
+            # are resolved.
+            variants = [
+                "INTEL_UHD_630",
+            ],
+        ),
+    },
+)
+
+targets.legacy_matrix_compound_suite(
+    name = "optimization_guide_linux_script_tests",
     basic_suites = {
         "model_validation_tests": targets.legacy_matrix_config(
             variants = [
@@ -1343,16 +1357,7 @@
                 "MODEL_VALIDATION_TRUNK",
             ],
         ),
-    },
-)
-
-targets.legacy_matrix_compound_suite(
-    name = "optimization_guide_linux_gtests",
-    basic_suites = {
-        "optimization_guide_nogpu_gtests": None,
-        "optimization_guide_gpu_gtests": targets.legacy_matrix_config(
-            # TODO(b:322815244): Add AMD and NVIDIA variants once driver issues
-            # are resolved.
+        "ondevice_stability_tests": targets.legacy_matrix_config(
             variants = [
                 "INTEL_UHD_630",
             ],
@@ -1369,6 +1374,19 @@
 )
 
 targets.legacy_matrix_compound_suite(
+    name = "optimization_guide_mac_script_tests",
+    basic_suites = {
+        "model_validation_tests": targets.legacy_matrix_config(
+            variants = [
+                "MODEL_VALIDATION_BASE",
+                "MODEL_VALIDATION_TRUNK",
+            ],
+        ),
+        "ondevice_stability_tests": None,
+    },
+)
+
+targets.legacy_matrix_compound_suite(
     name = "optimization_guide_win_gtests",
     basic_suites = {
         "optimization_guide_nogpu_gtests": None,
@@ -1383,6 +1401,18 @@
 )
 
 targets.legacy_matrix_compound_suite(
+    name = "optimization_guide_win_script_tests",
+    basic_suites = {
+        "model_validation_tests": targets.legacy_matrix_config(
+            variants = [
+                "MODEL_VALIDATION_BASE",
+                "MODEL_VALIDATION_TRUNK",
+            ],
+        ),
+    },
+)
+
+targets.legacy_matrix_compound_suite(
     name = "webview_trichrome_10_cts_tests_gtest",
     basic_suites = {
         "webview_trichrome_cts_tests": targets.legacy_matrix_config(
diff --git a/infra/config/targets/tests.star b/infra/config/targets/tests.star
index 9c25049..ba25ea6 100644
--- a/infra/config/targets/tests.star
+++ b/infra/config/targets/tests.star
@@ -197,6 +197,9 @@
 
 targets.tests.gtest_test(
     name = "blink_platform_unittests",
+    mixins = [
+        "skia_gold_test",
+    ],
 )
 
 targets.tests.isolated_script_test(
@@ -1611,6 +1614,10 @@
     name = "notification_helper_unittests",
 )
 
+targets.tests.isolated_script_test(
+    name = "ondevice_stability_tests",
+)
+
 targets.tests.gtest_test(
     name = "oobe_only_browser_tests",
     args = [
diff --git a/ios/chrome/browser/contextual_panel/model/BUILD.gn b/ios/chrome/browser/contextual_panel/model/BUILD.gn
new file mode 100644
index 0000000..c9c0c66
--- /dev/null
+++ b/ios/chrome/browser/contextual_panel/model/BUILD.gn
@@ -0,0 +1,14 @@
+# Copyright 2024 The Chromium Authors
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("model") {
+  sources = [
+    "contextual_panel_tab_helper.h",
+    "contextual_panel_tab_helper.mm",
+  ]
+  deps = [
+    "//base",
+    "//ios/web/public",
+  ]
+}
diff --git a/ios/chrome/browser/contextual_panel/model/contextual_panel_tab_helper.h b/ios/chrome/browser/contextual_panel/model/contextual_panel_tab_helper.h
new file mode 100644
index 0000000..bc0c42cf
--- /dev/null
+++ b/ios/chrome/browser/contextual_panel/model/contextual_panel_tab_helper.h
@@ -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.
+
+#ifndef IOS_CHROME_BROWSER_CONTEXTUAL_PANEL_MODEL_CONTEXTUAL_PANEL_TAB_HELPER_H_
+#define IOS_CHROME_BROWSER_CONTEXTUAL_PANEL_MODEL_CONTEXTUAL_PANEL_TAB_HELPER_H_
+
+#include "base/scoped_observation.h"
+#import "ios/web/public/web_state_observer.h"
+#import "ios/web/public/web_state_user_data.h"
+
+// Tab helper controlling the Contextual Panel feature for a given tab.
+class ContextualPanelTabHelper
+    : public web::WebStateObserver,
+      public web::WebStateUserData<ContextualPanelTabHelper> {
+ public:
+  ContextualPanelTabHelper(const ContextualPanelTabHelper&) = delete;
+  ContextualPanelTabHelper& operator=(const ContextualPanelTabHelper&) = delete;
+
+  ~ContextualPanelTabHelper() override;
+
+  // WebStateObserver:
+  void DidFinishNavigation(web::WebState* web_state,
+                           web::NavigationContext* navigation_context) override;
+  void WebStateDestroyed(web::WebState* web_state) override;
+
+ private:
+  friend class web::WebStateUserData<ContextualPanelTabHelper>;
+
+  ContextualPanelTabHelper(web::WebState* web_state);
+
+  WEB_STATE_USER_DATA_KEY_DECL();
+
+  // Scoped observation for WebState.
+  base::ScopedObservation<web::WebState, web::WebStateObserver>
+      web_state_observation_{this};
+
+  // The WebState this instance is observing. Will be null after
+  // WebStateDestroyed has been called.
+  raw_ptr<web::WebState> web_state_ = nullptr;
+};
+
+#endif  // IOS_CHROME_BROWSER_CONTEXTUAL_PANEL_MODEL_CONTEXTUAL_PANEL_TAB_HELPER_H_
diff --git a/ios/chrome/browser/contextual_panel/model/contextual_panel_tab_helper.mm b/ios/chrome/browser/contextual_panel/model/contextual_panel_tab_helper.mm
new file mode 100644
index 0000000..152eaa9
--- /dev/null
+++ b/ios/chrome/browser/contextual_panel/model/contextual_panel_tab_helper.mm
@@ -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.
+
+#import "ios/chrome/browser/contextual_panel/model/contextual_panel_tab_helper.h"
+
+#import "ios/web/public/web_state.h"
+
+ContextualPanelTabHelper::ContextualPanelTabHelper(web::WebState* web_state)
+    : web_state_(web_state) {
+  web_state_observation_.Observe(web_state_);
+}
+
+ContextualPanelTabHelper::~ContextualPanelTabHelper() = default;
+
+WEB_STATE_USER_DATA_KEY_IMPL(ContextualPanelTabHelper)
+
+#pragma mark - WebStateObserver
+
+void ContextualPanelTabHelper::DidFinishNavigation(
+    web::WebState* web_state,
+    web::NavigationContext* navigation_context) {
+  // Ask individual models for their data.
+}
+
+void ContextualPanelTabHelper::WebStateDestroyed(web::WebState* web_state) {
+  DCHECK_EQ(web_state_, web_state);
+  web_state_observation_.Reset();
+  web_state_ = nullptr;
+}
diff --git a/ios/chrome/browser/passwords/model/ios_chrome_password_manager_client.h b/ios/chrome/browser/passwords/model/ios_chrome_password_manager_client.h
index 1c0d1e3..08fa69ae 100644
--- a/ios/chrome/browser/passwords/model/ios_chrome_password_manager_client.h
+++ b/ios/chrome/browser/passwords/model/ios_chrome_password_manager_client.h
@@ -70,7 +70,6 @@
   ~IOSChromePasswordManagerClient() override;
 
   // password_manager::PasswordManagerClient implementation.
-  password_manager::SyncState GetPasswordSyncState() const override;
   bool PromptUserToSaveOrUpdatePassword(
       std::unique_ptr<password_manager::PasswordFormManagerForUI> form_to_save,
       bool update_password) override;
diff --git a/ios/chrome/browser/passwords/model/ios_chrome_password_manager_client.mm b/ios/chrome/browser/passwords/model/ios_chrome_password_manager_client.mm
index e0ad148b..303b74e 100644
--- a/ios/chrome/browser/passwords/model/ios_chrome_password_manager_client.mm
+++ b/ios/chrome/browser/passwords/model/ios_chrome_password_manager_client.mm
@@ -53,20 +53,8 @@
 using password_manager::PasswordManagerMetricsRecorder;
 using password_manager::PasswordStore;
 using password_manager::PasswordStoreInterface;
-using password_manager::SyncState;
 using password_manager::metrics_util::PasswordType;
 
-namespace {
-
-// Used for callbacks that expect a const pointer, since base::Callback isn't
-// smart enough to allow binding the SyncServiceFactory method directly.
-const syncer::SyncService* GetConstSyncServicePtr(
-    ChromeBrowserState* browser_state) {
-  return SyncServiceFactory::GetForBrowserStateIfExists(browser_state);
-}
-
-}  // namespace
-
 IOSChromePasswordManagerClient::IOSChromePasswordManagerClient(
     id<IOSChromePasswordManagerClientBridge> bridge)
     : bridge_(bridge),
@@ -74,9 +62,7 @@
           GetPrefs(),
           GetLocalStatePrefs(),
           SyncServiceFactory::GetForBrowserStateIfExists(bridge_.browserState)),
-      credentials_filter_(
-          this,
-          base::BindRepeating(&GetConstSyncServicePtr, bridge_.browserState)),
+      credentials_filter_(this),
       helper_(this) {
   saving_passwords_enabled_.Init(
       password_manager::prefs::kCredentialsEnableService, GetPrefs());
@@ -88,12 +74,6 @@
 
 IOSChromePasswordManagerClient::~IOSChromePasswordManagerClient() = default;
 
-SyncState IOSChromePasswordManagerClient::GetPasswordSyncState() const {
-  syncer::SyncService* sync_service =
-      SyncServiceFactory::GetForBrowserState(bridge_.browserState);
-  return password_manager::sync_util::GetPasswordSyncState(sync_service);
-}
-
 bool IOSChromePasswordManagerClient::PromptUserToChooseCredentials(
     std::vector<std::unique_ptr<password_manager::PasswordForm>> local_forms,
     const url::Origin& origin,
@@ -182,7 +162,7 @@
 
 const syncer::SyncService* IOSChromePasswordManagerClient::GetSyncService()
     const {
-  return GetConstSyncServicePtr(bridge_.browserState);
+  return SyncServiceFactory::GetForBrowserStateIfExists(bridge_.browserState);
 }
 
 PasswordStoreInterface*
diff --git a/ios/chrome/browser/passwords/model/password_manager_util_ios.cc b/ios/chrome/browser/passwords/model/password_manager_util_ios.cc
index cac35ff..c5c0e8d4 100644
--- a/ios/chrome/browser/passwords/model/password_manager_util_ios.cc
+++ b/ios/chrome/browser/passwords/model/password_manager_util_ios.cc
@@ -14,12 +14,13 @@
 bool IsSavingPasswordsToAccountWithNormalEncryption(
     const syncer::SyncService* sync_service) {
   switch (password_manager::sync_util::GetPasswordSyncState(sync_service)) {
-    case password_manager::SyncState::kSyncingNormalEncryption:
-    case password_manager::SyncState::kAccountPasswordsActiveNormalEncryption:
+    case password_manager::sync_util::SyncState::kSyncingNormalEncryption:
+    case password_manager::sync_util::SyncState::
+        kAccountPasswordsActiveNormalEncryption:
       return true;
-    case password_manager::SyncState::kNotSyncing:
-    case password_manager::SyncState::kSyncingWithCustomPassphrase:
-    case password_manager::SyncState::
+    case password_manager::sync_util::SyncState::kNotActive:
+    case password_manager::sync_util::SyncState::kSyncingWithCustomPassphrase:
+    case password_manager::sync_util::SyncState::
         kAccountPasswordsActiveWithCustomPassphrase:
       return false;
   }
diff --git a/ios/chrome/browser/ui/qr_scanner/BUILD.gn b/ios/chrome/browser/qr_scanner/ui_bundled/BUILD.gn
similarity index 97%
rename from ios/chrome/browser/ui/qr_scanner/BUILD.gn
rename to ios/chrome/browser/qr_scanner/ui_bundled/BUILD.gn
index a3c39a7..fa3cdac 100644
--- a/ios/chrome/browser/ui/qr_scanner/BUILD.gn
+++ b/ios/chrome/browser/qr_scanner/ui_bundled/BUILD.gn
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-source_set("qr_scanner") {
+source_set("ui") {
   sources = [
     "qr_scanner_camera_controller.h",
     "qr_scanner_camera_controller.mm",
@@ -37,7 +37,7 @@
     "qr_scanner_legacy_coordinator.mm",
   ]
   deps = [
-    ":qr_scanner",
+    ":ui",
     "//base",
     "//ios/chrome/browser/shared/coordinator/chrome_coordinator",
     "//ios/chrome/browser/shared/coordinator/scene:scene_state_header",
@@ -77,7 +77,7 @@
     "qr_scanner_app_interface.mm",
   ]
   deps = [
-    ":qr_scanner",
+    ":ui",
     "//base",
     "//components/search_engines",
     "//components/version_info",
diff --git a/ios/chrome/browser/ui/qr_scanner/DEPS b/ios/chrome/browser/qr_scanner/ui_bundled/DEPS
similarity index 73%
rename from ios/chrome/browser/ui/qr_scanner/DEPS
rename to ios/chrome/browser/qr_scanner/ui_bundled/DEPS
index 659a98e..bc5bbb2 100644
--- a/ios/chrome/browser/ui/qr_scanner/DEPS
+++ b/ios/chrome/browser/qr_scanner/ui_bundled/DEPS
@@ -1,6 +1,8 @@
 include_rules = [
   "+ios/chrome/browser/ui/scanner",
   "+ios/chrome/browser/ui/location_bar",
+  "+ios/chrome/browser/url_loading/model",
+  "+ios/chrome/browser/search_engines/model/template_url_service_factory.h",
 ]
 
 specific_include_rules = {
diff --git a/ios/chrome/browser/ui/qr_scanner/OWNERS b/ios/chrome/browser/qr_scanner/ui_bundled/OWNERS
similarity index 100%
rename from ios/chrome/browser/ui/qr_scanner/OWNERS
rename to ios/chrome/browser/qr_scanner/ui_bundled/OWNERS
diff --git a/ios/chrome/browser/ui/qr_scanner/README.md b/ios/chrome/browser/qr_scanner/ui_bundled/README.md
similarity index 100%
rename from ios/chrome/browser/ui/qr_scanner/README.md
rename to ios/chrome/browser/qr_scanner/ui_bundled/README.md
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_app_interface.h b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_app_interface.h
similarity index 94%
rename from ios/chrome/browser/ui/qr_scanner/qr_scanner_app_interface.h
rename to ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_app_interface.h
index 5e35b17..4403c4f8 100644
--- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_app_interface.h
+++ b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_app_interface.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 IOS_CHROME_BROWSER_UI_QR_SCANNER_QR_SCANNER_APP_INTERFACE_H_
-#define IOS_CHROME_BROWSER_UI_QR_SCANNER_QR_SCANNER_APP_INTERFACE_H_
+#ifndef IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_APP_INTERFACE_H_
+#define IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_APP_INTERFACE_H_
 
 #import <AVFoundation/AVFoundation.h>
 #import <UIKit/UIKit.h>
@@ -112,4 +112,4 @@
 
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_QR_SCANNER_QR_SCANNER_APP_INTERFACE_H_
+#endif  // IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_APP_INTERFACE_H_
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_app_interface.mm b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_app_interface.mm
similarity index 96%
rename from ios/chrome/browser/ui/qr_scanner/qr_scanner_app_interface.mm
rename to ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_app_interface.mm
index c27cd31c..87cf240 100644
--- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_app_interface.mm
+++ b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_app_interface.mm
@@ -2,7 +2,7 @@
 // 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/qr_scanner/qr_scanner_app_interface.h"
+#import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_app_interface.h"
 
 #import "base/apple/foundation_util.h"
 #import "base/strings/sys_string_conversions.h"
@@ -16,8 +16,8 @@
 #import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
 #import "ios/chrome/browser/shared/ui/symbols/chrome_icon.h"
 #import "ios/chrome/browser/ui/location_bar/location_bar_url_loader.h"
-#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_camera_controller.h"
-#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller.h"
+#import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_camera_controller.h"
+#import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view_controller.h"
 #import "ios/chrome/browser/ui/scanner/camera_controller.h"
 #import "ios/chrome/browser/url_loading/model/url_loading_browser_agent.h"
 #import "ios/chrome/browser/url_loading/model/url_loading_params.h"
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_app_interface_stub.mm b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_app_interface_stub.mm
similarity index 76%
rename from ios/chrome/browser/ui/qr_scanner/qr_scanner_app_interface_stub.mm
rename to ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_app_interface_stub.mm
index 59a75fd..2c3d5ff 100644
--- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_app_interface_stub.mm
+++ b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_app_interface_stub.mm
@@ -2,7 +2,7 @@
 // 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/qr_scanner/qr_scanner_app_interface.h"
+#import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_app_interface.h"
 
 #import "ios/testing/earl_grey/earl_grey_test.h"
 
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_camera_controller.h b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_camera_controller.h
similarity index 65%
rename from ios/chrome/browser/ui/qr_scanner/qr_scanner_camera_controller.h
rename to ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_camera_controller.h
index 821269d..125dcd7 100644
--- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_camera_controller.h
+++ b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_camera_controller.h
@@ -2,10 +2,10 @@
 // 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_QR_SCANNER_QR_SCANNER_CAMERA_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_UI_QR_SCANNER_QR_SCANNER_CAMERA_CONTROLLER_H_
+#ifndef IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_CAMERA_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_CAMERA_CONTROLLER_H_
 
-#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_camera_controller_delegate.h"
+#import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_camera_controller_delegate.h"
 
 @class CameraController;
 
@@ -23,4 +23,4 @@
 
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_QR_SCANNER_QR_SCANNER_CAMERA_CONTROLLER_H_
+#endif  // IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_CAMERA_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_camera_controller.mm b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_camera_controller.mm
similarity index 97%
rename from ios/chrome/browser/ui/qr_scanner/qr_scanner_camera_controller.mm
rename to ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_camera_controller.mm
index 5beee8b..1a6df2a 100644
--- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_camera_controller.mm
+++ b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_camera_controller.mm
@@ -2,7 +2,7 @@
 // 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/qr_scanner/qr_scanner_camera_controller.h"
+#import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_camera_controller.h"
 
 #import "base/apple/foundation_util.h"
 
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_camera_controller_delegate.h b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_camera_controller_delegate.h
similarity index 72%
rename from ios/chrome/browser/ui/qr_scanner/qr_scanner_camera_controller_delegate.h
rename to ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_camera_controller_delegate.h
index d2f48e2d..beedb53 100644
--- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_camera_controller_delegate.h
+++ b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_camera_controller_delegate.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 IOS_CHROME_BROWSER_UI_QR_SCANNER_QR_SCANNER_CAMERA_CONTROLLER_DELEGATE_H_
-#define IOS_CHROME_BROWSER_UI_QR_SCANNER_QR_SCANNER_CAMERA_CONTROLLER_DELEGATE_H_
+#ifndef IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_CAMERA_CONTROLLER_DELEGATE_H_
+#define IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_CAMERA_CONTROLLER_DELEGATE_H_
 
 #import "ios/chrome/browser/ui/scanner/camera_controller.h"
 
@@ -19,4 +19,4 @@
 
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_QR_SCANNER_QR_SCANNER_CAMERA_CONTROLLER_DELEGATE_H_
+#endif  // IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_CAMERA_CONTROLLER_DELEGATE_H_
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_legacy_coordinator.h
similarity index 74%
rename from ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h
rename to ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_legacy_coordinator.h
index 31bf8861..1a0fa08b 100644
--- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h
+++ b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_legacy_coordinator.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 IOS_CHROME_BROWSER_UI_QR_SCANNER_QR_SCANNER_LEGACY_COORDINATOR_H_
-#define IOS_CHROME_BROWSER_UI_QR_SCANNER_QR_SCANNER_LEGACY_COORDINATOR_H_
+#ifndef IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_LEGACY_COORDINATOR_H_
+#define IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_LEGACY_COORDINATOR_H_
 
 #import "ios/chrome/browser/shared/coordinator/chrome_coordinator/chrome_coordinator.h"
 
@@ -21,4 +21,4 @@
 
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_QR_SCANNER_QR_SCANNER_LEGACY_COORDINATOR_H_
+#endif  // IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_LEGACY_COORDINATOR_H_
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.mm b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_legacy_coordinator.mm
similarity index 94%
rename from ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.mm
rename to ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_legacy_coordinator.mm
index 2d25e87..1cf5f78 100644
--- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.mm
+++ b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_legacy_coordinator.mm
@@ -2,7 +2,7 @@
 // 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/qr_scanner/qr_scanner_legacy_coordinator.h"
+#import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_legacy_coordinator.h"
 
 #import <ostream>
 
@@ -12,7 +12,7 @@
 #import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
 #import "ios/chrome/browser/shared/public/commands/omnibox_commands.h"
 #import "ios/chrome/browser/shared/public/commands/qr_scanner_commands.h"
-#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller.h"
+#import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view_controller.h"
 #import "ios/chrome/browser/ui/scanner/scanner_presenting.h"
 
 @interface QRScannerLegacyCoordinator () <ScannerPresenting>
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view.h b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view.h
similarity index 71%
rename from ios/chrome/browser/ui/qr_scanner/qr_scanner_view.h
rename to ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view.h
index 4ffeb3b..f31649f 100644
--- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view.h
+++ b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view.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 IOS_CHROME_BROWSER_UI_QR_SCANNER_QR_SCANNER_VIEW_H_
-#define IOS_CHROME_BROWSER_UI_QR_SCANNER_QR_SCANNER_VIEW_H_
+#ifndef IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_VIEW_H_
+#define IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_VIEW_H_
 
 #import "ios/chrome/browser/ui/scanner/scanner_view.h"
 
@@ -15,4 +15,4 @@
 
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_QR_SCANNER_QR_SCANNER_VIEW_H_
+#endif  // IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_VIEW_H_
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view.mm b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view.mm
similarity index 92%
rename from ios/chrome/browser/ui/qr_scanner/qr_scanner_view.mm
rename to ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view.mm
index 4e478e4..e1f7acc 100644
--- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view.mm
+++ b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view.mm
@@ -2,7 +2,7 @@
 // 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/qr_scanner/qr_scanner_view.h"
+#import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view.h"
 
 #import "ios/chrome/grit/ios_strings.h"
 #import "ui/base/device_form_factor.h"
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller.h b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view_controller.h
similarity index 80%
rename from ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller.h
rename to ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view_controller.h
index 109df37..54803cee 100644
--- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller.h
+++ b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view_controller.h
@@ -2,10 +2,10 @@
 // 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_QR_SCANNER_QR_SCANNER_VIEW_CONTROLLER_H_
-#define IOS_CHROME_BROWSER_UI_QR_SCANNER_QR_SCANNER_VIEW_CONTROLLER_H_
+#ifndef IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_VIEW_CONTROLLER_H_
+#define IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_VIEW_CONTROLLER_H_
 
-#include "ios/chrome/browser/ui/qr_scanner/qr_scanner_camera_controller.h"
+#include "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_camera_controller.h"
 #import "ios/chrome/browser/ui/scanner/scanner_view_controller.h"
 
 @protocol LoadQueryCommands;
@@ -41,4 +41,4 @@
 
 @end
 
-#endif  // IOS_CHROME_BROWSER_UI_QR_SCANNER_QR_SCANNER_VIEW_CONTROLLER_H_
+#endif  // IOS_CHROME_BROWSER_QR_SCANNER_UI_BUNDLED_QR_SCANNER_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller.mm b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view_controller.mm
similarity index 95%
rename from ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller.mm
rename to ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view_controller.mm
index 01cac2f..c45196f 100644
--- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller.mm
+++ b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view_controller.mm
@@ -2,15 +2,15 @@
 // 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/qr_scanner/qr_scanner_view_controller.h"
+#import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view_controller.h"
 
 #import "base/logging.h"
 #import "base/metrics/user_metrics.h"
 #import "base/metrics/user_metrics_action.h"
 #import "base/strings/sys_string_conversions.h"
 #import "ios/chrome/browser/shared/public/commands/load_query_commands.h"
-#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_camera_controller.h"
-#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_view.h"
+#import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_camera_controller.h"
+#import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view.h"
 #import "ios/chrome/browser/ui/scanner/scanner_alerts.h"
 #import "ios/chrome/browser/ui/scanner/scanner_presenting.h"
 #import "ios/chrome/browser/ui/scanner/scanner_transitioning_delegate.h"
diff --git a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view_controller_egtest.mm
similarity index 99%
rename from ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm
rename to ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view_controller_egtest.mm
index 6482604..975e603 100644
--- a/ios/chrome/browser/ui/qr_scanner/qr_scanner_view_controller_egtest.mm
+++ b/ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_view_controller_egtest.mm
@@ -8,7 +8,7 @@
 #import "base/ios/ios_util.h"
 #import "base/strings/stringprintf.h"
 #import "base/strings/sys_string_conversions.h"
-#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_app_interface.h"
+#import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_app_interface.h"
 #import "ios/chrome/browser/ui/scanner/camera_state.h"
 #import "ios/chrome/grit/ios_strings.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
diff --git a/ios/chrome/browser/tabs/model/BUILD.gn b/ios/chrome/browser/tabs/model/BUILD.gn
index 78ec13b..ab2790e5 100644
--- a/ios/chrome/browser/tabs/model/BUILD.gn
+++ b/ios/chrome/browser/tabs/model/BUILD.gn
@@ -64,6 +64,7 @@
     "//ios/chrome/browser/commerce/model/price_notifications",
     "//ios/chrome/browser/commerce/model/push_notification",
     "//ios/chrome/browser/complex_tasks/model",
+    "//ios/chrome/browser/contextual_panel/model",
     "//ios/chrome/browser/crash_report/model/breadcrumbs",
     "//ios/chrome/browser/download/model",
     "//ios/chrome/browser/drive/model:drive_tab_helper",
diff --git a/ios/chrome/browser/tabs/model/tab_helper_util.mm b/ios/chrome/browser/tabs/model/tab_helper_util.mm
index ac2196d..b94a202c 100644
--- a/ios/chrome/browser/tabs/model/tab_helper_util.mm
+++ b/ios/chrome/browser/tabs/model/tab_helper_util.mm
@@ -30,6 +30,7 @@
 #import "ios/chrome/browser/commerce/model/shopping_persisted_data_tab_helper.h"
 #import "ios/chrome/browser/commerce/model/shopping_service_factory.h"
 #import "ios/chrome/browser/complex_tasks/model/ios_task_tab_helper.h"
+#import "ios/chrome/browser/contextual_panel/model/contextual_panel_tab_helper.h"
 #import "ios/chrome/browser/crash_report/model/breadcrumbs/breadcrumb_manager_tab_helper.h"
 #import "ios/chrome/browser/download/model/ar_quick_look_tab_helper.h"
 #import "ios/chrome/browser/download/model/document_download_tab_helper.h"
@@ -311,4 +312,8 @@
   if (!is_off_the_record) {
     PriceNotificationsTabHelper::CreateForWebState(web_state);
   }
+
+  if (IsContextualPanelEnabled()) {
+    ContextualPanelTabHelper::CreateForWebState(web_state);
+  }
 }
diff --git a/ios/chrome/browser/ui/authentication/history_sync/BUILD.gn b/ios/chrome/browser/ui/authentication/history_sync/BUILD.gn
index 43a7f60e..4af8ce8f 100644
--- a/ios/chrome/browser/ui/authentication/history_sync/BUILD.gn
+++ b/ios/chrome/browser/ui/authentication/history_sync/BUILD.gn
@@ -22,6 +22,7 @@
     "//base",
     "//components/pref_registry",
     "//components/prefs",
+    "//components/signin/public/base:signin_switches",
     "//components/signin/public/identity_manager/objc",
     "//components/sync/service",
     "//components/unified_consent",
diff --git a/ios/chrome/browser/ui/authentication/history_sync/history_sync_coordinator.mm b/ios/chrome/browser/ui/authentication/history_sync/history_sync_coordinator.mm
index 05e8c32..44a28bb6 100644
--- a/ios/chrome/browser/ui/authentication/history_sync/history_sync_coordinator.mm
+++ b/ios/chrome/browser/ui/authentication/history_sync/history_sync_coordinator.mm
@@ -4,10 +4,12 @@
 
 #import "ios/chrome/browser/ui/authentication/history_sync/history_sync_coordinator.h"
 
+#import "base/feature_list.h"
 #import "base/memory/raw_ptr.h"
 #import "base/metrics/histogram_functions.h"
 #import "base/metrics/user_metrics.h"
 #import "components/signin/public/base/signin_metrics.h"
+#import "components/signin/public/base/signin_switches.h"
 #import "components/sync/base/user_selectable_type.h"
 #import "components/sync/service/sync_service.h"
 #import "components/sync/service/sync_user_settings.h"
@@ -165,6 +167,12 @@
 
   _viewController = [[HistorySyncViewController alloc] init];
   _viewController.delegate = self;
+
+  // TODO(b/318349283): This property will also be based on the capability
+  // CanShowHistorySyncOptInsWithoutMinorModeRestrictions.
+  _viewController.useEquallyWeightedButtons = base::FeatureList::IsEnabled(
+      switches::kMinorModeRestrictionsForHistorySyncOptIn);
+
   ChromeAccountManagerService* chromeAccountManagerService =
       ChromeAccountManagerServiceFactory::GetForBrowserState(browserState);
   signin::IdentityManager* identityManager =
diff --git a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
index 44c9b2e2..f72674ed 100644
--- a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
+++ b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.h
@@ -71,6 +71,7 @@
   AutocompleteHistoryManager* GetAutocompleteHistoryManager() override;
   CreditCardCvcAuthenticator* GetCvcAuthenticator() override;
   CreditCardOtpAuthenticator* GetOtpAuthenticator() override;
+  CreditCardRiskBasedAuthenticator* GetRiskBasedAuthenticator() override;
   PrefService* GetPrefs() override;
   const PrefService* GetPrefs() const override;
   syncer::SyncService* GetSyncService() override;
@@ -196,6 +197,7 @@
   CardExpirationDateFixFlowControllerImpl
       card_expiration_date_fix_flow_controller_;
   std::unique_ptr<payments::MandatoryReauthManager> payments_reauth_manager_;
+  std::unique_ptr<CreditCardRiskBasedAuthenticator> risk_based_authenticator_;
 
   // A weak reference to the view controller used to present UI.
   __weak UIViewController* base_view_controller_;
diff --git a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
index 64675b6..9b325101 100644
--- a/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
+++ b/ios/chrome/browser/ui/autofill/chrome_autofill_client_ios.mm
@@ -25,6 +25,7 @@
 #import "components/autofill/core/browser/payments/autofill_save_card_ui_info.h"
 #import "components/autofill/core/browser/payments/credit_card_cvc_authenticator.h"
 #import "components/autofill/core/browser/payments/credit_card_otp_authenticator.h"
+#import "components/autofill/core/browser/payments/credit_card_risk_based_authenticator.h"
 #import "components/autofill/core/browser/payments/payments_network_interface.h"
 #import "components/autofill/core/browser/payments/virtual_card_enroll_metrics_logger.h"
 #import "components/autofill/core/browser/payments/virtual_card_enrollment_manager.h"
@@ -176,6 +177,15 @@
   return otp_authenticator_.get();
 }
 
+CreditCardRiskBasedAuthenticator*
+ChromeAutofillClientIOS::GetRiskBasedAuthenticator() {
+  if (!risk_based_authenticator_) {
+    risk_based_authenticator_ =
+        std::make_unique<CreditCardRiskBasedAuthenticator>(this);
+  }
+  return risk_based_authenticator_.get();
+}
+
 PrefService* ChromeAutofillClientIOS::GetPrefs() {
   return const_cast<PrefService*>(std::as_const(*this).GetPrefs());
 }
diff --git a/ios/chrome/browser/ui/browser_view/BUILD.gn b/ios/chrome/browser/ui/browser_view/BUILD.gn
index 6d08f80..e84ff60b 100644
--- a/ios/chrome/browser/ui/browser_view/BUILD.gn
+++ b/ios/chrome/browser/ui/browser_view/BUILD.gn
@@ -95,6 +95,7 @@
     "//ios/chrome/browser/prefs/model",
     "//ios/chrome/browser/prerender/model",
     "//ios/chrome/browser/promos_manager/model:features",
+    "//ios/chrome/browser/qr_scanner/ui_bundled:coordinator",
     "//ios/chrome/browser/reading_list/model",
     "//ios/chrome/browser/segmentation_platform/model",
     "//ios/chrome/browser/send_tab_to_self/model",
@@ -189,7 +190,6 @@
     "//ios/chrome/browser/ui/price_notifications:price_notifications_iph",
     "//ios/chrome/browser/ui/print",
     "//ios/chrome/browser/ui/promos_manager",
-    "//ios/chrome/browser/ui/qr_scanner:coordinator",
     "//ios/chrome/browser/ui/reading_list",
     "//ios/chrome/browser/ui/recent_tabs",
     "//ios/chrome/browser/ui/sad_tab",
diff --git a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
index 6b3a57d6..06eae0da 100644
--- a/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_coordinator.mm
@@ -61,6 +61,7 @@
 #import "ios/chrome/browser/prerender/model/prerender_service.h"
 #import "ios/chrome/browser/prerender/model/prerender_service_factory.h"
 #import "ios/chrome/browser/promos_manager/model/features.h"
+#import "ios/chrome/browser/qr_scanner/ui_bundled/qr_scanner_legacy_coordinator.h"
 #import "ios/chrome/browser/reading_list/model/reading_list_browser_agent.h"
 #import "ios/chrome/browser/segmentation_platform/model/segmentation_platform_service_factory.h"
 #import "ios/chrome/browser/shared/coordinator/alert/repost_form_coordinator.h"
@@ -187,7 +188,6 @@
 #import "ios/chrome/browser/ui/price_notifications/price_notifications_view_coordinator.h"
 #import "ios/chrome/browser/ui/print/print_coordinator.h"
 #import "ios/chrome/browser/ui/promos_manager/promos_manager_coordinator.h"
-#import "ios/chrome/browser/ui/qr_scanner/qr_scanner_legacy_coordinator.h"
 #import "ios/chrome/browser/ui/reading_list/reading_list_coordinator.h"
 #import "ios/chrome/browser/ui/reading_list/reading_list_coordinator_delegate.h"
 #import "ios/chrome/browser/ui/recent_tabs/recent_tabs_coordinator.h"
diff --git a/ios/chrome/browser/ui/first_run/BUILD.gn b/ios/chrome/browser/ui/first_run/BUILD.gn
index b28ef52..5f72e61 100644
--- a/ios/chrome/browser/ui/first_run/BUILD.gn
+++ b/ios/chrome/browser/ui/first_run/BUILD.gn
@@ -219,6 +219,7 @@
     "//ios/chrome/browser/ui/search_engine_choice:earl_grey_ui_test_util",
     "//ios/chrome/browser/ui/settings/google_services:constants",
     "//ios/chrome/common:string_util",
+    "//ios/chrome/common/ui/colors",
     "//ios/chrome/common/ui/promo_style:constants",
     "//ios/chrome/common/ui/table_view:cells_constants",
     "//ios/chrome/test/earl_grey:eg_test_support+eg2",
diff --git a/ios/chrome/browser/ui/first_run/first_run_egtest.mm b/ios/chrome/browser/ui/first_run/first_run_egtest.mm
index cec27eb..cbc8a81 100644
--- a/ios/chrome/browser/ui/first_run/first_run_egtest.mm
+++ b/ios/chrome/browser/ui/first_run/first_run_egtest.mm
@@ -39,6 +39,7 @@
 #import "ios/chrome/browser/ui/search_engine_choice/search_engine_choice_earl_grey_ui_test_util.h"
 #import "ios/chrome/browser/ui/settings/google_services/google_services_settings_constants.h"
 #import "ios/chrome/browser/ui/settings/google_services/manage_sync_settings_constants.h"
+#import "ios/chrome/common/ui/colors/semantic_color_names.h"
 #import "ios/chrome/common/ui/promo_style/constants.h"
 #import "ios/chrome/common/ui/table_view/table_view_cells_constants.h"
 #import "ios/chrome/grit/ios_branded_strings.h"
@@ -454,6 +455,12 @@
         syncer::kReplaceSyncPromosWithSignInPromos);
   }
 
+  if ([self isRunningTest:@selector
+            (testHistorySyncShownWithEquallyWeightedButtons)]) {
+    config.features_enabled.push_back(
+        switches::kMinorModeRestrictionsForHistorySyncOptIn);
+  }
+
   return config;
 }
 
@@ -1118,6 +1125,47 @@
   [self verifySyncOrHistoryEnabled:YES];
 }
 
+// Tests that the History Sync Opt-In screen will have equally weighted button
+// for supervised users.
+// TODO(b/318349283): This feature is only behind a feature flag. It will then
+// be based on AccountCapabilities.
+- (void)testHistorySyncShownWithEquallyWeightedButtons {
+  // Add identity.
+  FakeSystemIdentity* fakeIdentity = [FakeSystemIdentity fakeIdentity1];
+  [SigninEarlGrey addFakeIdentity:fakeIdentity];
+  // Accept sign-in.
+  [[self elementInteractionWithGreyMatcher:
+             chrome_test_util::SigninScreenPromoPrimaryButtonMatcher()
+                      scrollViewIdentifier:
+                          kPromoStyleScrollViewAccessibilityIdentifier]
+      performAction:grey_tap()];
+  [SigninEarlGrey verifyPrimaryAccountWithEmail:fakeIdentity.userEmail
+                                        consent:signin::ConsentLevel::kSignin];
+  // Verify that the History Sync Opt-In screen is shown.
+  [[EarlGrey
+      selectElementWithMatcher:grey_accessibilityID(
+                                   kHistorySyncViewAccessibilityIdentifier)]
+      assertWithMatcher:grey_sufficientlyVisible()];
+  // Verify that the primary and secondary buttons have the same foreground and
+  // background colors.
+  NSString* foregroundColorName = kBlueColor;
+  NSString* backgroundColorName = kBlueHaloColor;
+  [[EarlGrey
+      selectElementWithMatcher:
+          grey_allOf(
+              chrome_test_util::ButtonWithForegroundColor(foregroundColorName),
+              chrome_test_util::ButtonWithBackgroundColor(backgroundColorName),
+              chrome_test_util::PromoStylePrimaryActionButtonMatcher(), nil)]
+      assertWithMatcher:grey_sufficientlyVisible()];
+  [[EarlGrey
+      selectElementWithMatcher:
+          grey_allOf(
+              chrome_test_util::ButtonWithForegroundColor(foregroundColorName),
+              chrome_test_util::ButtonWithBackgroundColor(backgroundColorName),
+              chrome_test_util::PromoStyleSecondaryActionButtonMatcher(), nil)]
+      assertWithMatcher:grey_sufficientlyVisible()];
+}
+
 #pragma mark - Sync UI Disabled
 
 // Tests sign-in with FRE when there's no account on the device.
@@ -1208,6 +1256,19 @@
                       scrollViewIdentifier:
                           kPromoStyleScrollViewAccessibilityIdentifier]
       assertWithMatcher:grey_notNil()];
+  // Verify that buttons have the expected colors.
+  [[EarlGrey
+      selectElementWithMatcher:
+          grey_allOf(chrome_test_util::ButtonWithForegroundColor(
+                         kSolidButtonTextColor),
+                     chrome_test_util::ButtonWithBackgroundColor(kBlueColor),
+                     chrome_test_util::PromoStylePrimaryActionButtonMatcher(),
+                     nil)] assertWithMatcher:grey_sufficientlyVisible()];
+  [[EarlGrey
+      selectElementWithMatcher:
+          grey_allOf(chrome_test_util::ButtonWithForegroundColor(kBlueColor),
+                     chrome_test_util::PromoStyleSecondaryActionButtonMatcher(),
+                     nil)] assertWithMatcher:grey_sufficientlyVisible()];
 }
 
 // Tests that the correct subtitle is shown in the FRE sign-in screen if the
diff --git a/ios/chrome/browser/ui/main/BUILD.gn b/ios/chrome/browser/ui/main/BUILD.gn
index efc9d398..eb57ab7 100644
--- a/ios/chrome/browser/ui/main/BUILD.gn
+++ b/ios/chrome/browser/ui/main/BUILD.gn
@@ -82,6 +82,7 @@
     "//ios/chrome/browser/device_sharing/model",
     "//ios/chrome/browser/download/model",
     "//ios/chrome/browser/main/model",
+    "//ios/chrome/browser/qr_scanner/ui_bundled:coordinator",
     "//ios/chrome/browser/reading_list/model",
     "//ios/chrome/browser/sessions",
     "//ios/chrome/browser/sessions:session_restoration_service",
@@ -105,7 +106,6 @@
     "//ios/chrome/browser/ui/incognito_reauth:incognito_reauth_scene_agent",
     "//ios/chrome/browser/ui/page_info:coordinator",
     "//ios/chrome/browser/ui/print",
-    "//ios/chrome/browser/ui/qr_scanner:coordinator",
     "//ios/chrome/browser/ui/reading_list",
     "//ios/chrome/browser/ui/recent_tabs",
     "//ios/chrome/browser/ui/settings/sync",
diff --git a/ios/chrome/browser/ui/push_notification/notifications_opt_in_alert_coordinator.h b/ios/chrome/browser/ui/push_notification/notifications_opt_in_alert_coordinator.h
index 4014cc2..5be3e67 100644
--- a/ios/chrome/browser/ui/push_notification/notifications_opt_in_alert_coordinator.h
+++ b/ios/chrome/browser/ui/push_notification/notifications_opt_in_alert_coordinator.h
@@ -5,6 +5,8 @@
 #ifndef IOS_CHROME_BROWSER_UI_PUSH_NOTIFICATION_NOTIFICATIONS_OPT_IN_ALERT_COORDINATOR_H_
 #define IOS_CHROME_BROWSER_UI_PUSH_NOTIFICATION_NOTIFICATIONS_OPT_IN_ALERT_COORDINATOR_H_
 
+#import <optional>
+
 #import "ios/chrome/browser/shared/coordinator/chrome_coordinator/chrome_coordinator.h"
 
 enum class PushNotificationClientId;
@@ -36,7 +38,7 @@
 
 // The client id of the push notification client that the user is
 // opting-in for.
-@property(nonatomic, assign) PushNotificationClientId clientId;
+@property(nonatomic, assign) std::optional<PushNotificationClientId> clientId;
 
 // The confirmation message to show in a Snackbar when notifications have been
 // enabled.
diff --git a/ios/chrome/browser/ui/push_notification/notifications_opt_in_alert_coordinator.mm b/ios/chrome/browser/ui/push_notification/notifications_opt_in_alert_coordinator.mm
index 0a265a6c..b62696f5 100644
--- a/ios/chrome/browser/ui/push_notification/notifications_opt_in_alert_coordinator.mm
+++ b/ios/chrome/browser/ui/push_notification/notifications_opt_in_alert_coordinator.mm
@@ -29,6 +29,9 @@
 }
 
 - (void)start {
+  CHECK(self.clientId.has_value());
+  CHECK(self.confirmationMessage);
+
   [self requestPushNotificationPermission];
 }
 
@@ -122,7 +125,7 @@
   NSString* gaiaID = base::SysUTF8ToNSString(
       infoCache->GetGAIAIdOfBrowserStateAtIndex(browserStateIndex));
   GetApplicationContext()->GetPushNotificationService()->SetPreference(
-      gaiaID, self.clientId, true);
+      gaiaID, self.clientId.value(), true);
 }
 
 // Shows a snackbar message indicating that notifications are enabled.
diff --git a/ios/chrome/browser/ui/settings/notifications/BUILD.gn b/ios/chrome/browser/ui/settings/notifications/BUILD.gn
index 9c42d958..92515ceb 100644
--- a/ios/chrome/browser/ui/settings/notifications/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/notifications/BUILD.gn
@@ -31,6 +31,7 @@
     "//ios/chrome/browser/shared/ui/table_view:utils",
     "//ios/chrome/browser/shared/ui/table_view/cells",
     "//ios/chrome/browser/signin/model",
+    "//ios/chrome/browser/ui/content_suggestions:content_suggestions_ui_util",
     "//ios/chrome/browser/ui/push_notification:opt_in_alert_coordinator",
     "//ios/chrome/browser/ui/push_notification:presenters",
     "//ios/chrome/browser/ui/settings/notifications/tracking_price",
diff --git a/ios/chrome/browser/ui/settings/notifications/DEPS b/ios/chrome/browser/ui/settings/notifications/DEPS
index fa9f9d77..0b7e06c5 100644
--- a/ios/chrome/browser/ui/settings/notifications/DEPS
+++ b/ios/chrome/browser/ui/settings/notifications/DEPS
@@ -1,3 +1,4 @@
 include_rules = [
+  "+ios/chrome/browser/ui/content_suggestions",
   "+ios/chrome/browser/ui/push_notification",
 ]
diff --git a/ios/chrome/browser/ui/settings/notifications/notifications_coordinator.mm b/ios/chrome/browser/ui/settings/notifications/notifications_coordinator.mm
index 88f1b2fd..eeec5c907 100644
--- a/ios/chrome/browser/ui/settings/notifications/notifications_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/notifications/notifications_coordinator.mm
@@ -8,11 +8,13 @@
 #import "base/check.h"
 #import "base/check_op.h"
 #import "base/strings/sys_string_conversions.h"
+#import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
 #import "ios/chrome/browser/shared/model/browser/browser.h"
 #import "ios/chrome/browser/shared/model/browser_state/chrome_browser_state.h"
 #import "ios/chrome/browser/shared/ui/table_view/table_view_utils.h"
 #import "ios/chrome/browser/signin/model/authentication_service.h"
 #import "ios/chrome/browser/signin/model/authentication_service_factory.h"
+#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.h"
 #import "ios/chrome/browser/ui/push_notification/notifications_opt_in_alert_coordinator.h"
 #import "ios/chrome/browser/ui/settings/notifications/notifications_mediator.h"
 #import "ios/chrome/browser/ui/settings/notifications/notifications_navigation_commands.h"
@@ -22,6 +24,7 @@
 #import "ios/chrome/browser/ui/settings/notifications/tracking_price/tracking_price_coordinator.h"
 #import "ios/chrome/grit/ios_branded_strings.h"
 #import "ios/chrome/grit/ios_strings.h"
+#import "ui/base/l10n/l10n_util.h"
 #import "ui/base/l10n/l10n_util_mac.h"
 
 @interface NotificationsCoordinator () <
@@ -99,8 +102,11 @@
   _optInAlertCoordinator = [[NotificationsOptInAlertCoordinator alloc]
       initWithBaseViewController:self.viewController
                          browser:self.browser];
+  _optInAlertCoordinator.clientId = PushNotificationClientId::kContent;
   _optInAlertCoordinator.alertMessage = l10n_util::GetNSString(
       IDS_IOS_CONTENT_NOTIFICATIONS_SETTINGS_ALERT_MESSAGE);
+  _optInAlertCoordinator.confirmationMessage =
+      l10n_util::GetNSString(IDS_IOS_CONTENT_NOTIFICATION_SNACKBAR_TITLE);
   [_optInAlertCoordinator start];
 }
 
@@ -109,8 +115,12 @@
   _optInAlertCoordinator = [[NotificationsOptInAlertCoordinator alloc]
       initWithBaseViewController:self.viewController
                          browser:self.browser];
+  _optInAlertCoordinator.clientId = PushNotificationClientId::kTips;
   _optInAlertCoordinator.alertMessage = l10n_util::GetNSString(
       IDS_IOS_TIPS_NOTIFICATIONS_SETTINGS_ALERT_SUBTITLE);
+  _optInAlertCoordinator.confirmationMessage = l10n_util::GetNSStringF(
+      IDS_IOS_NOTIFICATIONS_CONFIRMATION_MESSAGE,
+      l10n_util::GetStringUTF16(content_suggestions::SetUpListTitleStringID()));
   [_optInAlertCoordinator start];
 }
 
diff --git a/ios/chrome/browser/ui/settings/notifications/notifications_mediator.mm b/ios/chrome/browser/ui/settings/notifications/notifications_mediator.mm
index 530e891..70377d77 100644
--- a/ios/chrome/browser/ui/settings/notifications/notifications_mediator.mm
+++ b/ios/chrome/browser/ui/settings/notifications/notifications_mediator.mm
@@ -266,7 +266,7 @@
       if (!value) {
         break;
       }
-      [self.presenter presentTipsNotificationPermissionAlert];
+      [self.presenter presentPushNotificationPermissionAlert];
       break;
     }
     case ItemTypeTipsNotifications: {
diff --git a/ios/chrome/browser/ui/settings/password/passwords_mediator.mm b/ios/chrome/browser/ui/settings/password/passwords_mediator.mm
index 3bb378e7..2da939b 100644
--- a/ios/chrome/browser/ui/settings/password/passwords_mediator.mm
+++ b/ios/chrome/browser/ui/settings/password/passwords_mediator.mm
@@ -127,9 +127,9 @@
   _currentState = _passwordCheckManager->GetPasswordCheckState();
   [self updateConsumerPasswordCheckState:_currentState];
   [self.consumer
-      setSavingPasswordsToAccount:password_manager::sync_util::
-                                      GetPasswordSyncState(_syncService) !=
-                                  password_manager::SyncState::kNotSyncing];
+      setSavingPasswordsToAccount:
+          password_manager::sync_util::GetPasswordSyncState(_syncService) !=
+          password_manager::sync_util::SyncState::kNotActive];
 }
 
 - (void)disconnect {
@@ -398,9 +398,9 @@
 
 - (void)onSyncStateChanged {
   [self.consumer
-      setSavingPasswordsToAccount:password_manager::sync_util::
-                                      GetPasswordSyncState(_syncService) !=
-                                  password_manager::SyncState::kNotSyncing];
+      setSavingPasswordsToAccount:
+          password_manager::sync_util::GetPasswordSyncState(_syncService) !=
+          password_manager::sync_util::SyncState::kNotActive];
 }
 
 @end
diff --git a/ios/chrome/common/ui/promo_style/promo_style_view_controller.h b/ios/chrome/common/ui/promo_style/promo_style_view_controller.h
index 4320830..72a9ba9a0 100644
--- a/ios/chrome/common/ui/promo_style/promo_style_view_controller.h
+++ b/ios/chrome/common/ui/promo_style/promo_style_view_controller.h
@@ -186,6 +186,9 @@
 // Aligns the elements to the top of the view.
 @property(nonatomic, assign) BOOL topAlignedLayout;
 
+// Whether action buttons should have the same visual style.
+@property(nonatomic, assign) BOOL useEquallyWeightedButtons;
+
 @end
 
 #endif  // IOS_CHROME_COMMON_UI_PROMO_STYLE_PROMO_STYLE_VIEW_CONTROLLER_H_
diff --git a/ios/chrome/common/ui/promo_style/promo_style_view_controller.mm b/ios/chrome/common/ui/promo_style/promo_style_view_controller.mm
index 9f1e57d..d0307bc 100644
--- a/ios/chrome/common/ui/promo_style/promo_style_view_controller.mm
+++ b/ios/chrome/common/ui/promo_style/promo_style_view_controller.mm
@@ -49,6 +49,7 @@
 constexpr CGFloat kLearnMoreButtonSide = 40;
 constexpr CGFloat kheaderImageSize = 48;
 constexpr CGFloat kFullheaderImageSize = 100;
+constexpr CGFloat kStackViewDefaultSpacing = 10;
 
 // Corner radius for the whole view.
 constexpr CGFloat kCornerRadius = 20;
@@ -125,7 +126,7 @@
   // layout.
   BOOL _shouldScrollToBottom;
 
-  // Wheter the buttons have been updated from "More" to the action buttons.
+  // Whether the buttons have been updated from "More" to the action buttons.
   BOOL _buttonUpdated;
 }
 
@@ -219,6 +220,13 @@
   _actionStackView.axis = UILayoutConstraintAxisVertical;
   _actionStackView.translatesAutoresizingMaskIntoConstraints = NO;
   [_actionStackView addArrangedSubview:self.primaryActionButton];
+
+  // Spacing is needed when all buttons have filled background.
+  if (self.useEquallyWeightedButtons) {
+    [_actionStackView setCustomSpacing:kStackViewDefaultSpacing
+                             afterView:_primaryActionButton];
+  }
+
   [view addSubview:_actionStackView];
 
   // Create a layout guide to constrain the width of the content, while still
@@ -719,35 +727,46 @@
   return _specificContentView;
 }
 
+- (HighlightButton*)createHighlightButtonWithText:(NSString*)buttonText
+                          accessibilityIdentifier:
+                              (NSString*)accessibilityIdentifier {
+  UIButtonConfiguration* buttonConfiguration =
+      [UIButtonConfiguration plainButtonConfiguration];
+  buttonConfiguration.contentInsets = NSDirectionalEdgeInsetsMake(
+      kButtonVerticalInsets, 0, kButtonVerticalInsets, 0);
+  buttonConfiguration.titlePadding = kMoreArrowMargin;
+  buttonConfiguration.background.backgroundColor =
+      self.useEquallyWeightedButtons ? [UIColor colorNamed:kBlueHaloColor]
+                                     : [UIColor colorNamed:kBlueColor];
+  buttonConfiguration.baseForegroundColor =
+      self.useEquallyWeightedButtons
+          ? [UIColor colorNamed:kBlueColor]
+          : [UIColor colorNamed:kSolidButtonTextColor];
+  buttonConfiguration.background.cornerRadius = kPrimaryButtonCornerRadius;
+  buttonConfiguration.title = buttonText;
+  buttonConfiguration.titleLineBreakMode = NSLineBreakByTruncatingTail;
+
+  HighlightButton* button = [[HighlightButton alloc] initWithFrame:CGRectZero];
+  button.configuration = buttonConfiguration;
+  [self setPrimaryActionButtonFont:button];
+  button.translatesAutoresizingMaskIntoConstraints = NO;
+  button.pointerInteractionEnabled = YES;
+  button.pointerStyleProvider = CreateOpaqueButtonPointerStyleProvider();
+  button.accessibilityIdentifier = accessibilityIdentifier;
+  return button;
+}
+
 - (UIButton*)primaryActionButton {
   if (!_primaryActionButton) {
-    _primaryActionButton = [[HighlightButton alloc] initWithFrame:CGRectZero];
-    UIButtonConfiguration* buttonConfiguration =
-        [UIButtonConfiguration plainButtonConfiguration];
-    buttonConfiguration.contentInsets = NSDirectionalEdgeInsetsMake(
-        kButtonVerticalInsets, 0, kButtonVerticalInsets, 0);
-    buttonConfiguration.titlePadding = kMoreArrowMargin;
-    buttonConfiguration.background.backgroundColor =
-        [UIColor colorNamed:kBlueColor];
-    buttonConfiguration.baseForegroundColor =
-        [UIColor colorNamed:kSolidButtonTextColor];
-    buttonConfiguration.background.cornerRadius = kPrimaryButtonCornerRadius;
-
     // Use `primaryActionString` even if scrolling to the end is mandatory
     // because at the viewDidLoad stage, the scroll view hasn't computed its
     // content height, so there is no way to know if scrolling is needed.
     // This label will be updated at the viewDidAppear stage if necessary.
-    buttonConfiguration.title = self.primaryActionString;
-    buttonConfiguration.titleLineBreakMode = NSLineBreakByTruncatingTail;
-    _primaryActionButton.configuration = buttonConfiguration;
-    [self setPrimaryActionButtonFont:_primaryActionButton];
+    _primaryActionButton =
+        [self createHighlightButtonWithText:self.primaryActionString
+                    accessibilityIdentifier:
+                        kPromoStylePrimaryActionAccessibilityIdentifier];
 
-    _primaryActionButton.translatesAutoresizingMaskIntoConstraints = NO;
-    _primaryActionButton.pointerInteractionEnabled = YES;
-    _primaryActionButton.pointerStyleProvider =
-        CreateOpaqueButtonPointerStyleProvider();
-    _primaryActionButton.accessibilityIdentifier =
-        kPromoStylePrimaryActionAccessibilityIdentifier;
     [_primaryActionButton addTarget:self
                              action:@selector(didTapPrimaryActionButton)
                    forControlEvents:UIControlEventTouchUpInside];
@@ -1333,12 +1352,21 @@
 
 - (UIButton*)createSecondaryActionButton {
   DCHECK(self.secondaryActionString);
-  UIButton* button = [self createButtonWithText:self.secondaryActionString
-                        accessibilityIdentifier:
-                            kPromoStyleSecondaryActionAccessibilityIdentifier];
-  UILabel* titleLabel = button.titleLabel;
-  titleLabel.adjustsFontSizeToFitWidth = YES;
-  titleLabel.minimumScaleFactor = 0.7;
+  UIButton* button;
+  if (self.useEquallyWeightedButtons) {
+    // Create the secondaryActionButton matching the button type, colors, and
+    // text style of the primaryActionButton.
+    button = [self createHighlightButtonWithText:self.secondaryActionString
+                         accessibilityIdentifier:
+                             kPromoStyleSecondaryActionAccessibilityIdentifier];
+  } else {
+    button = [self createButtonWithText:self.secondaryActionString
+                accessibilityIdentifier:
+                    kPromoStyleSecondaryActionAccessibilityIdentifier];
+    UILabel* titleLabel = button.titleLabel;
+    titleLabel.adjustsFontSizeToFitWidth = YES;
+    titleLabel.minimumScaleFactor = 0.7;
+  }
 
   [button addTarget:self
                 action:@selector(didTapSecondaryActionButton)
diff --git a/ios/chrome/test/earl_grey/BUILD.gn b/ios/chrome/test/earl_grey/BUILD.gn
index 4ee0b715..c4dbdd1a4 100644
--- a/ios/chrome/test/earl_grey/BUILD.gn
+++ b/ios/chrome/test/earl_grey/BUILD.gn
@@ -98,6 +98,7 @@
     "//ios/chrome/browser/passwords/model",
     "//ios/chrome/browser/passwords/model:eg_app_support+eg2",
     "//ios/chrome/browser/policy/model:eg_app_support+eg2",
+    "//ios/chrome/browser/qr_scanner/ui_bundled:eg_app_support+eg2",
     "//ios/chrome/browser/search_engines/model:eg_app_support+eg2",
     "//ios/chrome/browser/search_engines/model:search_engines_util",
     "//ios/chrome/browser/search_engines/model:template_url_service_factory",
@@ -156,7 +157,6 @@
     "//ios/chrome/browser/ui/permissions:eg_app_support+eg2",
     "//ios/chrome/browser/ui/popup_menu:constants",
     "//ios/chrome/browser/ui/popup_menu/overflow_menu:feature_flags",
-    "//ios/chrome/browser/ui/qr_scanner:eg_app_support+eg2",
     "//ios/chrome/browser/ui/reading_list:eg_app_support+eg2",
     "//ios/chrome/browser/ui/recent_tabs:eg_app_support+eg2",
     "//ios/chrome/browser/ui/recent_tabs:recent_tabs_ui_constants",
diff --git a/ios/chrome/test/earl_grey/chrome_matchers.h b/ios/chrome/test/earl_grey/chrome_matchers.h
index f75e4c558..b0e9256 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers.h
+++ b/ios/chrome/test/earl_grey/chrome_matchers.h
@@ -39,6 +39,14 @@
 // `label` and accessibility trait UIAccessibilityTraitButton.
 id<GREYMatcher> ButtonWithAccessibilityLabel(NSString* label);
 
+// Returns a matcher for element with with foreground color corresponding to
+// `colorName` and accessibility trait UIAccessibilityTraitButton.
+id<GREYMatcher> ButtonWithForegroundColor(NSString* colorName);
+
+// Returns a matcher for element with with background color corresponding to
+// `colorName` and accessibility trait UIAccessibilityTraitButton.
+id<GREYMatcher> ButtonWithBackgroundColor(NSString* colorName);
+
 // Returns a matcher for context menu items with accessibility label
 // corresponding to `label`.
 id<GREYMatcher> ContextMenuItemWithAccessibilityLabel(NSString* label);
diff --git a/ios/chrome/test/earl_grey/chrome_matchers.mm b/ios/chrome/test/earl_grey/chrome_matchers.mm
index c0760a6c..024f1775 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers.mm
+++ b/ios/chrome/test/earl_grey/chrome_matchers.mm
@@ -39,6 +39,14 @@
   return [ChromeMatchersAppInterface buttonWithAccessibilityLabelID:message_id];
 }
 
+id<GREYMatcher> ButtonWithForegroundColor(NSString* colorName) {
+  return [ChromeMatchersAppInterface buttonWithForegroundColor:colorName];
+}
+
+id<GREYMatcher> ButtonWithBackgroundColor(NSString* colorName) {
+  return [ChromeMatchersAppInterface buttonWithBackgroundColor:colorName];
+}
+
 id<GREYMatcher> ContextMenuItemWithAccessibilityLabel(NSString* label) {
   return
       [ChromeMatchersAppInterface contextMenuItemWithAccessibilityLabel:label];
diff --git a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h
index 31f9c99..b146d43de 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h
+++ b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.h
@@ -35,6 +35,14 @@
 // and accessibility trait UIAccessibilityTraitButton.
 + (id<GREYMatcher>)buttonWithAccessibilityLabelID:(int)messageID;
 
+// Matcher for element with foreground color corresponding to `colorName`
+// and accessibility trait UIAccessibilityTraitButton.
++ (id<GREYMatcher>)buttonWithForegroundColor:(NSString*)colorName;
+
+// Matcher for element with background color corresponding to `colorName`
+// and accessibility trait UIAccessibilityTraitButton.
++ (id<GREYMatcher>)buttonWithBackgroundColor:(NSString*)colorName;
+
 // Matcher for context menu items with accessibility label
 // corresponding to `label`.
 + (id<GREYMatcher>)contextMenuItemWithAccessibilityLabel:(NSString*)label;
diff --git a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
index 5f282cf..e1e3aa1 100644
--- a/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_matchers_app_interface.mm
@@ -176,6 +176,56 @@
       buttonWithAccessibilityLabel:l10n_util::GetNSStringWithFixup(messageID)];
 }
 
++ (id<GREYMatcher>)buttonWithForegroundColor:(NSString*)colorName {
+  GREYMatchesBlock matches = ^BOOL(id element) {
+    if (![element isKindOfClass:UIButton.class]) {
+      return NO;
+    }
+    UIButton* button = base::apple::ObjCCastStrict<UIButton>(element);
+    return CGColorEqualToColor(
+        [UIColor colorNamed:colorName].CGColor,
+        button.configuration.baseForegroundColor.CGColor);
+  };
+
+  NSString* descriptionString =
+      [NSString stringWithFormat:@"Foreground color %@", colorName];
+
+  GREYDescribeToBlock describe = ^(id<GREYDescription> description) {
+    [description appendText:descriptionString];
+  };
+
+  id<GREYMatcher> foregroundColorMatcher =
+      [[GREYElementMatcherBlock alloc] initWithMatchesBlock:matches
+                                           descriptionBlock:describe];
+  return grey_allOf(grey_accessibilityTrait(UIAccessibilityTraitButton),
+                    foregroundColorMatcher, nil);
+}
+
++ (id<GREYMatcher>)buttonWithBackgroundColor:(NSString*)colorName {
+  GREYMatchesBlock matches = ^BOOL(id element) {
+    if (![element isKindOfClass:UIButton.class]) {
+      return NO;
+    }
+    UIButton* button = base::apple::ObjCCastStrict<UIButton>(element);
+    return CGColorEqualToColor(
+        [UIColor colorNamed:colorName].CGColor,
+        button.configuration.background.backgroundColor.CGColor);
+  };
+
+  NSString* descriptionString =
+      [NSString stringWithFormat:@"Background color %@", colorName];
+
+  GREYDescribeToBlock describe = ^(id<GREYDescription> description) {
+    [description appendText:descriptionString];
+  };
+
+  id<GREYMatcher> backgroundColorMatcher =
+      [[GREYElementMatcherBlock alloc] initWithMatchesBlock:matches
+                                           descriptionBlock:describe];
+  return grey_allOf(grey_accessibilityTrait(UIAccessibilityTraitButton),
+                    backgroundColorMatcher, nil);
+}
+
 + (id<GREYMatcher>)contextMenuItemWithAccessibilityLabel:(NSString*)label {
   return grey_allOf(grey_accessibilityLabel(label),
                     grey_accessibilityTrait(UIAccessibilityTraitButton), nil);
diff --git a/ios/chrome/test/earl_grey2/BUILD.gn b/ios/chrome/test/earl_grey2/BUILD.gn
index 6119f03..561f9d63 100644
--- a/ios/chrome/test/earl_grey2/BUILD.gn
+++ b/ios/chrome/test/earl_grey2/BUILD.gn
@@ -183,6 +183,7 @@
 
   deps = [
     "//ios/chrome/browser/plus_addresses/ui:eg2_tests",
+    "//ios/chrome/browser/qr_scanner/ui_bundled:eg2_tests",
     "//ios/chrome/browser/settings/model/sync/utils:eg2_tests",
     "//ios/chrome/browser/ui/autofill/bottom_sheet:eg2_tests",
     "//ios/chrome/browser/ui/autofill/form_input_accessory:eg2_tests",
@@ -216,7 +217,6 @@
     "//ios/chrome/browser/ui/popup_menu/overflow_menu:eg2_tests",
     "//ios/chrome/browser/ui/popup_menu/overflow_menu/destination_usage_history:eg2_tests",
     "//ios/chrome/browser/ui/price_notifications:eg2_tests",
-    "//ios/chrome/browser/ui/qr_scanner:eg2_tests",
     "//ios/chrome/browser/ui/reading_list:eg2_tests",
     "//ios/chrome/browser/ui/recent_tabs:eg2_tests",
     "//ios/chrome/browser/ui/sad_tab:eg2_tests",
diff --git a/ios/testing/data/http_server_files/tall_page.html b/ios/testing/data/http_server_files/tall_page.html
index ed20770..8d3b0ffb 100644
--- a/ios/testing/data/http_server_files/tall_page.html
+++ b/ios/testing/data/http_server_files/tall_page.html
@@ -3,7 +3,7 @@
   <head>
     <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
     <title>Full Screen</title>
-    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
   </head>
   <body>
     <p>List of numbers</p>
diff --git a/ios/web/js_features/fullscreen/BUILD.gn b/ios/web/js_features/fullscreen/BUILD.gn
index 9db48f5e..51d2deb 100644
--- a/ios/web/js_features/fullscreen/BUILD.gn
+++ b/ios/web/js_features/fullscreen/BUILD.gn
@@ -10,6 +10,8 @@
     ":fullscreen_js",
     "//base",
     "//ios/web/public/js_messaging",
+    "//ios/web/web_state:web_state_impl_header",
+    "//ios/web/web_state/ui:web_controller_header",
   ]
 
   sources = [
@@ -22,7 +24,11 @@
   visibility = [ ":fullscreen" ]
 
   sources = [ "resources/fullscreen.ts" ]
-  deps = [ "//ios/web/public/js_messaging:util_scripts" ]
+  deps = [
+    "//ios/web/public/js_messaging:frame_id",
+    "//ios/web/public/js_messaging:gcrweb",
+    "//ios/web/public/js_messaging:util_scripts",
+  ]
 }
 
 source_set("unittests") {
@@ -32,8 +38,11 @@
     ":fullscreen",
     "//base/test:test_support",
     "//ios/web/js_messaging:java_script_feature",
+    "//ios/web/public",
     "//ios/web/public/js_messaging",
     "//ios/web/public/test:test_fixture",
+    "//ios/web/web_state:web_state_impl_header",
+    "//ios/web/web_state/ui:web_controller_header",
     "//testing/gtest",
   ]
 
diff --git a/ios/web/js_features/fullscreen/fullscreen_java_script_feature.mm b/ios/web/js_features/fullscreen/fullscreen_java_script_feature.mm
index 365fd112..a157164 100644
--- a/ios/web/js_features/fullscreen/fullscreen_java_script_feature.mm
+++ b/ios/web/js_features/fullscreen/fullscreen_java_script_feature.mm
@@ -6,6 +6,10 @@
 
 #import "ios/web/public/js_messaging/java_script_feature_util.h"
 #import "ios/web/public/js_messaging/script_message.h"
+#import "ios/web/public/js_messaging/web_frame.h"
+#import "ios/web/public/js_messaging/web_frames_manager.h"
+#import "ios/web/web_state/ui/crw_web_controller.h"
+#import "ios/web/web_state/web_state_impl.h"
 
 namespace {
 const char kScriptName[] = "fullscreen";
@@ -28,11 +32,12 @@
           ContentWorld::kIsolatedWorld,
           {FeatureScript::CreateWithFilename(
               kScriptName,
-              FeatureScript::InjectionTime::kDocumentStart,
+              FeatureScript::InjectionTime::kDocumentEnd,
               FeatureScript::TargetFrames::kMainFrame,
               FeatureScript::ReinjectionBehavior::
                   kReinjectOnDocumentRecreation)},
-          {web::java_script_features::GetCommonJavaScriptFeature()}) {}
+          {web::java_script_features::GetCommonJavaScriptFeature(),
+           web::java_script_features::GetMessageJavaScriptFeature()}) {}
 FullscreenJavaScriptFeature::~FullscreenJavaScriptFeature() = default;
 
 std::optional<std::string>
@@ -49,12 +54,26 @@
     return;
   }
 
-  std::optional<bool> viewport_fit_cover =
-      script_dict->FindBool(kScriptMessageViewportFitCoverKey);
-  if (viewport_fit_cover) {
-    // TODO(crbug.com/1394631): Implement logic to correctly handle
-    // viewport-fit:cover.
+  if (!script_message.is_main_frame()) {
+    return;
   }
+
+  const std::string* frame_id = script_dict->FindString("frame_id");
+  if (!frame_id) {
+    return;
+  }
+
+  WebFrame* main_frame = GetWebFramesManager(web_state)->GetMainWebFrame();
+  std::string main_frame_id = main_frame ? main_frame->GetFrameId() : "";
+  if (main_frame_id != *frame_id) {
+    // Frame has changed, do not send message to the web controller as it would
+    // update the incorrect navigation item.
+    return;
+  }
+  auto cover = script_dict->FindBool(kScriptMessageViewportFitCoverKey).value();
+  CRWWebController* web_controller =
+      WebStateImpl::FromWebState(web_state)->GetWebController();
+  [web_controller handleViewportFit:static_cast<BOOL>(cover)];
 }
 
 }  // namespace web
diff --git a/ios/web/js_features/fullscreen/fullscreen_java_script_feature_unittest.mm b/ios/web/js_features/fullscreen/fullscreen_java_script_feature_unittest.mm
index 4c8e1a5..dcfd841f 100644
--- a/ios/web/js_features/fullscreen/fullscreen_java_script_feature_unittest.mm
+++ b/ios/web/js_features/fullscreen/fullscreen_java_script_feature_unittest.mm
@@ -8,6 +8,9 @@
 #import "ios/web/js_messaging/java_script_feature_manager.h"
 #import "ios/web/public/js_messaging/java_script_feature_util.h"
 #import "ios/web/public/test/web_test_with_web_state.h"
+#import "ios/web/public/web_state.h"
+#import "ios/web/web_state/ui/crw_web_controller.h"
+#import "ios/web/web_state/web_state_impl.h"
 #import "testing/gtest_mac.h"
 
 namespace web {
@@ -19,9 +22,6 @@
 
   void SetUp() override {
     WebTestWithWebState::SetUp();
-    OverrideJavaScriptFeatures(
-        {web::java_script_features::GetCommonJavaScriptFeature(),
-         web::FullscreenJavaScriptFeature::GetInstance()});
   }
 };
 
@@ -34,8 +34,9 @@
   LoadHtml(html);
   ASSERT_TRUE(WaitUntilLoaded());
 
-  // TODO(crbug.com/1394631): Verify that CRWWebControllerContainerView's
-  // `cover` property is true.
+  CRWWebController* web_controller =
+      web::WebStateImpl::FromWebState(web_state())->GetWebController();
+  ASSERT_TRUE(web_controller.isCover);
 }
 
 // Tests that a page with viewport-fit=auto correctly propagates this state
@@ -47,8 +48,9 @@
   LoadHtml(html);
   ASSERT_TRUE(WaitUntilLoaded());
 
-  // TODO(crbug.com/1394631): Verify that CRWWebControllerContainerView's
-  // `cover` property is false.
+  CRWWebController* web_controller =
+      web::WebStateImpl::FromWebState(web_state())->GetWebController();
+  ASSERT_FALSE(web_controller.isCover);
 }
 
 // Tests that a page with no viewport-fit value correctly propagates this state
@@ -59,8 +61,9 @@
   LoadHtml(html);
   ASSERT_TRUE(WaitUntilLoaded());
 
-  // TODO(crbug.com/1394631): Verify that CRWWebControllerContainerView's
-  // `cover` property is false.
+  CRWWebController* web_controller =
+      web::WebStateImpl::FromWebState(web_state())->GetWebController();
+  ASSERT_FALSE(web_controller.isCover);
 }
 
 }  // namespace web
diff --git a/ios/web/js_features/fullscreen/resources/fullscreen.ts b/ios/web/js_features/fullscreen/resources/fullscreen.ts
index c39fbc0..b21b1be 100644
--- a/ios/web/js_features/fullscreen/resources/fullscreen.ts
+++ b/ios/web/js_features/fullscreen/resources/fullscreen.ts
@@ -6,18 +6,22 @@
  * @fileoverview Reports viewport details to the app.
  */
 
-import {sendWebKitMessage} from '//ios/web/public/js_messaging/resources/utils.js'
+import { getFrameId } from '//ios/web/public/js_messaging/resources/frame_id.js';
+import { sendWebKitMessage } from '//ios/web/public/js_messaging/resources/utils.js'
 
 /**
  * Reads the viewport configuration and reports it back to the browser.
  */
-function reportViewportConfiguration(): void {
-  // TODO(crbug.com/1394631): Find the current value of viewport-fit and report
-  // it to the browser.
-  sendWebKitMessage('FullscreenViewportHandler',
-    {
-      'cover' : false
+function reportViewportConfiguration() {
+  let viewportMeta = window.document.querySelector('meta[name = "viewport"]');
+  if (viewportMeta) {
+    let coverValue =
+        viewportMeta.getAttribute('content')?.includes('viewport-fit=cover');
+    sendWebKitMessage('FullscreenViewportHandler', {
+      'frame_id': getFrameId(),
+      'cover': coverValue,
     });
+  }
 }
 
 window.addEventListener('load', reportViewportConfiguration);
diff --git a/ios/web/js_messaging/java_script_feature_util_impl.mm b/ios/web/js_messaging/java_script_feature_util_impl.mm
index 3b34dc5..4f50a5c7 100644
--- a/ios/web/js_messaging/java_script_feature_util_impl.mm
+++ b/ios/web/js_messaging/java_script_feature_util_impl.mm
@@ -80,6 +80,7 @@
       ContextMenuJavaScriptFeature::FromBrowserState(browser_state),
       ErrorPageJavaScriptFeature::GetInstance(),
       FindInPageJavaScriptFeature::GetInstance(),
+      FullscreenJavaScriptFeature::GetInstance(),
       GetFaviconJavaScriptFeature(),
       GetScrollHelperJavaScriptFeature(),
       GetWindowErrorJavaScriptFeature(),
diff --git a/ios/web/web_state/ui/crw_context_menu_controller.mm b/ios/web/web_state/ui/crw_context_menu_controller.mm
index 70703e56..2f3a798 100644
--- a/ios/web/web_state/ui/crw_context_menu_controller.mm
+++ b/ios/web/web_state/ui/crw_context_menu_controller.mm
@@ -111,11 +111,13 @@
   params.location = [self.webView convertPoint:location
                                       fromView:interaction.view];
 
-  __block UIContextMenuConfiguration* configuration;
-  self.webState->GetDelegate()->ContextMenuConfiguration(
-      self.webState, params, ^(UIContextMenuConfiguration* conf) {
-        configuration = conf;
-      });
+  __block UIContextMenuConfiguration* configuration = nil;
+  if (self.webState && self.webState->GetDelegate()) {
+    self.webState->GetDelegate()->ContextMenuConfiguration(
+        self.webState, params, ^(UIContextMenuConfiguration* conf) {
+          configuration = conf;
+        });
+  }
 
   if (configuration) {
     // User long pressed on a link or an image. Cancelling all touches will
@@ -168,8 +170,10 @@
         (UIContextMenuConfiguration*)configuration
                                             animator:
         (id<UIContextMenuInteractionCommitAnimating>)animator {
-  self.webState->GetDelegate()->ContextMenuWillCommitWithAnimator(self.webState,
-                                                                  animator);
+  if (self.webState && self.webState->GetDelegate()) {
+    self.webState->GetDelegate()->ContextMenuWillCommitWithAnimator(
+        self.webState, animator);
+  }
 }
 
 - (void)contextMenuInteraction:(UIContextMenuInteraction*)interaction
diff --git a/ios/web/web_state/ui/crw_web_controller.h b/ios/web/web_state/ui/crw_web_controller.h
index eac5d6a..9535a3c 100644
--- a/ios/web/web_state/ui/crw_web_controller.h
+++ b/ios/web/web_state/ui/crw_web_controller.h
@@ -250,6 +250,12 @@
 // Returns the under page background color.
 - (UIColor*)underPageBackgroundColor;
 
+#pragma mark Fullscreen Message Handlers
+
+// Handles the viewport fit value, `isCover` is true when the "viewport-fit" is
+// equal to "cover".
+- (void)handleViewportFit:(BOOL)isCover;
+
 #pragma mark Navigation Message Handlers
 
 // Handles a navigation hash change message for the current webpage.
@@ -283,6 +289,8 @@
 // Returns the current page loading phase.
 // TODO(crbug.com/956511): Remove this once refactor is done.
 @property(nonatomic, readonly, assign) web::WKNavigationState navigationState;
+// YES if the web container view fill the screen.
+@property(nonatomic, readonly) BOOL isCover;
 
 // Injects a CRWWebViewContentView for testing.  Takes ownership of
 // `webViewContentView`.
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 363870b..215c1e6 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -426,6 +426,10 @@
   return _touchTrackingRecognizer;
 }
 
+- (BOOL)isCover {
+  return _containerView.cover;
+}
+
 #pragma mark Navigation and Session Information
 
 - (NavigationManagerImpl*)navigationManagerImpl {
@@ -882,6 +886,11 @@
   return self.webView.interactionState;
 }
 
+- (void)handleViewportFit:(BOOL)isCover {
+  _containerView.cover = isCover;
+  [_containerView layoutSubviews];
+}
+
 - (void)handleNavigationHashChange {
   web::NavigationItemImpl* currentItem = self.currentNavItem;
   if (currentItem) {
diff --git a/ios/web/web_state/ui/crw_web_controller_container_view.h b/ios/web/web_state/ui/crw_web_controller_container_view.h
index e0f6cc62d..d71c1cd 100644
--- a/ios/web/web_state/ui/crw_web_controller_container_view.h
+++ b/ios/web/web_state/ui/crw_web_controller_container_view.h
@@ -43,6 +43,9 @@
 @property(nonatomic, weak) id<CRWWebControllerContainerViewDelegate>
     delegate;  // weak
 
+// YES if the webView should cover the entire screen and ignore the safe area.
+@property(nonatomic, assign) BOOL cover;
+
 // Designated initializer.  `proxy`'s content view will be updated as different
 // content is added to the container.
 - (instancetype)initWithDelegate:
diff --git a/ios/web/web_state/ui/crw_web_controller_container_view.mm b/ios/web/web_state/ui/crw_web_controller_container_view.mm
index 9243246..9b18ff9 100644
--- a/ios/web/web_state/ui/crw_web_controller_container_view.mm
+++ b/ios/web/web_state/ui/crw_web_controller_container_view.mm
@@ -29,7 +29,7 @@
 @synthesize delegate = _delegate;
 
 - (instancetype)initWithDelegate:
-        (id<CRWWebControllerContainerViewDelegate>)delegate {
+    (id<CRWWebControllerContainerViewDelegate>)delegate {
   self = [super initWithFrame:CGRectZero];
   if (self) {
     DCHECK(delegate);
@@ -69,7 +69,7 @@
   if (![_webViewContentView isEqual:webViewContentView]) {
     [_webViewContentView removeFromSuperview];
     _webViewContentView = webViewContentView;
-    [_webViewContentView setFrame:self.bounds];
+    [self updateWebViewContentViewFrame];
     [self addSubview:_webViewContentView];
   }
 }
@@ -108,7 +108,7 @@
 
   // webViewContentView layout.  `-setNeedsLayout` is called in case any webview
   // layout updates need to occur despite the bounds size staying constant.
-  self.webViewContentView.frame = self.bounds;
+  [self updateWebViewContentViewFrame];
   [self.webViewContentView setNeedsLayout];
 }
 
@@ -133,7 +133,7 @@
   if (containerWindow ||
       ![_delegate shouldKeepRenderProcessAliveForContainerView:self]) {
     if (self.webViewContentView.superview != self) {
-      [_webViewContentView setFrame:self.bounds];
+      [self updateWebViewContentViewFrame];
       // Insert the content view on the back of the container view so any view
       // that was presented on top of the content view can still appear.
       [self insertSubview:_webViewContentView atIndex:0];
@@ -178,4 +178,24 @@
   [self.webViewContentView.webView drawRect:rect];
 }
 
+#pragma mark - UIView overrides
+
+- (void)safeAreaInsetsDidChange {
+  // Update the frame to take into account the safe area inset as they are set
+  // fractionally later than the rest of the view loading.
+  [self updateWebViewContentViewFrame];
+}
+
+#pragma mark - Private helpers
+
+// Update the content view frame.
+- (void)updateWebViewContentViewFrame {
+  if (self.cover) {
+    [self.webViewContentView setFrame:self.bounds];
+  } else {
+    [self.webViewContentView
+        setFrame:UIEdgeInsetsInsetRect(self.bounds, self.safeAreaInsets)];
+  }
+}
+
 @end
diff --git a/ios/web_view/internal/passwords/web_view_password_manager_client.h b/ios/web_view/internal/passwords/web_view_password_manager_client.h
index d3a2e54..a14966e 100644
--- a/ios/web_view/internal/passwords/web_view_password_manager_client.h
+++ b/ios/web_view/internal/passwords/web_view_password_manager_client.h
@@ -57,7 +57,6 @@
   ~WebViewPasswordManagerClient() override;
 
   // password_manager::PasswordManagerClient implementation.
-  password_manager::SyncState GetPasswordSyncState() const override;
   bool PromptUserToSaveOrUpdatePassword(
       std::unique_ptr<password_manager::PasswordFormManagerForUI> form_to_save,
       bool update_password) override;
diff --git a/ios/web_view/internal/passwords/web_view_password_manager_client.mm b/ios/web_view/internal/passwords/web_view_password_manager_client.mm
index db12972..a286dd1 100644
--- a/ios/web_view/internal/passwords/web_view_password_manager_client.mm
+++ b/ios/web_view/internal/passwords/web_view_password_manager_client.mm
@@ -11,7 +11,6 @@
 #include "components/autofill/core/browser/logging/log_manager.h"
 #include "components/keyed_service/core/service_access_type.h"
 #include "components/password_manager/core/browser/password_form.h"
-#import "components/password_manager/core/browser/password_sync_util.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/password_manager/ios/password_manager_ios_util.h"
 #import "ios/web_view/internal/app/application_context.h"
@@ -28,7 +27,6 @@
 using password_manager::PasswordFormManagerForUI;
 using password_manager::PasswordManagerMetricsRecorder;
 using password_manager::PasswordStoreInterface;
-using password_manager::SyncState;
 
 namespace ios_web_view {
 
@@ -84,10 +82,7 @@
       account_store_(account_store),
       reuse_manager_(reuse_manager),
       password_feature_manager_(pref_service, sync_service),
-      credentials_filter_(
-          this,
-          base::BindRepeating(&WebViewPasswordManagerClient::GetSyncService,
-                              base::Unretained(this))),
+      credentials_filter_(this),
       requirements_service_(requirements_service),
       helper_(this) {
   saving_passwords_enabled_.Init(
@@ -96,10 +91,6 @@
 
 WebViewPasswordManagerClient::~WebViewPasswordManagerClient() = default;
 
-SyncState WebViewPasswordManagerClient::GetPasswordSyncState() const {
-  return password_manager::sync_util::GetPasswordSyncState(sync_service_);
-}
-
 bool WebViewPasswordManagerClient::PromptUserToChooseCredentials(
     std::vector<std::unique_ptr<password_manager::PasswordForm>> local_forms,
     const url::Origin& origin,
diff --git a/ios_internal b/ios_internal
index b8db844..e0f7bfa 160000
--- a/ios_internal
+++ b/ios_internal
@@ -1 +1 @@
-Subproject commit b8db84491e44b069aebdf9afd1c1118efc806d05
+Subproject commit e0f7bfa6413ecdadaf5626cfe53f3ef58d23c102
diff --git a/media/formats/mp4/aac.cc b/media/formats/mp4/aac.cc
index 102f0de3..4f16711 100644
--- a/media/formats/mp4/aac.cc
+++ b/media/formats/mp4/aac.cc
@@ -207,7 +207,7 @@
   auto adts = std::make_unique<uint8_t[]>(total_size);
 
   auto adts_span = base::make_span(adts.get(), total_size);
-  auto adts_header = adts_span.first(kADTSHeaderMinSize);
+  auto adts_header = adts_span.first<kADTSHeaderMinSize>();
   auto adts_data = adts_span.subspan(kADTSHeaderMinSize);
 
   SetAdtsHeader(adts_header, total_size);
@@ -258,7 +258,7 @@
 
   adts.insert(buffer->begin(), kADTSHeaderMinSize, 0);
 
-  SetAdtsHeader(base::make_span(adts).first(kADTSHeaderMinSize), size);
+  SetAdtsHeader(base::make_span(adts).first<kADTSHeaderMinSize>(), size);
 
   *adts_header_size = kADTSHeaderMinSize;
   return true;
diff --git a/native_client b/native_client
index 225f880..fb7f990 160000
--- a/native_client
+++ b/native_client
@@ -1 +1 @@
-Subproject commit 225f880e2de2a2d2b33fbdad55ca27eca3cdc103
+Subproject commit fb7f990c4d1d25abfd5bad2ed2b00b12dacbca14
diff --git a/net/http/transport_security_state_static.pins b/net/http/transport_security_state_static.pins
index b1617c1..043ee12 100644
--- a/net/http/transport_security_state_static.pins
+++ b/net/http/transport_security_state_static.pins
@@ -43,9 +43,9 @@
 #   hash function for preloaded entries again (we have already done so once).
 #
 
-# Last updated: 2024-02-04 12:55 UTC
+# Last updated: 2024-02-05 12:54 UTC
 PinsListTimestamp
-1707051352
+1707137643
 
 TestSPKI
 sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
diff --git a/net/http/transport_security_state_static_pins.json b/net/http/transport_security_state_static_pins.json
index 7fb6c100..cd529be5 100644
--- a/net/http/transport_security_state_static_pins.json
+++ b/net/http/transport_security_state_static_pins.json
@@ -31,7 +31,7 @@
 // the 'static_spki_hashes' and 'bad_static_spki_hashes' fields in 'pinsets'
 // refer to, and the timestamp at which the pins list was last updated.
 //
-// Last updated: 2024-02-04 12:55 UTC
+// Last updated: 2024-02-05 12:54 UTC
 //
 {
   "pinsets": [
diff --git a/services/network/DIR_METADATA b/services/network/DIR_METADATA
index eeda617..96a3b62 100644
--- a/services/network/DIR_METADATA
+++ b/services/network/DIR_METADATA
@@ -1,11 +1,6 @@
-# Metadata information for this directory.
-#
-# For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
-#
-# For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
-
-monorail {
+monorail: {
   component: "Internals>Services>Network"
-}
\ No newline at end of file
+}
+buganizer_public: {
+  component_id: 1456990
+}
diff --git a/services/network/cors/DIR_METADATA b/services/network/cors/DIR_METADATA
index 56c5048b..5fcc522d 100644
--- a/services/network/cors/DIR_METADATA
+++ b/services/network/cors/DIR_METADATA
@@ -1,11 +1,6 @@
-# Metadata information for this directory.
-#
-# For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
-#
-# For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
-
-monorail {
+monorail: {
   component: "Blink>SecurityFeature>CORS"
-}
\ No newline at end of file
+}
+buganizer_public: {
+  component_id: 1456518
+}
diff --git a/services/network/first_party_sets/DIR_METADATA b/services/network/first_party_sets/DIR_METADATA
index 7f16b604..997d54a1 100644
--- a/services/network/first_party_sets/DIR_METADATA
+++ b/services/network/first_party_sets/DIR_METADATA
@@ -1,5 +1,7 @@
+monorail: {
+  component: "Internals>Network>First-Party-Sets"
+}
 team_email: "chrome-first-party-sets@chromium.org"
-monorail {
-        project: "chromium"
-        component: "Internals>Network>First-Party-Sets"
+buganizer_public: {
+  component_id: 1456243
 }
diff --git a/services/network/ip_protection/DIR_METADATA b/services/network/ip_protection/DIR_METADATA
index 77705009..0dd79617 100644
--- a/services/network/ip_protection/DIR_METADATA
+++ b/services/network/ip_protection/DIR_METADATA
@@ -1,4 +1,7 @@
+monorail: {
+  component: "Privacy>Fingerprinting>IPProtection"
+}
 team_email: "ip-protection-trio@google.com"
-monorail {
-   component: "Privacy>Fingerprinting>IPProtection"
+buganizer_public: {
+  component_id: 1456782
 }
diff --git a/services/network/p2p/DIR_METADATA b/services/network/p2p/DIR_METADATA
index 5d8ce5b..b8f041e 100644
--- a/services/network/p2p/DIR_METADATA
+++ b/services/network/p2p/DIR_METADATA
@@ -1,11 +1,6 @@
-# Metadata information for this directory.
-#
-# For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
-#
-# For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
-
-monorail {
+monorail: {
   component: "Blink>WebRTC"
-}
\ No newline at end of file
+}
+buganizer_public: {
+  component_id: 1456096
+}
diff --git a/services/network/public/cpp/cert_verifier/DIR_METADATA b/services/network/public/cpp/cert_verifier/DIR_METADATA
index 15af268..db473f6d 100644
--- a/services/network/public/cpp/cert_verifier/DIR_METADATA
+++ b/services/network/public/cpp/cert_verifier/DIR_METADATA
@@ -1,12 +1,7 @@
-# Metadata information for this directory.
-#
-# For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
-#
-# For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
-
-monorail {
+monorail: {
   component: "Internals>Network>Certificate"
 }
 team_email: "trusty-transport@chromium.org"
+buganizer_public: {
+  component_id: 1456839
+}
diff --git a/services/network/public/cpp/corb/DIR_METADATA b/services/network/public/cpp/corb/DIR_METADATA
index b956b941..c4a53c30 100644
--- a/services/network/public/cpp/corb/DIR_METADATA
+++ b/services/network/public/cpp/corb/DIR_METADATA
@@ -1,4 +1,7 @@
-monorail {
+monorail: {
   component: "Blink>SecurityFeature>ORB"
 }
 team_email: "site-isolation-dev@chromium.org"
+buganizer_public: {
+  component_id: 1456282
+}
diff --git a/services/network/resource_scheduler/DIR_METADATA b/services/network/resource_scheduler/DIR_METADATA
index 486a7d11..78c7396 100644
--- a/services/network/resource_scheduler/DIR_METADATA
+++ b/services/network/resource_scheduler/DIR_METADATA
@@ -1,11 +1,6 @@
-# Metadata information for this directory.
-#
-# For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
-#
-# For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
-
-monorail {
+monorail: {
   component: "Internals>Network>NetworkQuality"
-}
\ No newline at end of file
+}
+buganizer_public: {
+  component_id: 1456882
+}
diff --git a/services/network/sct_auditing/DIR_METADATA b/services/network/sct_auditing/DIR_METADATA
index dbb74a32..c63053f 100644
--- a/services/network/sct_auditing/DIR_METADATA
+++ b/services/network/sct_auditing/DIR_METADATA
@@ -1,11 +1,6 @@
-# Metadata information for this directory.
-#
-# For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
-#
-# For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
-
-monorail {
+monorail: {
   component: "Internals>Network>CertTrans"
 }
+buganizer_public: {
+  component_id: 1456813
+}
diff --git a/services/network/trust_tokens/trust_token_database_owner.cc b/services/network/trust_tokens/trust_token_database_owner.cc
index 6a10cac..6253914 100644
--- a/services/network/trust_tokens/trust_token_database_owner.cc
+++ b/services/network/trust_tokens/trust_token_database_owner.cc
@@ -90,10 +90,6 @@
           db_task_runner)),
       db_task_runner_(db_task_runner),
       backing_database_(std::make_unique<sql::Database>(sql::DatabaseOptions{
-          // As they work on deleting the feature (crbug.com/1120969), sql/
-          // owners prefer to see which clients are explicitly okay with using
-          // exclusive locking (the default).
-          .exclusive_locking = true,
           .page_size = 4096,
           .cache_size = 500,
           // TODO(pwnall): Add a meta table and remove this option.
diff --git a/services/network/web_bundle/DIR_METADATA b/services/network/web_bundle/DIR_METADATA
index ea2195a..4ba3be55 100644
--- a/services/network/web_bundle/DIR_METADATA
+++ b/services/network/web_bundle/DIR_METADATA
@@ -1,11 +1,6 @@
-# Metadata information for this directory.
-#
-# For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
-#
-# For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
-
-monorail {
+monorail: {
   component: "Blink>Loader>WebPackaging"
-}
\ No newline at end of file
+}
+buganizer_public: {
+  component_id: 1456230
+}
diff --git a/skia/BUILD.gn b/skia/BUILD.gn
index 01ee204..fa4ee839 100644
--- a/skia/BUILD.gn
+++ b/skia/BUILD.gn
@@ -596,7 +596,10 @@
     sources += skia_skresources_sources
     sources += skia_sksg_sources
     sources += skia_shaper_primitive_sources
-    defines += [ "SKOTTIE_TRIVIAL_FONTRUN_ITER" ]
+    defines += [
+      "SK_SHAPER_PRIMITIVE_AVAILABLE",
+      "SKOTTIE_TRIVIAL_FONTRUN_ITER",
+    ]
   }
 }
 
diff --git a/storage/DIR_METADATA b/storage/DIR_METADATA
index 150afdb..154e22b 100644
--- a/storage/DIR_METADATA
+++ b/storage/DIR_METADATA
@@ -1,12 +1,7 @@
-# Metadata information for this directory.
-#
-# For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
-#
-# For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
-
-monorail {
+monorail: {
   component: "Blink>Storage"
 }
-team_email: "storage-dev@chromium.org"
\ No newline at end of file
+team_email: "storage-dev@chromium.org"
+buganizer_public: {
+  component_id: 1456519
+}
diff --git a/storage/browser/blob/COMMON_METADATA b/storage/browser/blob/COMMON_METADATA
index cec432d..1cf8979 100644
--- a/storage/browser/blob/COMMON_METADATA
+++ b/storage/browser/blob/COMMON_METADATA
@@ -1,3 +1,6 @@
-monorail {
+monorail: {
   component: "Blink>Storage>FileAPI"
 }
+buganizer_public: {
+  component_id: 1456727
+}
diff --git a/storage/browser/database/DIR_METADATA b/storage/browser/database/DIR_METADATA
index 5bb86736d..7158dee9 100644
--- a/storage/browser/database/DIR_METADATA
+++ b/storage/browser/database/DIR_METADATA
@@ -1,11 +1,6 @@
-# Metadata information for this directory.
-#
-# For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
-#
-# For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
-
-monorail {
+monorail: {
   component: "Blink>Storage>WebSQL"
 }
+buganizer_public: {
+  component_id: 1456877
+}
diff --git a/storage/browser/database/database_tracker.cc b/storage/browser/database/database_tracker.cc
index 2926db8..348245d 100644
--- a/storage/browser/database/database_tracker.cc
+++ b/storage/browser/database/database_tracker.cc
@@ -112,7 +112,6 @@
                   ? profile_path_.Append(kIncognitoDatabaseDirectoryName)
                   : profile_path_.Append(kDatabaseDirectoryName)),
       db_(std::make_unique<sql::Database>(sql::DatabaseOptions{
-          .exclusive_locking = true,
           .page_size = 4096,
           .cache_size = 500,
       })),
diff --git a/storage/browser/file_system/DIR_METADATA b/storage/browser/file_system/DIR_METADATA
index a6d562a..82476d47 100644
--- a/storage/browser/file_system/DIR_METADATA
+++ b/storage/browser/file_system/DIR_METADATA
@@ -1,11 +1,6 @@
-# Metadata information for this directory.
-#
-# For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
-#
-# For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
-
-monorail {
+monorail: {
   component: "Blink>Storage>FileSystem"
 }
+buganizer_public: {
+  component_id: 1456307
+}
diff --git a/storage/browser/quota/COMMON_METADATA b/storage/browser/quota/COMMON_METADATA
index cf9ebe3..7c44c7e9 100644
--- a/storage/browser/quota/COMMON_METADATA
+++ b/storage/browser/quota/COMMON_METADATA
@@ -1,3 +1,6 @@
-monorail {
+monorail: {
   component: "Blink>Storage>Quota"
 }
+buganizer_public: {
+  component_id: 1456618
+}
diff --git a/storage/common/database/DIR_METADATA b/storage/common/database/DIR_METADATA
index 5bb86736d..7158dee9 100644
--- a/storage/common/database/DIR_METADATA
+++ b/storage/common/database/DIR_METADATA
@@ -1,11 +1,6 @@
-# Metadata information for this directory.
-#
-# For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
-#
-# For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
-
-monorail {
+monorail: {
   component: "Blink>Storage>WebSQL"
 }
+buganizer_public: {
+  component_id: 1456877
+}
diff --git a/storage/common/file_system/DIR_METADATA b/storage/common/file_system/DIR_METADATA
index a6d562a..82476d47 100644
--- a/storage/common/file_system/DIR_METADATA
+++ b/storage/common/file_system/DIR_METADATA
@@ -1,11 +1,6 @@
-# Metadata information for this directory.
-#
-# For more information on DIR_METADATA files, see:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/README.md
-#
-# For the schema of this file, see Metadata message:
-#   https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto
-
-monorail {
+monorail: {
   component: "Blink>Storage>FileSystem"
 }
+buganizer_public: {
+  component_id: 1456307
+}
diff --git a/testing/buildbot/chrome.json b/testing/buildbot/chrome.json
index 294d3cf..553b00c1 100644
--- a/testing/buildbot/chrome.json
+++ b/testing/buildbot/chrome.json
@@ -2805,10 +2805,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04",
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index c134c84c8..73d31cf 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -551,7 +551,8 @@
         "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/android_31_google_atd_x64.textpb"
+          "--avd-config=../../tools/android/avd/proto/android_31_google_atd_x64.textpb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "args": [
@@ -563,6 +564,11 @@
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -4621,7 +4627,8 @@
         "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/android_32_google_atd_x64_foldable.textpb"
+          "--avd-config=../../tools/android/avd/proto/android_32_google_atd_x64_foldable.textpb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "args": [
@@ -4633,6 +4640,11 @@
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -8334,7 +8346,8 @@
         "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/android_33_google_atd_x64.textpb"
+          "--avd-config=../../tools/android/avd/proto/android_33_google_atd_x64.textpb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "args": [
@@ -8346,6 +8359,11 @@
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index ac08ae3..987b5d86 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -2213,7 +2213,8 @@
         "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/android_30_google_apis_x86.textpb"
+          "--avd-config=../../tools/android/avd/proto/android_30_google_apis_x86.textpb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "args": [
@@ -2225,6 +2226,11 @@
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -6044,7 +6050,8 @@
           "--use-persistent-shell",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/android_31_google_apis_x64.textpb"
+          "--avd-config=../../tools/android/avd/proto/android_31_google_apis_x64.textpb",
+          "--git-revision=${got_revision}"
         ],
         "description": "Run with android_31_google_apis_x64",
         "isolate_profile_data": true,
@@ -6058,6 +6065,11 @@
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -10371,7 +10383,8 @@
         "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/android_32_google_apis_x64_foldable.textpb"
+          "--avd-config=../../tools/android/avd/proto/android_32_google_apis_x64_foldable.textpb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "args": [
@@ -10383,6 +10396,11 @@
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -14035,7 +14053,8 @@
         "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/android_33_google_apis_x64.textpb"
+          "--avd-config=../../tools/android/avd/proto/android_33_google_apis_x64.textpb",
+          "--git-revision=${got_revision}"
         ],
         "description": "Run with android_33_google_apis_x64",
         "merge": {
@@ -14048,6 +14067,11 @@
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -18011,7 +18035,8 @@
         "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/android_34_google_apis_x64.textpb"
+          "--avd-config=../../tools/android/avd/proto/android_34_google_apis_x64.textpb",
+          "--git-revision=${got_revision}"
         ],
         "description": "Run with android_34_google_apis_x64",
         "merge": {
@@ -18024,6 +18049,11 @@
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -21868,7 +21898,8 @@
       {
         "args": [
           "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
+          "--recover-devices",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "args": [
@@ -21880,6 +21911,11 @@
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -30761,7 +30797,8 @@
           "--use-persistent-shell",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android26.textpb"
+          "--avd-config=../../tools/android/avd/proto/generic_android26.textpb",
+          "--git-revision=${got_revision}"
         ],
         "isolate_profile_data": true,
         "merge": {
@@ -30774,6 +30811,11 @@
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -35502,7 +35544,8 @@
         "args": [
           "--use-persistent-shell",
           "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
+          "--recover-devices",
+          "--git-revision=${got_revision}"
         ],
         "isolate_profile_data": true,
         "merge": {
@@ -35515,6 +35558,11 @@
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -36547,7 +36595,8 @@
         "args": [
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/android_28_google_apis_x86.textpb"
+          "--avd-config=../../tools/android/avd/proto/android_28_google_apis_x86.textpb",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "args": [
@@ -36559,6 +36608,11 @@
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
diff --git a/testing/buildbot/chromium.cft.json b/testing/buildbot/chromium.cft.json
index 43f85c3..f1e4959 100644
--- a/testing/buildbot/chromium.cft.json
+++ b/testing/buildbot/chromium.cft.json
@@ -152,11 +152,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "arm64",
@@ -2113,11 +2121,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04"
@@ -3953,11 +3969,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -5581,11 +5605,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 700d0d4c..8a81b8a 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -1651,10 +1651,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -3159,11 +3167,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -4724,11 +4740,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -5285,9 +5309,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_v123.0.6282.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6283.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6282.0",
+        "description": "Run with ash-chrome version 123.0.6283.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5297,8 +5321,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6282.0",
-              "revision": "version:123.0.6282.0"
+              "location": "lacros_version_skew_tests_v123.0.6283.0",
+              "revision": "version:123.0.6283.0"
             }
           ],
           "dimensions": {
@@ -5441,9 +5465,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_v123.0.6282.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6283.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6282.0",
+        "description": "Run with ash-chrome version 123.0.6283.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -5453,8 +5477,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6282.0",
-              "revision": "version:123.0.6282.0"
+              "location": "lacros_version_skew_tests_v123.0.6283.0",
+              "revision": "version:123.0.6283.0"
             }
           ],
           "dimensions": {
diff --git a/testing/buildbot/chromium.coverage.json b/testing/buildbot/chromium.coverage.json
index 7308dd2..df8d767 100644
--- a/testing/buildbot/chromium.coverage.json
+++ b/testing/buildbot/chromium.coverage.json
@@ -1253,7 +1253,8 @@
       {
         "args": [
           "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
+          "--recover-devices",
+          "--git-revision=${got_revision}"
         ],
         "isolate_profile_data": true,
         "merge": {
@@ -1266,6 +1267,11 @@
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -3958,7 +3964,8 @@
           "--use-persistent-shell",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
-          "--avd-config=../../tools/android/avd/proto/generic_android26.textpb"
+          "--avd-config=../../tools/android/avd/proto/generic_android26.textpb",
+          "--git-revision=${got_revision}"
         ],
         "isolate_profile_data": true,
         "merge": {
@@ -3971,6 +3978,11 @@
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -8565,13 +8577,19 @@
       {
         "args": [
           "--code-coverage-dir=${ISOLATED_OUTDIR}",
-          "--device-spec=virtual_device_large"
+          "--device-spec=virtual_device_large",
+          "--git-revision=${got_revision}"
         ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04"
@@ -16620,11 +16638,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04"
@@ -18004,11 +18030,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04"
@@ -19898,11 +19932,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04"
@@ -20422,9 +20464,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_v123.0.6282.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6283.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6282.0",
+        "description": "Run with ash-chrome version 123.0.6283.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -20434,8 +20476,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6282.0",
-              "revision": "version:123.0.6282.0"
+              "location": "lacros_version_skew_tests_v123.0.6283.0",
+              "revision": "version:123.0.6283.0"
             }
           ],
           "dimensions": {
@@ -20572,9 +20614,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_v123.0.6282.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6283.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6282.0",
+        "description": "Run with ash-chrome version 123.0.6283.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -20584,8 +20626,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6282.0",
-              "revision": "version:123.0.6282.0"
+              "location": "lacros_version_skew_tests_v123.0.6283.0",
+              "revision": "version:123.0.6283.0"
             }
           ],
           "dimensions": {
@@ -21381,11 +21423,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -22659,11 +22709,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Windows-10-19045"
diff --git a/testing/buildbot/chromium.fuchsia.fyi.json b/testing/buildbot/chromium.fuchsia.fyi.json
index 9cd9aab..dd2bb14 100644
--- a/testing/buildbot/chromium.fuchsia.fyi.json
+++ b/testing/buildbot/chromium.fuchsia.fyi.json
@@ -119,10 +119,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "arm64",
@@ -1049,10 +1057,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "kvm": "1",
@@ -2076,10 +2092,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "kvm": "1",
diff --git a/testing/buildbot/chromium.fuchsia.json b/testing/buildbot/chromium.fuchsia.json
index 815629c..b2f21b1 100644
--- a/testing/buildbot/chromium.fuchsia.json
+++ b/testing/buildbot/chromium.fuchsia.json
@@ -120,10 +120,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "arm64",
@@ -1093,11 +1101,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "kvm": "1",
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 29985c4..fba3518a 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -250,10 +250,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "arm64",
@@ -1947,7 +1955,15 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "test": "blink_platform_unittests",
         "test_id_prefix": "ninja://third_party/blink/renderer/platform:blink_platform_unittests/"
       },
@@ -5015,12 +5031,18 @@
           "${ISOLATED_OUTDIR}",
           "--xcode-build-version",
           "15e5178i",
-          "--xctest"
+          "--xctest",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
         },
         "name": "blink_platform_unittests iPhone 14 17.2",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -40280,10 +40302,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04"
@@ -40979,10 +41009,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04"
@@ -41908,10 +41946,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04",
@@ -42432,9 +42478,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_v123.0.6282.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6283.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6282.0",
+        "description": "Run with ash-chrome version 123.0.6283.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -42443,8 +42489,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6282.0",
-              "revision": "version:123.0.6282.0"
+              "location": "lacros_version_skew_tests_v123.0.6283.0",
+              "revision": "version:123.0.6283.0"
             }
           ],
           "dimensions": {
@@ -42582,9 +42628,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_v123.0.6282.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6283.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6282.0",
+        "description": "Run with ash-chrome version 123.0.6283.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -42593,8 +42639,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6282.0",
-              "revision": "version:123.0.6282.0"
+              "location": "lacros_version_skew_tests_v123.0.6283.0",
+              "revision": "version:123.0.6283.0"
             }
           ],
           "dimensions": {
@@ -43387,10 +43433,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04",
@@ -43906,9 +43960,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_v123.0.6282.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6283.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6282.0",
+        "description": "Run with ash-chrome version 123.0.6283.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -43917,8 +43971,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6282.0",
-              "revision": "version:123.0.6282.0"
+              "location": "lacros_version_skew_tests_v123.0.6283.0",
+              "revision": "version:123.0.6283.0"
             }
           ],
           "dimensions": {
@@ -44056,9 +44110,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_v123.0.6282.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6283.0/test_ash_chrome"
         ],
-        "description": "Run with ash-chrome version 123.0.6282.0",
+        "description": "Run with ash-chrome version 123.0.6283.0",
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
@@ -44067,8 +44121,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6282.0",
-              "revision": "version:123.0.6282.0"
+              "location": "lacros_version_skew_tests_v123.0.6283.0",
+              "revision": "version:123.0.6283.0"
             }
           ],
           "dimensions": {
@@ -45077,11 +45131,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04",
@@ -47405,10 +47467,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -49694,11 +49764,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json
index b9bc1df2..5676917 100644
--- a/testing/buildbot/chromium.linux.json
+++ b/testing/buildbot/chromium.linux.json
@@ -80,10 +80,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04"
@@ -1042,11 +1050,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04"
@@ -3083,13 +3099,19 @@
         "args": [
           "--no-xvfb",
           "--use-weston",
-          "--ozone-platform=wayland"
+          "--ozone-platform=wayland",
+          "--git-revision=${got_revision}"
         ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04"
@@ -4706,10 +4728,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04"
diff --git a/testing/buildbot/chromium.mac.json b/testing/buildbot/chromium.mac.json
index 6960fea1..714afd4 100644
--- a/testing/buildbot/chromium.mac.json
+++ b/testing/buildbot/chromium.mac.json
@@ -152,10 +152,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -1665,11 +1673,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -3291,11 +3307,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -4920,11 +4944,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -6627,10 +6659,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -23024,10 +23064,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "arm64",
@@ -24503,11 +24551,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "ci_only": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "arm64",
@@ -26066,11 +26122,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "ci_only": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "arm64",
diff --git a/testing/buildbot/chromium.memory.fyi.json b/testing/buildbot/chromium.memory.fyi.json
index 70db4c3..22af5a1 100644
--- a/testing/buildbot/chromium.memory.fyi.json
+++ b/testing/buildbot/chromium.memory.fyi.json
@@ -168,12 +168,18 @@
       },
       {
         "args": [
-          "--test-launcher-print-test-stdio=always"
+          "--test-launcher-print-test-stdio=always",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -1684,12 +1690,18 @@
       },
       {
         "args": [
-          "--test-launcher-print-test-stdio=always"
+          "--test-launcher-print-test-stdio=always",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04"
@@ -3161,11 +3173,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04"
@@ -4408,12 +4428,18 @@
       },
       {
         "args": [
-          "--test-launcher-print-test-stdio=always"
+          "--test-launcher-print-test-stdio=always",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -4737,12 +4763,18 @@
       },
       {
         "args": [
-          "--test-launcher-print-test-stdio=always"
+          "--test-launcher-print-test-stdio=always",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
diff --git a/testing/buildbot/chromium.memory.json b/testing/buildbot/chromium.memory.json
index 22530dd..0b0e409e 100644
--- a/testing/buildbot/chromium.memory.json
+++ b/testing/buildbot/chromium.memory.json
@@ -160,12 +160,18 @@
       },
       {
         "args": [
-          "--test-launcher-print-test-stdio=always"
+          "--test-launcher-print-test-stdio=always",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04"
@@ -1622,10 +1628,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04"
@@ -2960,12 +2974,18 @@
       },
       {
         "args": [
-          "--test-launcher-print-test-stdio=always"
+          "--test-launcher-print-test-stdio=always",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -4654,12 +4674,18 @@
       },
       {
         "args": [
-          "--test-launcher-print-test-stdio=always"
+          "--test-launcher-print-test-stdio=always",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -6296,12 +6322,18 @@
       },
       {
         "args": [
-          "--test-launcher-print-test-stdio=always"
+          "--test-launcher-print-test-stdio=always",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -7812,12 +7844,18 @@
       },
       {
         "args": [
-          "--test-launcher-print-test-stdio=always"
+          "--test-launcher-print-test-stdio=always",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04"
@@ -9304,12 +9342,18 @@
       },
       {
         "args": [
-          "--test-launcher-print-test-stdio=always"
+          "--test-launcher-print-test-stdio=always",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -11101,7 +11145,8 @@
       {
         "args": [
           "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
+          "--recover-devices",
+          "--git-revision=${got_revision}"
         ],
         "merge": {
           "args": [
@@ -11113,6 +11158,11 @@
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "resultdb": {
           "enable": true,
           "has_native_resultdb_integration": true
@@ -15739,13 +15789,19 @@
         "args": [
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
-          "--asan-symbolize-output"
+          "--asan-symbolize-output",
+          "--git-revision=${got_revision}"
         ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -16451,12 +16507,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_v123.0.6282.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6283.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 123.0.6282.0",
+        "description": "Run with ash-chrome version 123.0.6283.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16466,8 +16522,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6282.0",
-              "revision": "version:123.0.6282.0"
+              "location": "lacros_version_skew_tests_v123.0.6283.0",
+              "revision": "version:123.0.6283.0"
             }
           ],
           "dimensions": {
@@ -16627,12 +16683,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_v123.0.6282.0/test_ash_chrome",
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6283.0/test_ash_chrome",
           "--test-launcher-print-test-stdio=always",
           "--combine-ash-logs-on-bots",
           "--asan-symbolize-output"
         ],
-        "description": "Run with ash-chrome version 123.0.6282.0",
+        "description": "Run with ash-chrome version 123.0.6283.0",
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
@@ -16642,8 +16698,8 @@
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v123.0.6282.0",
-              "revision": "version:123.0.6282.0"
+              "location": "lacros_version_skew_tests_v123.0.6283.0",
+              "revision": "version:123.0.6283.0"
             }
           ],
           "dimensions": {
@@ -17678,10 +17734,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Ubuntu-22.04"
@@ -18886,10 +18950,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "os": "Windows-10-19045"
diff --git a/testing/buildbot/chromium.win.json b/testing/buildbot/chromium.win.json
index 3a649ce..2534dd4 100644
--- a/testing/buildbot/chromium.win.json
+++ b/testing/buildbot/chromium.win.json
@@ -259,11 +259,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -2447,10 +2455,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -4104,11 +4120,19 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "isolate_profile_data": true,
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "x86-64",
@@ -6057,10 +6081,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "arm64",
@@ -6658,7 +6690,7 @@
             "os": "Windows-11"
           },
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com",
-          "shards": 3
+          "shards": 9
         },
         "test": "interactive_ui_tests",
         "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/"
@@ -7690,10 +7722,18 @@
         "test_id_prefix": "ninja://third_party/blink/renderer/platform/heap:blink_heap_unittests/"
       },
       {
+        "args": [
+          "--git-revision=${got_revision}"
+        ],
         "merge": {
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
         "name": "blink_platform_unittests",
+        "precommit_args": [
+          "--gerrit-issue=${patch_issue}",
+          "--gerrit-patchset=${patch_set}",
+          "--buildbucket-id=${buildbucket_build_id}"
+        ],
         "swarming": {
           "dimensions": {
             "cpu": "arm64",
diff --git a/testing/buildbot/filters/accessibility-linux.browser_tests.filter b/testing/buildbot/filters/accessibility-linux.browser_tests.filter
index 26d3a98..4d67dcd 100644
--- a/testing/buildbot/filters/accessibility-linux.browser_tests.filter
+++ b/testing/buildbot/filters/accessibility-linux.browser_tests.filter
@@ -50,6 +50,7 @@
 -BrowserNavigatorTest.Disposition_IncompatibleWindow_NoExisting
 -BrowserNavigatorTest.SingletonIncognitoLeak
 -BrowserNavigatorTest.SwitchToTabIncognitoLeak
+-CapabilityDelegationBrowserTest.SameOriginPaymentRequest
 -ChromeMainTest.SecondLaunchFromIncognitoWithNormalUrl
 -ChromeNavigationBrowserTest.WindowOpenBlockedAfterClickNavigation
 -ChromeSitePerProcessTest.PostMessageSenderAndReceiverRaceToCreatePopup
@@ -108,6 +109,7 @@
 -HeadlessModeTaggedPrintToPdfCommandBrowserTest.HeadlessTaggedPrintToPdf/1
 -HistogramsInternalsUIBrowserTest.*
 -IFrameTest.InEmptyFrame
+-IdleBrowserTest.Start
 -InteractiveBrowserTestBrowsertest.InstrumentTabsAsTestSteps
 -InterestGroupPermissionsBrowserTest.ThirdPartyCookiesAllowedForSite
 -InterestGroupPermissionsBrowserTest.ThirdPartyCookiesBlocked
@@ -207,6 +209,7 @@
 -SW_IncognitoEnabled/IncognitoExtensionApiTabTest.Tabs/*
 -SystemNetworkContextManagerHttpNegotiateHeader.SetsPrefOnHttpNegotiateHeader
 -TabCaptureApiTest.CaptureInSplitIncognitoMode
+-TabResourceUsageTabHelperTest.MemoryUsageUpdatesAfterNavigation
 -ThirdPartyPartitionedStorageAccessibilityCanBeDisabledTest.Basic/0
 -ThirdPartyPartitionedStorageAccessibilityCanBeDisabledTest.Basic/1
 -ThirdPartyPartitionedStorageAccessibilitySharedWorkerTest.Basic/0
diff --git a/testing/buildbot/filters/ios.content_browsertests.filter b/testing/buildbot/filters/ios.content_browsertests.filter
index b2e22c7..c5d519dc 100644
--- a/testing/buildbot/filters/ios.content_browsertests.filter
+++ b/testing/buildbot/filters/ios.content_browsertests.filter
@@ -95,7 +95,6 @@
 -SRC_ClearKey/EncryptedMediaTest.Playback_VideoOnly_WebM_VP9Profile2/0
 -ScrollBehaviorBrowserTest.InstantScriptScrollAdjustsSmoothWheelScroll
 -ScrollBehaviorBrowserTest.SmoothWheelScrollCompletesWithScriptedMirror
--ScrollLatencyScrollbarBrowserTest.ScrollbarThumbDragLatency
 -SignedExchangeSubresourcePrefetchBrowserTest.IntegrityMismatchSendsReport
 -SitePerProcessHighDPIHitTestBrowserTest.CursorUpdateReceivedFromCrossSiteIframe
 -SitePerProcessHitTestBrowserTest.CrossProcessMouseCapture
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index ec18e441..1bdbc04f 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -1249,6 +1249,10 @@
     "label": "//components/optimization_guide/internal:ondevice_model_example",
     "type": "additional_compile_target",
   },
+  "ondevice_stability_tests": {
+    "label": "//components/optimization_guide/internal/testing:ondevice_stability_tests",
+    "type": "generated_script",
+  },
   "openscreen_unittests": {
     "label": "//chrome/browser/media/router:openscreen_unittests",
     "type": "console_test_launcher",
diff --git a/testing/buildbot/internal.optimization_guide.json b/testing/buildbot/internal.optimization_guide.json
index ba014f3a..c4fddf1 100644
--- a/testing/buildbot/internal.optimization_guide.json
+++ b/testing/buildbot/internal.optimization_guide.json
@@ -253,6 +253,33 @@
         "test": "model_validation_tests",
         "test_id_prefix": "ninja://components/optimization_guide/internal/testing:model_validation_tests/",
         "variant_id": "MODEL_VALIDATION_TRUNK"
+      },
+      {
+        "args": [
+          "--chromedriver",
+          "chromedriver",
+          "--binary",
+          "chrome"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ondevice_stability_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_stability_tests",
+        "test_id_prefix": "ninja://components/optimization_guide/internal/testing:ondevice_stability_tests/",
+        "variant_id": "Intel UHD 630"
       }
     ]
   },
@@ -401,6 +428,32 @@
         "test": "model_validation_tests",
         "test_id_prefix": "ninja://components/optimization_guide/internal/testing:model_validation_tests/",
         "variant_id": "MODEL_VALIDATION_TRUNK"
+      },
+      {
+        "args": [
+          "--chromedriver",
+          "chromedriver",
+          "--binary",
+          "Google Chrome.app/Contents/MacOS/Google Chrome"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ondevice_stability_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_stability_tests",
+        "test_id_prefix": "ninja://components/optimization_guide/internal/testing:ondevice_stability_tests/"
       }
     ]
   },
@@ -549,6 +602,32 @@
         "test": "model_validation_tests",
         "test_id_prefix": "ninja://components/optimization_guide/internal/testing:model_validation_tests/",
         "variant_id": "MODEL_VALIDATION_TRUNK"
+      },
+      {
+        "args": [
+          "--chromedriver",
+          "chromedriver",
+          "--binary",
+          "Google Chrome.app/Contents/MacOS/Google Chrome"
+        ],
+        "merge": {
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "ondevice_stability_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_stability_tests",
+        "test_id_prefix": "ninja://components/optimization_guide/internal/testing:ondevice_stability_tests/"
       }
     ]
   },
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 61a89eb..8449bed 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -3290,6 +3290,11 @@
           },
         }
       },
+      'win11-arm64-dbg-tests': {
+        'swarming': {
+          'shards': 9,
+        },
+      },
     },
   },
   'interactive_ui_tests Lacros version skew testing ash beta': {
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 9f9d564..4b4d34e 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -108,7 +108,11 @@
 
     'android_pie_rel_reduced_capacity_gtests': {
       'android_browsertests': {},
-      'blink_platform_unittests': {},
+      'blink_platform_unittests': {
+        'mixins': [
+          'skia_gold_test',
+        ],
+      },
       'cc_unittests': {},
       'content_browsertests': {
         'swarming': {
@@ -835,7 +839,11 @@
       'base_unittests': {},
       'blink_common_unittests': {},
       'blink_heap_unittests': {},
-      'blink_platform_unittests': {},
+      'blink_platform_unittests': {
+        'mixins': [
+          'skia_gold_test',
+        ],
+      },
       'boringssl_crypto_tests': {},
       'boringssl_ssl_tests': {},
       'capture_unittests': {
@@ -2185,7 +2193,11 @@
       'blink_common_unittests': {},
       'blink_fuzzer_unittests': {},
       'blink_heap_unittests': {},
-      'blink_platform_unittests': {},
+      'blink_platform_unittests': {
+        'mixins': [
+          'skia_gold_test',
+        ],
+      },
       'blink_unittests': {},
       'blink_unittests_v2': {},
       'boringssl_crypto_tests': {},
@@ -4029,6 +4041,9 @@
       'blink_fuzzer_unittests': {},
       'blink_heap_unittests': {},
       'blink_platform_unittests': {
+        'mixins': [
+          'skia_gold_test',
+        ],
         'args': [
           '--test-launcher-bot-mode',
           '--test-launcher-filter-file=testing/buildbot/filters/ios.blink_platform_unittests.filter',
@@ -4537,7 +4552,11 @@
       'app_shell_unittests': {},
       'base_unittests': {},
       'blink_heap_unittests': {},
-      'blink_platform_unittests': {},
+      'blink_platform_unittests': {
+        'mixins': [
+          'skia_gold_test',
+        ],
+      },
       'blink_unittests': {},
       'blink_unittests_v2': {},
       'cc_unittests': {},
@@ -4756,6 +4775,32 @@
       },
     },
 
+    'ondevice_stability_tests': {
+      'ondevice_stability_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',
+        ],
+      },
+    },
+
     'optimization_guide_android_gtests': {
       'optimization_guide_components_unittests': {
         'test': 'components_unittests',
@@ -7882,15 +7927,6 @@
       },
     },
 
-    'model_validation_tests_full': {
-      'model_validation_tests': {
-        'variants': [
-          'MODEL_VALIDATION_BASE',
-          'MODEL_VALIDATION_TRUNK',
-        ],
-      },
-    },
-
     'optimization_guide_linux_gtests': {
       'optimization_guide_gpu_gtests': {
         'variants': [
@@ -7900,11 +7936,35 @@
       'optimization_guide_nogpu_gtests': {},
     },
 
+    'optimization_guide_linux_script_tests': {
+      'model_validation_tests': {
+        'variants': [
+          'MODEL_VALIDATION_BASE',
+          'MODEL_VALIDATION_TRUNK',
+        ],
+      },
+      'ondevice_stability_tests': {
+        'variants': [
+          'INTEL_UHD_630',
+        ],
+      },
+    },
+
     'optimization_guide_mac_gtests': {
       'optimization_guide_gpu_gtests': {},
       'optimization_guide_nogpu_gtests': {},
     },
 
+    'optimization_guide_mac_script_tests': {
+      'model_validation_tests': {
+        'variants': [
+          'MODEL_VALIDATION_BASE',
+          'MODEL_VALIDATION_TRUNK',
+        ],
+      },
+      'ondevice_stability_tests': {},
+    },
+
     'optimization_guide_win_gtests': {
       'optimization_guide_gpu_gtests': {
         'variants': [
@@ -7915,6 +7975,15 @@
       'optimization_guide_nogpu_gtests': {},
     },
 
+    'optimization_guide_win_script_tests': {
+      'model_validation_tests': {
+        'variants': [
+          'MODEL_VALIDATION_BASE',
+          'MODEL_VALIDATION_TRUNK',
+        ],
+      },
+    },
+
     'webview_trichrome_10_cts_tests_gtest': {
       'webview_trichrome_cts_tests': {
         'variants': [
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 4119c17e..8aa7644 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -307,16 +307,16 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'identifier': 'Lacros version skew testing ash canary',
-    'description': 'Run with ash-chrome version 123.0.6282.0',
+    'description': 'Run with ash-chrome version 123.0.6283.0',
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6282.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v123.0.6283.0/test_ash_chrome',
     ],
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v123.0.6282.0',
-          'revision': 'version:123.0.6282.0',
+          'location': 'lacros_version_skew_tests_v123.0.6283.0',
+          'revision': 'version:123.0.6283.0',
         },
       ],
     },
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 1aad3e97..d341a4f3 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -6636,7 +6636,7 @@
         ],
         'test_suites': {
            'gtest_tests': 'optimization_guide_linux_gtests',
-           'isolated_scripts': 'model_validation_tests_full',
+           'isolated_scripts': 'optimization_guide_linux_script_tests',
         },
         'os_type': 'linux',
       },
@@ -6651,7 +6651,7 @@
         ],
         'test_suites': {
            'gtest_tests': 'optimization_guide_mac_gtests',
-           'isolated_scripts': 'model_validation_tests_full',
+           'isolated_scripts': 'optimization_guide_mac_script_tests',
         },
         'os_type': 'mac',
       },
@@ -6666,7 +6666,7 @@
         ],
         'test_suites': {
            'gtest_tests': 'optimization_guide_mac_gtests',
-           'isolated_scripts': 'model_validation_tests_full',
+           'isolated_scripts': 'optimization_guide_mac_script_tests',
         },
         'os_type': 'mac',
       },
@@ -6681,7 +6681,7 @@
         ],
         'test_suites': {
            'gtest_tests': 'optimization_guide_win_gtests',
-           'isolated_scripts': 'model_validation_tests_full',
+           'isolated_scripts': 'optimization_guide_win_script_tests',
         },
         'os_type': 'win',
       },
@@ -6697,7 +6697,7 @@
         ],
         'test_suites': {
            'gtest_tests': 'optimization_guide_win_gtests',
-           'isolated_scripts': 'model_validation_tests_full',
+           'isolated_scripts': 'optimization_guide_win_script_tests',
 
         },
         'os_type': 'win',
diff --git a/testing/libfuzzer/reference.md b/testing/libfuzzer/reference.md
index 37540c3..7f7cd34 100644
--- a/testing/libfuzzer/reference.md
+++ b/testing/libfuzzer/reference.md
@@ -170,6 +170,7 @@
 |------|-------------|
 | max_len | Maximum length of test input. |
 | timeout | Timeout of seconds. Units slower than this value will be reported as bugs. |
+| rss_limit_mb | Memory usage limit in Mb, default 2048. Some Chrome targets, such as Blink, require more than the default to initialize. |
 
 Full list of options can be found at [libFuzzer options] page and by running
 the binary with `-help=1`.
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 610ac94b8..38afe1c 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -10881,10 +10881,12 @@
                         "ReducedNTPTopSpace": "20",
                         "SetUpListCompactedTimeThresholdDays": "0",
                         "enable_default_model": "true",
-                        "variant": "tab-resumption-recent-tab-only"
+                        "variant": "tab-resumption-all-tabs"
                     },
                     "enable_features": [
+                        "IOSParcelTracking",
                         "MagicStack",
+                        "SafetyCheckMagicStack",
                         "SegmentationPlatformIosModuleRanker",
                         "SegmentationPlatformUserVisibleTaskRunner",
                         "TabResumption"
@@ -19631,6 +19633,27 @@
             ]
         }
     ],
+    "UseRecordedBoundsForTiling": [
+        {
+            "platforms": [
+                "android",
+                "android_webview",
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "UseRecordedBoundsForTiling"
+                    ]
+                }
+            ]
+        }
+    ],
     "UseServerPredictionsOnSaveParsing": [
         {
             "platforms": [
diff --git a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
index ab7b35183..40235e6 100644
--- a/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
+++ b/third_party/blink/public/mojom/use_counter/metrics/web_feature.mojom
@@ -3814,9 +3814,9 @@
   kServiceWorkerEventHandlerModifiedAfterInitialization = 4469,
   kAuthorizationCrossOrigin = 4470,
   kCSSColorMixFunction = 4471,
-  kOBSOLETE_CSSColorColorSpecifiedSpace = 4472,
-  kOBSOLETE_CSSColorLabOklab = 4473,
-  kOBSOLETE_CSSColorLchOklch = 4474,
+  kCSSColorColorSpecifiedSpace = 4472,
+  kCSSColorLabOklab = 4473,
+  kCSSColorLchOklch = 4474,
   kOBSOLETE_CreateNSResolverWithNonElements2 = 4475,
   kGetDisplayMediaWithPreferCurrentTabTrue = 4476,
   kFencedFrameConfigAttribute = 4477,
@@ -4196,10 +4196,6 @@
   kSpeculationRulesBrowserPrerenderRule = 4832,
   kFirstPartySharedWorkerSameSiteCookiesNone = 4833,
   kCSSCustomStateDeprecatedSyntax = 4834,
-  kCSSColor_SpaceRGB = 4835,
-  kCSSColor_SpaceRGB_outOfRec2020= 4836,
-  kCSSColor_SpaceOkLxx = 4837,
-  kCSSColor_SpaceOkLxx_OutOfRange = 4838,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/public/mojom/webid/federated_auth_request.mojom b/third_party/blink/public/mojom/webid/federated_auth_request.mojom
index 949de38..fdb621e 100644
--- a/third_party/blink/public/mojom/webid/federated_auth_request.mojom
+++ b/third_party/blink/public/mojom/webid/federated_auth_request.mojom
@@ -19,7 +19,6 @@
 // third_party/blink/public/mojom/devtools/inspector_issue.mojom.
 enum RequestTokenStatus {
   kSuccess,
-  kApprovalDeclined,
   kErrorTooManyRequests,
   kErrorCanceled,
   kError,
diff --git a/third_party/blink/renderer/core/css/properties/css_color_function_parser.cc b/third_party/blink/renderer/core/css/properties/css_color_function_parser.cc
index e6eeb35f..7e621a0 100644
--- a/third_party/blink/renderer/core/css/properties/css_color_function_parser.cc
+++ b/third_party/blink/renderer/core/css/properties/css_color_function_parser.cc
@@ -15,20 +15,6 @@
 
 namespace blink {
 
-namespace {
-
-// Returns true if, when converted to Rec2020 space, all components of `color`
-// are in the interval [-1/255, 256/255].
-bool IsInGamutRec2020(Color color) {
-  const float kEpsilon = 1 / 255.f;
-  color.ConvertToColorSpace(Color::ColorSpace::kRec2020);
-  return -kEpsilon <= color.Param0() && color.Param0() <= 1.f + kEpsilon &&
-         -kEpsilon <= color.Param1() && color.Param1() <= 1.f + kEpsilon &&
-         -kEpsilon <= color.Param2() && color.Param2() <= 1.f + kEpsilon;
-}
-
-}  // namespace
-
 static Color::ColorSpace CSSValueIDToColorSpace(const CSSValueID& id) {
   switch (id) {
     case CSSValueID::kRgb:
@@ -557,36 +543,6 @@
 
   if (is_relative_color_) {
     context.Count(WebFeature::kCSSRelativeColor);
-  } else {
-    switch (color_space_) {
-      case Color::ColorSpace::kSRGB:
-      case Color::ColorSpace::kSRGBLinear:
-      case Color::ColorSpace::kDisplayP3:
-      case Color::ColorSpace::kA98RGB:
-      case Color::ColorSpace::kProPhotoRGB:
-      case Color::ColorSpace::kRec2020:
-        context.Count(WebFeature::kCSSColor_SpaceRGB);
-        if (!IsInGamutRec2020(result)) {
-          context.Count(WebFeature::kCSSColor_SpaceRGB_outOfRec2020);
-        }
-        break;
-      case Color::ColorSpace::kOklab:
-      case Color::ColorSpace::kOklch:
-        context.Count(WebFeature::kCSSColor_SpaceOkLxx);
-        if (!IsInGamutRec2020(result)) {
-          context.Count(WebFeature::kCSSColor_SpaceOkLxx_OutOfRange);
-        }
-        break;
-      case Color::ColorSpace::kXYZD50:
-      case Color::ColorSpace::kXYZD65:
-      case Color::ColorSpace::kLab:
-      case Color::ColorSpace::kLch:
-      case Color::ColorSpace::kSRGBLegacy:
-      case Color::ColorSpace::kHSL:
-      case Color::ColorSpace::kHWB:
-      case Color::ColorSpace::kNone:
-        break;
-    }
   }
 
   return true;
diff --git a/third_party/blink/renderer/core/fetch/fetch_manager.cc b/third_party/blink/renderer/core/fetch/fetch_manager.cc
index 89ce85470..c7575dc 100644
--- a/third_party/blink/renderer/core/fetch/fetch_manager.cc
+++ b/third_party/blink/renderer/core/fetch/fetch_manager.cc
@@ -30,6 +30,7 @@
 #include "services/network/public/mojom/url_loader_factory.mojom-blink.h"
 #include "third_party/blink/public/common/browser_interface_broker_proxy.h"
 #include "third_party/blink/public/common/features.h"
+#include "third_party/blink/public/common/scheme_registry.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
 #include "third_party/blink/public/mojom/loader/code_cache.mojom-blink.h"
 #include "third_party/blink/public/mojom/loader/fetch_later.mojom-blink.h"
@@ -615,7 +616,9 @@
   response_http_status_code_ = response.HttpStatusCode();
 
   if (response.MimeType() == "application/wasm" &&
-      response.CurrentRequestUrl().ProtocolIsInHTTPFamily()) {
+      (response.CurrentRequestUrl().ProtocolIsInHTTPFamily() ||
+       CommonSchemeRegistry::IsExtensionScheme(
+           response.CurrentRequestUrl().Protocol().Ascii()))) {
     // We create a ScriptCachedMetadataHandler for WASM modules.
     cached_metadata_handler_ =
         MakeGarbageCollected<ScriptCachedMetadataHandler>(
diff --git a/third_party/blink/renderer/core/frame/animation_frame_timing_monitor.cc b/third_party/blink/renderer/core/frame/animation_frame_timing_monitor.cc
index ba841e6..08b1dd4 100644
--- a/third_party/blink/renderer/core/frame/animation_frame_timing_monitor.cc
+++ b/third_party/blink/renderer/core/frame/animation_frame_timing_monitor.cc
@@ -464,7 +464,7 @@
                           ? ScriptTimingInfo::InvokerType::kModuleScript
                           : ScriptTimingInfo::InvokerType::kClassicScript,
       .start_time = probe_data.CaptureStartTime(),
-      .source_location = {.url = url}};
+      .source_location = {.url = url, .char_position = 0}};
   if (probe_data.sanitize) {
     pending_script_info_->execution_start_time =
         pending_script_info_->start_time;
@@ -526,7 +526,7 @@
       .url = ToCoreStringWithUndefinedOrNullCheck(isolate, source_location),
       .function_name =
           ToCoreStringWithUndefinedOrNullCheck(isolate, function->GetName()),
-      .start_position = function->GetScriptStartPosition()};
+      .char_position = function->GetScriptStartPosition()};
 }
 
 }  // namespace
diff --git a/third_party/blink/renderer/core/timing/animation_frame_timing_info.h b/third_party/blink/renderer/core/timing/animation_frame_timing_info.h
index fe06fc13..ae5d463 100644
--- a/third_party/blink/renderer/core/timing/animation_frame_timing_info.h
+++ b/third_party/blink/renderer/core/timing/animation_frame_timing_info.h
@@ -34,7 +34,7 @@
   struct ScriptSourceLocation {
     WTF::String url;
     WTF::String function_name;
-    int start_position = 0;
+    int char_position = -1;
   };
 
   ScriptTimingInfo(ExecutionContext* context,
diff --git a/third_party/blink/renderer/core/timing/performance_script_timing.cc b/third_party/blink/renderer/core/timing/performance_script_timing.cc
index 0303082..3d57574 100644
--- a/third_party/blink/renderer/core/timing/performance_script_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_script_timing.cc
@@ -4,6 +4,8 @@
 
 #include "third_party/blink/renderer/core/timing/performance_script_timing.h"
 
+#include <cstdint>
+
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_object_builder.h"
 #include "third_party/blink/renderer/core/frame/dom_window.h"
@@ -159,26 +161,14 @@
   }
 }
 
-WTF::String PerformanceScriptTiming::sourceLocation() const {
-  const ScriptTimingInfo::ScriptSourceLocation& source_location =
-      info_->GetSourceLocation();
-  if (!source_location.url) {
-    return WTF::String("");
-  }
-
-  StringBuilder builder;
-  if (!source_location.function_name.empty()) {
-    builder.Append(source_location.function_name);
-    builder.Append("@");
-  }
-
-  builder.Append(source_location.url);
-  if (source_location.start_position >= 0) {
-    builder.Append(":");
-    builder.AppendNumber(source_location.start_position);
-  }
-
-  return builder.ToString();
+WTF::String PerformanceScriptTiming::sourceURL() const {
+  return info_->GetSourceLocation().url;
+}
+WTF::String PerformanceScriptTiming::sourceFunctionName() const {
+  return info_->GetSourceLocation().function_name;
+}
+int32_t PerformanceScriptTiming::sourceCharPosition() const {
+  return info_->GetSourceLocation().char_position;
 }
 
 PerformanceEntryType PerformanceScriptTiming::EntryTypeEnum() const {
@@ -194,7 +184,9 @@
   builder.AddNumber("forcedStyleAndLayoutDuration",
                     forcedStyleAndLayoutDuration());
   builder.AddNumber("pauseDuration", pauseDuration());
-  builder.AddString("sourceLocation", sourceLocation());
+  builder.AddString("sourceURL", sourceURL());
+  builder.AddString("sourceFunctionName", sourceFunctionName());
+  builder.AddNumber("sourceCharPosition", sourceCharPosition());
 }
 
 void PerformanceScriptTiming::Trace(Visitor* visitor) const {
diff --git a/third_party/blink/renderer/core/timing/performance_script_timing.h b/third_party/blink/renderer/core/timing/performance_script_timing.h
index d6bda6c..62743a2 100644
--- a/third_party/blink/renderer/core/timing/performance_script_timing.h
+++ b/third_party/blink/renderer/core/timing/performance_script_timing.h
@@ -5,6 +5,8 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_PERFORMANCE_SCRIPT_TIMING_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_PERFORMANCE_SCRIPT_TIMING_H_
 
+#include <cstdint>
+
 #include "third_party/blink/renderer/core/dom/dom_high_res_time_stamp.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
 #include "third_party/blink/renderer/core/timing/animation_frame_timing_info.h"
@@ -35,7 +37,9 @@
   DOMHighResTimeStamp forcedStyleAndLayoutDuration() const;
   DOMHighResTimeStamp pauseDuration() const;
   LocalDOMWindow* window() const;
-  WTF::String sourceLocation() const;
+  WTF::String sourceURL() const;
+  WTF::String sourceFunctionName() const;
+  int32_t sourceCharPosition() const;
   const AtomicString& windowAttribution() const;
   AtomicString invokerType() const;
   AtomicString invoker() const;
diff --git a/third_party/blink/renderer/core/timing/performance_script_timing.idl b/third_party/blink/renderer/core/timing/performance_script_timing.idl
index 6d78845..1f2060de 100644
--- a/third_party/blink/renderer/core/timing/performance_script_timing.idl
+++ b/third_party/blink/renderer/core/timing/performance_script_timing.idl
@@ -26,6 +26,8 @@
     readonly attribute DOMHighResTimeStamp forcedStyleAndLayoutDuration;
     readonly attribute DOMHighResTimeStamp pauseDuration;
     readonly attribute Window? window;
-    readonly attribute DOMString sourceLocation;
+    readonly attribute DOMString sourceURL;
+    readonly attribute DOMString sourceFunctionName;
+    readonly attribute long long sourceCharPosition;
     [CallWith=ScriptState, ImplementedAs=toJSONForBinding] object toJSON();
 };
diff --git a/third_party/blink/renderer/modules/ml/ml_trace_unittest.cc b/third_party/blink/renderer/modules/ml/ml_trace_unittest.cc
index fb4fc214..c70c8c2 100644
--- a/third_party/blink/renderer/modules/ml/ml_trace_unittest.cc
+++ b/third_party/blink/renderer/modules/ml/ml_trace_unittest.cc
@@ -86,7 +86,10 @@
       const std::string* trace_type = dict.FindString("ph");
       CHECK(trace_type);
       // Count both the "BEGIN" and "END" traces.
-      if (*trace_type != "E" && *trace_type != "e") {
+      if (*trace_type == "n") {
+        ((event_counts)[*name].first)++;
+        ((event_counts)[*name].second)++;
+      } else if (*trace_type != "E" && *trace_type != "e") {
         ((event_counts)[*name].first)++;
       } else {
         ((event_counts)[*name].second)++;
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 02d329b..590ff47 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -2452,6 +2452,7 @@
     # Required by some image decoder tests.
     "image-decoders/testing/",
     "//third_party/blink/web_tests/images/resources/",
+    "//third_party/blink/web_tests/images/bmp-suite/",
 
     # Required by some font tests.
     "//third_party/blink/web_tests/external/wpt/fonts/",
@@ -2613,7 +2614,7 @@
 }
 
 fuzzer_test("blink_png_decoder_fuzzer") {
-  sources = [ "png_fuzzer.cc" ]
+  sources = [ "image-decoders/png/png_image_decoder_fuzzer.cc" ]
   deps = [
     ":blink_fuzzer_test_support",
     ":platform",
@@ -2625,6 +2626,7 @@
     "//third_party/blink/web_tests/images/png-suite/samples",
     "//third_party/blink/web_tests/images/resources/pngfuzz",
   ]
+  libfuzzer_options = [ "rss_limit_mb=8192" ]
 }
 
 fuzzer_test("blink_jpeg_decoder_fuzzer") {
diff --git a/third_party/blink/renderer/platform/image-decoders/BUILD.gn b/third_party/blink/renderer/platform/image-decoders/BUILD.gn
index 725e2265..0556105b 100644
--- a/third_party/blink/renderer/platform/image-decoders/BUILD.gn
+++ b/third_party/blink/renderer/platform/image-decoders/BUILD.gn
@@ -122,18 +122,15 @@
   }
 }
 
-if (fuzztest_supported) {
-  test("bmp_image_decoder_fuzztests") {
-    fuzztests = [ "BMPImageDecoderFuzzer.Decode" ]
-
-    sources = [ "bmp/bmp_image_decoder_fuzzer.cc" ]
-
-    deps = [
-      ":image_decoders",
-      "//base",
-      "//third_party/blink/renderer/platform:image_headers",
-      "//third_party/blink/renderer/platform/wtf",
-      "//third_party/fuzztest:fuzztest_gtest_main",
-    ]
-  }
+fuzzer_test("blink_bmp_image_decoder_fuzzer") {
+  sources = [ "bmp/bmp_image_decoder_fuzzer.cc" ]
+  deps = [
+    ":image_decoders",
+    "//base",
+    "//third_party/blink/renderer/platform",
+    "//third_party/blink/renderer/platform:blink_fuzzer_test_support",
+    "//third_party/blink/renderer/platform:image_headers",
+    "//third_party/blink/renderer/platform/wtf",
+  ]
+  seed_corpuses = [ "//third_party/blink/web_tests/images/bmp-suite/good" ]
 }
diff --git a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder_test.cc b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder_test.cc
index 102f14079..ed7fe63 100644
--- a/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder_test.cc
+++ b/third_party/blink/renderer/platform/image-decoders/avif/avif_image_decoder_test.cc
@@ -870,41 +870,65 @@
 
 }  // namespace
 
-TEST(AnimatedAVIFTests, ValidImages) {
-  // star-animated-8bpc.avif, star-animated-10bpc.avif, and
-  // star-animated-12bpc.avif contain an EditListBox whose `flags` field is
-  // equal to 0, meaning the edit list is not repeated. Therefore their
-  // `expected_repetition_count` is 0.
-  TestByteByByteDecode(&CreateAVIFDecoder,
-                       "/images/resources/avif/star-animated-8bpc.avif", 5u, 0);
-  TestByteByByteDecode(
-      &CreateAVIFDecoder,
-      "/images/resources/avif/star-animated-8bpc-with-alpha.avif", 5u,
-      kAnimationLoopInfinite);
-  TestByteByByteDecode(&CreateAVIFDecoder,
-                       "/images/resources/avif/star-animated-10bpc.avif", 5u,
-                       0);
-  TestByteByByteDecode(
-      &CreateAVIFDecoder,
-      "/images/resources/avif/star-animated-10bpc-with-alpha.avif", 5u,
-      kAnimationLoopInfinite);
-  TestByteByByteDecode(&CreateAVIFDecoder,
-                       "/images/resources/avif/star-animated-12bpc.avif", 5u,
-                       0);
-  TestByteByByteDecode(
-      &CreateAVIFDecoder,
-      "/images/resources/avif/star-animated-12bpc-with-alpha.avif", 5u,
-      kAnimationLoopInfinite);
-  TestByteByByteDecode(
-      &CreateAVIFDecoder,
-      "/images/resources/avif/star-animated-8bpc-1-repetition.avif", 5u, 1);
-  TestByteByByteDecode(
-      &CreateAVIFDecoder,
-      "/images/resources/avif/star-animated-8bpc-10-repetition.avif", 5u, 10);
-  TestByteByByteDecode(
-      &CreateAVIFDecoder,
-      "/images/resources/avif/star-animated-8bpc-infinite-repetition.avif", 5u,
-      kAnimationLoopInfinite);
+struct AVIFImageParam {
+  const char* path;
+  size_t expected_frame_count;
+  int expected_repetition_count;
+};
+
+constexpr AVIFImageParam kAnimatedTestParams[] = {
+    // star-animated-8bpc.avif, star-animated-10bpc.avif, and
+    // star-animated-12bpc.avif contain an EditListBox whose `flags` field is
+    // equal to 0, meaning the edit list is not repeated. Therefore their
+    // `expected_repetition_count` is 0.
+    {"/images/resources/avif/star-animated-8bpc.avif", 5u, 0},
+    {"/images/resources/avif/star-animated-8bpc-with-alpha.avif", 5u,
+     kAnimationLoopInfinite},
+    {"/images/resources/avif/star-animated-10bpc.avif", 5u, 0},
+    {"/images/resources/avif/star-animated-10bpc-with-alpha.avif", 5u,
+     kAnimationLoopInfinite},
+    {"/images/resources/avif/star-animated-12bpc.avif", 5u, 0},
+    {"/images/resources/avif/star-animated-12bpc-with-alpha.avif", 5u,
+     kAnimationLoopInfinite},
+    {"/images/resources/avif/star-animated-8bpc-1-repetition.avif", 5u, 1},
+    {"/images/resources/avif/star-animated-8bpc-10-repetition.avif", 5u, 10},
+    {"/images/resources/avif/star-animated-8bpc-infinite-repetition.avif", 5u,
+     kAnimationLoopInfinite},
+};
+
+constexpr AVIFImageParam kStaticTestParams[] = {
+    {"/images/resources/avif/red-at-12-oclock-with-color-profile-lossy.avif", 1,
+     kAnimationNone},
+    {"/images/resources/avif/red-at-12-oclock-with-color-profile-8bpc.avif", 1,
+     kAnimationNone},
+    {"/images/resources/avif/red-at-12-oclock-with-color-profile-10bpc.avif", 1,
+     kAnimationNone},
+    {"/images/resources/avif/red-at-12-oclock-with-color-profile-12bpc.avif", 1,
+     kAnimationNone},
+    {"/images/resources/avif/tiger_3layer_1res.avif", 1, kAnimationNone},
+    {"/images/resources/avif/tiger_3layer_3res.avif", 1, kAnimationNone},
+    {"/images/resources/avif/tiger_420_8b_grid1x13.avif", 1, kAnimationNone},
+    {"/images/resources/avif/dice_444_10b_grid4x3.avif", 1, kAnimationNone},
+    {"/images/resources/avif/gracehopper_422_12b_grid2x4.avif", 1,
+     kAnimationNone},
+    {"/images/resources/avif/small-with-gainmap-adobe.avif", 1, kAnimationNone},
+    {"/images/resources/avif/small-with-gainmap-iso.avif", 1, kAnimationNone},
+};
+
+using AVIFValidImagesTest = ::testing::TestWithParam<AVIFImageParam>;
+
+INSTANTIATE_TEST_SUITE_P(AnimatedAVIF,
+                         AVIFValidImagesTest,
+                         ::testing::ValuesIn(kAnimatedTestParams));
+
+INSTANTIATE_TEST_SUITE_P(StaticAVIF,
+                         AVIFValidImagesTest,
+                         ::testing::ValuesIn(kStaticTestParams));
+
+TEST_P(AVIFValidImagesTest, ByteByByteDecode) {
+  TestByteByByteDecode(&CreateAVIFDecoder, GetParam().path,
+                       GetParam().expected_frame_count,
+                       GetParam().expected_repetition_count);
 }
 
 TEST(AnimatedAVIFTests, HasMultipleSubImages) {
@@ -974,47 +998,6 @@
       ErrorPhase::kDecode);
 }
 
-TEST(StaticAVIFTests, ValidImages) {
-  TestByteByByteDecode(
-      &CreateAVIFDecoder,
-      "/images/resources/avif/red-at-12-oclock-with-color-profile-lossy.avif",
-      1, kAnimationNone);
-  TestByteByByteDecode(
-      &CreateAVIFDecoder,
-      "/images/resources/avif/red-at-12-oclock-with-color-profile-8bpc.avif", 1,
-      kAnimationNone);
-  TestByteByByteDecode(
-      &CreateAVIFDecoder,
-      "/images/resources/avif/red-at-12-oclock-with-color-profile-10bpc.avif",
-      1, kAnimationNone);
-  TestByteByByteDecode(
-      &CreateAVIFDecoder,
-      "/images/resources/avif/red-at-12-oclock-with-color-profile-12bpc.avif",
-      1, kAnimationNone);
-  TestByteByByteDecode(&CreateAVIFDecoder,
-                       "/images/resources/avif/tiger_3layer_1res.avif", 1,
-                       kAnimationNone);
-  TestByteByByteDecode(&CreateAVIFDecoder,
-                       "/images/resources/avif/tiger_3layer_3res.avif", 1,
-                       kAnimationNone);
-  TestByteByByteDecode(&CreateAVIFDecoder,
-                       "/images/resources/avif/tiger_420_8b_grid1x13.avif", 1,
-                       kAnimationNone);
-  TestByteByByteDecode(&CreateAVIFDecoder,
-                       "/images/resources/avif/dice_444_10b_grid4x3.avif", 1,
-                       kAnimationNone);
-  TestByteByByteDecode(
-      &CreateAVIFDecoder,
-      "/images/resources/avif/gracehopper_422_12b_grid2x4.avif", 1,
-      kAnimationNone);
-  TestByteByByteDecode(&CreateAVIFDecoder,
-                       "/images/resources/avif/small-with-gainmap-adobe.avif",
-                       1, kAnimationNone);
-  TestByteByByteDecode(&CreateAVIFDecoder,
-                       "/images/resources/avif/small-with-gainmap-iso.avif", 1,
-                       kAnimationNone);
-}
-
 TEST(StaticAVIFTests, GetAdobeGainmapInfoAndData) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitWithFeatures(
diff --git a/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder_fuzzer.cc b/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder_fuzzer.cc
index b645cd08..320070c 100644
--- a/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder_fuzzer.cc
+++ b/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder_fuzzer.cc
@@ -2,53 +2,45 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// Compile with:
+// $ gn gen out/Fuzz '--args=use_libfuzzer=true is_asan=true is_debug=false \
+//       is_ubsan_security=true use_remoteexec=true' --check
+// $ ninja -C out/Fuzz blink_bmp_image_decoder_fuzzer
+//
+// Run with:
+// $ out/Fuzz/blink_bmp_image_decoder_fuzzer \
+//       third_party/blink/web_tests/images/bmp-suite/good/
+//
+// Alternatively, it can be run with:
+// $ out/Fuzz/blink_bmp_image_decoder_fuzzer \
+//       ~/another_dir_to_store_corpus \
+//       third_party/blink/web_tests/images/bmp-suite/good/
+//
+// In this case, the fuzzer will read both passed-in directories, but all newly-
+// generated testcases will go into ~/another_dir_to_store_corpus.
+
 #include "third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder.h"
 
 #include "base/memory/scoped_refptr.h"
 #include "third_party/blink/renderer/platform/graphics/color_behavior.h"
 #include "third_party/blink/renderer/platform/image-decoders/image_decoder.h"
-#include "third_party/blink/renderer/platform/wtf/allocator/partitions.h"
+#include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h"
 #include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
-#include "third_party/fuzztest/src/fuzztest/fuzztest.h"
 
-#include <memory>
-#include <string_view>
-#include <tuple>
-#include <vector>
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  using blink::BMPImageDecoder;
+  using blink::ColorBehavior;
+  using blink::ImageDecoder;
+  using WTF::SharedBuffer;
 
-namespace blink {
-namespace {
+  static blink::BlinkFuzzerTestSupport test_support;
 
-// This is a valid 1x1 BMP which displays a red pixel.
-constexpr char kBitmapFile[] =
-    "\x42\x4d\x1e\x00\x00\x00\x00\x00\x00\x00\x1a\x00"
-    "\x00\x00\x0c\x00\x00\x00\x01\x00\x01\x00\x01\x00"
-    "\x18\x00\x00\x00\xff\x00";
+  scoped_refptr<SharedBuffer> buf = SharedBuffer::Create(data, size);
 
-std::unique_ptr<ImageDecoder> CreateBMPDecoder() {
-  return std::make_unique<BMPImageDecoder>(
-      ImageDecoder::kAlphaNotPremultiplied, ColorBehavior::kTransformToSRGB,
-      ImageDecoder::kNoDecodedImageByteLimit);
+  BMPImageDecoder decoder{ImageDecoder::kAlphaNotPremultiplied,
+                          ColorBehavior::kTransformToSRGB,
+                          ImageDecoder::kNoDecodedImageByteLimit};
+  decoder.SetData(buf, /*all_data_received=*/true);
+  decoder.DecodeFrameBufferAtIndex(0);
+  return 0;
 }
-
-void Decode(std::string_view input) {
-  scoped_refptr<SharedBuffer> data =
-      SharedBuffer::Create(input.data(), input.size());
-
-  std::unique_ptr<ImageDecoder> decoder = CreateBMPDecoder();
-  decoder->SetData(data.get(), /*all_data_received=*/true);
-  decoder->DecodeFrameBufferAtIndex(0);
-}
-
-FUZZ_TEST(BMPImageDecoderFuzzer, Decode)
-    .WithSeeds([]() -> std::vector<std::tuple<std::string_view>> {
-      WTF::Partitions::Initialize();
-
-      // We need to explicitly pass a size to the std::string_view constructor,
-      // because the bitmap data contains zero bytes in the middle; these would
-      // terminate the string.
-      return {{std::string_view(kBitmapFile, std::size(kBitmapFile))}};
-    });
-
-}  // namespace
-}  // namespace blink
diff --git a/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder_test.cc b/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder_test.cc
index 6c867a7..1d4c0bb 100644
--- a/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder_test.cc
+++ b/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder_test.cc
@@ -5,9 +5,12 @@
 #include "third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_decoder.h"
 
 #include <memory>
+
+#include "base/strings/stringprintf.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/platform/image-decoders/image_decoder_base_test.h"
 #include "third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.h"
+#include "third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.h"
 #include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
 
 namespace blink {
@@ -111,6 +114,107 @@
   EXPECT_FALSE(decoder->Failed());
 }
 
+TEST(BMPImageDecoderTest, VerifyBMPSuite) {
+  struct BMPSuiteEntry {
+    const char* dir;
+    const char* bmp;
+    const char* png;
+  };
+  static constexpr BMPSuiteEntry kBMPSuiteEntries[] = {
+      {"good", "pal1", "pal1"},
+      {"good", "pal1wb", "pal1"},
+      {"good", "pal1bg", "pal1bg"},
+      {"good", "pal4", "pal4"},
+      {"good", "pal4gs", "pal4gs"},
+      {"good", "pal4rle", "pal4"},
+      {"good", "pal8", "pal8"},
+      {"good", "pal8-0", "pal8"},
+      {"good", "pal8gs", "pal8gs"},
+      {"good", "pal8rle", "pal8"},
+      {"good", "pal8w126", "pal8w126"},
+      {"good", "pal8w125", "pal8w125"},
+      {"good", "pal8w124", "pal8w124"},
+      {"good", "pal8topdown", "pal8"},
+      {"good", "pal8nonsquare", "pal8nonsquare-e"},
+      {"good", "pal8os2", "pal8"},
+      {"good", "pal8v5", "pal8"},
+      {"good", "rgb16", "rgb16"},
+      {"good", "rgb16bfdef", "rgb16"},
+      {"good", "rgb16-565", "rgb16-565"},
+      {"good", "rgb16-565pal", "rgb16-565"},
+      {"good", "rgb24", "rgb24"},
+      {"good", "rgb24pal", "rgb24"},
+      {"good", "rgb32", "rgb24"},
+      {"good", "rgb32bfdef", "rgb24"},
+      {"good", "rgb32bf", "rgb24"},
+
+      // The following "good" image is not included because our decoder puts a
+      // slight color tinge in the result. This isn't visible to the naked eye,
+      // but it is not pixel-perfect to the reference. Despite being in the
+      // "good" category, the test description states "not sure that the gamma
+      // and chromaticity values in this file are sensible, because I can’t find
+      // any detailed documentation of them" so a slight deviation seems fine.
+      // {"good/pal8v4", "pal8"},
+  };
+
+  for (const BMPSuiteEntry& entry : kBMPSuiteEntries) {
+    // Load the BMP file under test.
+    std::string bmp_path =
+        base::StringPrintf("/images/bmp-suite/%s/%s.bmp", entry.dir, entry.bmp);
+    scoped_refptr<SharedBuffer> data = ReadFile(bmp_path.c_str());
+    ASSERT_NE(data.get(), nullptr) << "unable to load '" << bmp_path << "'";
+    ASSERT_FALSE(data->empty());
+
+    std::unique_ptr<ImageDecoder> decoder = CreateBMPDecoder();
+    decoder->SetData(data.get(), /*all_data_received=*/true);
+    ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0);
+    ASSERT_EQ(ImageFrame::kFrameComplete, frame->GetStatus());
+    ASSERT_FALSE(decoder->Failed());
+
+    // Load the PNG reference for this file.
+    std::string png_path =
+        base::StringPrintf("/images/bmp-suite/reference/%s.png", entry.png);
+    scoped_refptr<SharedBuffer> reference_data = ReadFile(png_path.c_str());
+    ASSERT_NE(reference_data.get(), nullptr)
+        << "unable to load '" << png_path << "'";
+    ASSERT_FALSE(reference_data->empty());
+
+    PNGImageDecoder png_decoder{ImageDecoder::kAlphaNotPremultiplied,
+                                ImageDecoder::kDefaultBitDepth,
+                                ColorBehavior::kTransformToSRGB,
+                                ImageDecoder::kNoDecodedImageByteLimit};
+    png_decoder.SetData(reference_data.get(), /*all_data_received=*/true);
+    ImageFrame* reference_frame = png_decoder.DecodeFrameBufferAtIndex(0);
+    ASSERT_EQ(ImageFrame::kFrameComplete, reference_frame->GetStatus());
+    ASSERT_FALSE(png_decoder.Failed());
+
+    // Compare the ImageFrames.
+    // TODO: https://crbug.com/1524420 - use Skia Gold for pixel diffing
+    ASSERT_EQ(frame->GetPixelFormat(), ImageFrame::kN32);
+    ASSERT_EQ(reference_frame->GetPixelFormat(), ImageFrame::kN32);
+    ASSERT_EQ(frame->Bitmap().width(), reference_frame->Bitmap().width());
+    ASSERT_EQ(frame->Bitmap().height(), reference_frame->Bitmap().height());
+
+    for (int y = frame->Bitmap().height() - 1; y >= 0; --y) {
+      for (int x = frame->Bitmap().width() - 1; x >= 0; --x) {
+        ImageFrame::PixelData* pixel_rgba = frame->GetAddr(x, y);
+        ImageFrame::PixelData* reference_rgba = reference_frame->GetAddr(x, y);
+        if (*pixel_rgba != *reference_rgba) {
+          ADD_FAILURE() << base::StringPrintf(
+              "%s: pixel mismatch at %d, %d - RGBA in reference "
+              "[%02X%02X%02X%02X] vs actual [%02X%02X%02X%02X]",
+              bmp_path.c_str(), x, y, SkGetPackedR32(*reference_rgba),
+              SkGetPackedG32(*reference_rgba), SkGetPackedB32(*reference_rgba),
+              SkGetPackedA32(*reference_rgba), SkGetPackedR32(*pixel_rgba),
+              SkGetPackedG32(*pixel_rgba), SkGetPackedB32(*pixel_rgba),
+              SkGetPackedA32(*pixel_rgba));
+          x = y = 0;  // Only report the first pixel mismatch.
+        }
+      }
+    }
+  }
+}
+
 class BMPImageDecoderCorpusTest : public ImageDecoderBaseTest {
  public:
   BMPImageDecoderCorpusTest() : ImageDecoderBaseTest("bmp") {}
diff --git a/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_fuzzer.cc b/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_fuzzer.cc
new file mode 100644
index 0000000..a1f9184
--- /dev/null
+++ b/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_fuzzer.cc
@@ -0,0 +1,56 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Compile with:
+// gn gen out/Fuzz '--args=use_libfuzzer=true is_asan=true
+// is_debug=false is_ubsan_security=true' --check
+// ninja -C out/Fuzz blink_png_decoder_fuzzer
+//
+// Run with:
+// ./out/Fuzz/blink_png_decoder_fuzzer
+// third_party/blink/web_tests/images/resources/pngfuzz
+//
+// Alternatively, it can be run with:
+// ./out/Fuzz/blink_png_decoder_fuzzer ~/another_dir_to_store_corpus
+// third_party/blink/web_tests/images/resources/pngfuzz
+//
+// so the fuzzer will read both directories passed, but all new generated
+// testcases will go into ~/another_dir_to_store_corpus
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <memory>
+
+#include "third_party/blink/renderer/platform/graphics/color_behavior.h"
+#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h"
+#include "third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.h"
+#include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h"
+#include "third_party/blink/renderer/platform/wtf/shared_buffer.h"
+
+namespace blink {
+
+std::unique_ptr<ImageDecoder> CreatePNGDecoder() {
+  // TODO(b/323934468): Initialize decoder settings dynamically using fuzzer
+  // input
+  return std::make_unique<PNGImageDecoder>(
+      ImageDecoder::kAlphaPremultiplied, ImageDecoder::kDefaultBitDepth,
+      ColorBehavior::kTransformToSRGB, ImageDecoder::kNoDecodedImageByteLimit);
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  static BlinkFuzzerTestSupport test_support;
+  auto buffer = SharedBuffer::Create(data, size);
+  auto decoder = CreatePNGDecoder();
+  static constexpr bool kAllDataReceived = true;
+  decoder->SetData(buffer.get(), kAllDataReceived);
+  for (wtf_size_t frame = 0; frame < decoder->FrameCount(); ++frame) {
+    decoder->DecodeFrameBufferAtIndex(frame);
+    if (decoder->Failed())
+      return 0;
+  }
+  return 0;
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/cached_metadata_handler.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/cached_metadata_handler.cc
index db165ee4..508478d0 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/cached_metadata_handler.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/cached_metadata_handler.cc
@@ -116,8 +116,9 @@
     const String& cache_storage_name,
     const uint8_t* data,
     size_t size) {
-  if (!code_cache_host)
+  if (!code_cache_host) {
     return;
+  }
   if (cache_storage_name.IsNull()) {
     code_cache_host->get()->DidGenerateCacheableMetadata(
         code_cache_type, KURL(url), response_time,
diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender.cc
index 29052a7..5b2e0bb 100644
--- a/third_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/resource_request_sender.cc
@@ -180,8 +180,7 @@
   // The only case where it's easy to skip a kEmpty request is when a content
   // equality check is required, because only ScriptResource supports that
   // requirement.
-  if (request.destination == network::mojom::RequestDestination::kEmpty &&
-      !should_use_source_hash) {
+  if (request.destination == network::mojom::RequestDestination::kEmpty) {
     return true;
   }
   return false;
diff --git a/third_party/blink/renderer/platform/png_fuzzer.cc b/third_party/blink/renderer/platform/png_fuzzer.cc
deleted file mode 100644
index 7f05965..0000000
--- a/third_party/blink/renderer/platform/png_fuzzer.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2016 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// TODO (scroggo): Move this to
-// third_party/blink/renderer/platform/image-decoders ?
-
-// Compile with:
-// gn gen out/Fuzz '--args=use_libfuzzer=true is_asan=true
-// is_debug=false is_ubsan_security=true' --check
-// ninja -C out/Fuzz blink_png_decoder_fuzzer
-//
-// Run with:
-// ./out/Fuzz/blink_png_decoder_fuzzer
-// third_party/blink/web_tests/images/resources/pngfuzz
-//
-// Alternatively, it can be run with:
-// ./out/Fuzz/blink_png_decoder_fuzzer ~/another_dir_to_store_corpus
-// third_party/blink/web_tests/images/resources/pngfuzz
-//
-// so the fuzzer will read both directories passed, but all new generated
-// testcases will go into ~/another_dir_to_store_corpus
-//
-// For more details, see
-// https://chromium.googlesource.com/chromium/src/+/main/testing/libfuzzer/README.md
-
-#include <memory>
-
-#include "third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.h"
-#include "third_party/blink/renderer/platform/testing/blink_fuzzer_test_support.h"
-
-namespace blink {
-
-std::unique_ptr<ImageDecoder> CreateDecoder(
-    ImageDecoder::AlphaOption alpha_option) {
-  return std::make_unique<PNGImageDecoder>(
-      alpha_option, ImageDecoder::kDefaultBitDepth,
-      ColorBehavior::kTransformToSRGB, ImageDecoder::kNoDecodedImageByteLimit);
-}
-
-int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  static BlinkFuzzerTestSupport test_support = BlinkFuzzerTestSupport();
-  auto buffer = SharedBuffer::Create(data, size);
-  // TODO (scroggo): Also test ImageDecoder::AlphaNotPremultiplied?
-  auto decoder = CreateDecoder(ImageDecoder::kAlphaPremultiplied);
-  const bool kAllDataReceived = true;
-  decoder->SetData(buffer.get(), kAllDataReceived);
-  decoder->FrameCount();
-  if (decoder->Failed())
-    return 0;
-  for (size_t frame = 0; frame < decoder->FrameCount(); frame++) {
-    decoder->DecodeFrameBufferAtIndex(frame);
-    if (decoder->Failed())
-      return 0;
-  }
-  return 0;
-}
-
-}  // namespace blink
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-  return blink::LLVMFuzzerTestOneInput(data, size);
-}
diff --git a/third_party/blink/tools/blinkpy/w3c/import_notifier.py b/third_party/blink/tools/blinkpy/w3c/import_notifier.py
index e778af4..6a09bf0a 100644
--- a/third_party/blink/tools/blinkpy/w3c/import_notifier.py
+++ b/third_party/blink/tools/blinkpy/w3c/import_notifier.py
@@ -35,8 +35,8 @@
 GITHUB_COMMIT_PREFIX = WPT_GH_URL + 'commit/'
 SHORT_GERRIT_PREFIX = 'https://crrev.com/c/'
 
-USE_BUGANIZER = False
-BUGANIZER_WPT_COMPONENT = "1415957"
+USE_BUGANIZER = True
+BUGANIZER_WPT_COMPONENT = '1456176'
 
 
 class ImportNotifier:
diff --git a/third_party/blink/web_tests/external/DIR_METADATA b/third_party/blink/web_tests/external/DIR_METADATA
index 0c8c010..0ad3c499 100644
--- a/third_party/blink/web_tests/external/DIR_METADATA
+++ b/third_party/blink/web_tests/external/DIR_METADATA
@@ -1,5 +1,4 @@
-monorail {
-  component: "Blink>Infra>Ecosystem"
+buganizer_public {
+  component_id: 1456176
 }
 team_email: "ecosystem-infra@chromium.org"
-
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index fdacad5..4b37a90 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -378,6 +378,13 @@
        {}
       ]
      ],
+     "remaining-invalid-objects.html": [
+      "fe117040665cc04041b70af3dcfc3ef593923b76",
+      [
+       null,
+       {}
+      ]
+     ],
      "removed-from-flat-tree.html": [
       "bc5726fed367a22b551fd3e8e00329a35e822fe8",
       [
@@ -295053,6 +295060,10 @@
      "f1c2a17f287945caa185475f439de706f11d56b7",
      []
     ],
+    "console-log-symbol.any.shadowrealm-expected.txt": [
+     "f1c2a17f287945caa185475f439de706f11d56b7",
+     []
+    ],
     "console-tests-historical.any.shadowrealm-expected.txt": [
      "f1c2a17f287945caa185475f439de706f11d56b7",
      []
@@ -383189,10 +383200,6 @@
       ]
      }
     },
-    "exif-chunk-expected.txt": [
-     "b9caa68bebc7717d8dcbed5c4a9974617597b9a5",
-     []
-    ],
     "support": {
      "cicp-display-p3.png": [
       "8fa0ce2123c4f876a71b1ca80e04931614f3b87f",
@@ -398682,7 +398689,7 @@
      []
     ],
     "RTCConfiguration-iceServers-expected.txt": [
-     "36da29c385bebb933d8be144dd8159297b1db217",
+     "01eaf2bfe8b46ab1ed8234690e4b562b5e838c79",
      []
     ],
     "RTCDTMFSender-helper.js": [
@@ -429752,6 +429759,42 @@
       {}
      ]
     ],
+    "console-log-symbol.any.js": [
+     "a2facb6c64e86428383260735ed2df3e88c2c809",
+     [
+      "console/console-log-symbol.any.html",
+      {
+       "script_metadata": [
+        [
+         "global",
+         "window,dedicatedworker,shadowrealm"
+        ]
+       ]
+      }
+     ],
+     [
+      "console/console-log-symbol.any.shadowrealm.html",
+      {
+       "script_metadata": [
+        [
+         "global",
+         "window,dedicatedworker,shadowrealm"
+        ]
+       ]
+      }
+     ],
+     [
+      "console/console-log-symbol.any.worker.html",
+      {
+       "script_metadata": [
+        [
+         "global",
+         "window,dedicatedworker,shadowrealm"
+        ]
+       ]
+      }
+     ]
+    ],
     "console-namespace-object-class-string.any.js": [
      "d3ff7f7d07636154080f4d24106e1a6703c37dc4",
      [
@@ -670797,7 +670840,7 @@
      ]
     ],
     "RTCConfiguration-iceServers.html": [
-     "1893ba02f3042abe14db2cce50903f917b9280ff",
+     "d344cce9f862a07034aeaeb96ab4012f0bab6eca",
      [
       null,
       {}
diff --git a/third_party/blink/web_tests/external/wpt/console/console-log-symbol.any.js b/third_party/blink/web_tests/external/wpt/console/console-log-symbol.any.js
new file mode 100644
index 0000000..a2facb6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/console/console-log-symbol.any.js
@@ -0,0 +1,10 @@
+// META: global=window,dedicatedworker,shadowrealm
+"use strict";
+// https://console.spec.whatwg.org/
+
+test(() => {
+    console.log(Symbol());
+    console.log(Symbol("abc"));
+    console.log(Symbol.for("def"));
+    console.log(Symbol.isConcatSpreadable);
+}, "Logging a symbol doesn't throw");
diff --git a/third_party/blink/web_tests/external/wpt/console/console-log-symbol.any.shadowrealm-expected.txt b/third_party/blink/web_tests/external/wpt/console/console-log-symbol.any.shadowrealm-expected.txt
new file mode 100644
index 0000000..f1c2a17
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/console/console-log-symbol.any.shadowrealm-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+Harness Error. harness_status.status = 1 , harness_status.message = Unhandled rejection: ShadowRealm is not defined
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/long-animation-frame/tentative/loaf-source-location-redirect.html b/third_party/blink/web_tests/external/wpt/long-animation-frame/tentative/loaf-source-location-redirect.html
index a6bfd68..c0bb96b1 100644
--- a/third_party/blink/web_tests/external/wpt/long-animation-frame/tentative/loaf-source-location-redirect.html
+++ b/third_party/blink/web_tests/external/wpt/long-animation-frame/tentative/loaf-source-location-redirect.html
@@ -50,22 +50,20 @@
       }, script => script.duration >= VERY_LONG_FRAME_DURATION - 5, t);
 
       const result =
-        script.sourceLocation.startsWith(postRedirectURL) ? "post-redirect" :
-        script.sourceLocation.startsWith(preRedirectURL) ? "pre-redirect" :
-        script.sourceLocation === "" ?
+        script.sourceURL.startsWith(postRedirectURL) ? "post-redirect" :
+        script.sourceURL.startsWith(preRedirectURL) ? "pre-redirect" :
+        script.sourceURL === "" ?
         "empty" : "other";
 
-      assert_not_equals(result, "other", `Unexpected source location ${script.sourceLocation}`);
+      assert_not_equals(result, "other", `Unexpected source location ${script.sourceURL}`);
       if (!cors)
         assert_equals(script.executionStart, script.startTime, "Opaque scripts should hide execution start time");
 
       if (cors) {
         assert_not_equals(result, "empty", "CORS-ok scripts should expose sourceLocation");
       } else {
-        const char_index = result === "empty" ? 0 :
-          parseInt(script.sourceLocation.match(/\:(\d+)$/)?.[1] ?? "0");
         assert_not_equals(result, "post-redirect", "No-CORS classic scripts should not expose post-redirect URL");
-        assert_equals(char_index, 0, "No-CORS classic scripts should not expose character index");
+        assert_equals(script.sourceCharPosition, type === "script-block" ? 0 : -1, "No-CORS classic scripts should not expose character index");
       }
     }, `Test ${type} with ${scriptType}`);
   }
diff --git a/third_party/blink/web_tests/external/wpt/long-animation-frame/tentative/loaf-source-location.html b/third_party/blink/web_tests/external/wpt/long-animation-frame/tentative/loaf-source-location.html
index cdd4bb4b..51e8565 100644
--- a/third_party/blink/web_tests/external/wpt/long-animation-frame/tentative/loaf-source-location.html
+++ b/third_party/blink/web_tests/external/wpt/long-animation-frame/tentative/loaf-source-location.html
@@ -11,16 +11,15 @@
 <div id="log"></div>
 <script>
 
-const source_location_regex = /^([^@]+\@)?https?\:\/\/[^\/]+[^\:]+\:\d*$/;
-
 promise_test(async t => {
   const [entry, script] = await expect_long_frame_with_script(() => {
     requestAnimationFrame(function non_bound_function() {
       busy_wait();
     });
   }, script => script.invoker === "FrameRequestCallback", t);
-  assert_true(script.sourceLocation?.startsWith("non_bound_function"));
-  assert_regexp_match(script.sourceLocation, source_location_regex);
+  assert_equals(script.sourceURL, location.href);
+  assert_equals(script.sourceFunctionName, "non_bound_function");
+  assert_greater_than(script.sourceCharPosition, 0);
 }, "Source location should be extracted from non-bound functions");
 
 promise_test(async t => {
@@ -30,8 +29,9 @@
       busy_wait();
     }).bind(object));
   }, script => script.invoker === "FrameRequestCallback", t);
-  assert_true(script.sourceLocation?.startsWith("my_bound_function"));
-  assert_regexp_match(script.sourceLocation, source_location_regex);
+  assert_equals(script.sourceURL, location.href);
+  assert_equals(script.sourceFunctionName, "my_bound_function");
+  assert_greater_than(script.sourceCharPosition, 0);
 }, "Source location should be extracted from bound functions");
 
 promise_test(async t => {
@@ -39,9 +39,8 @@
     t.step_timeout(function my_timeout() {
       busy_wait();
     });
-  }, script => script.invoker === "TimerHandler:setTimeout" && script.sourceLocation, t );
-  assert_true(script.sourceLocation.includes("testharness.js"));
-  assert_regexp_match(script.sourceLocation, source_location_regex);
+  }, script => script.invoker === "TimerHandler:setTimeout" && script.sourceURL, t );
+  assert_true(script.sourceURL.includes("testharness.js"));
 }, "Source location should be extracted for setTimeout");
 
 promise_test(async t => {
@@ -51,7 +50,7 @@
     scriptElement.src = scriptLocation;
     document.body.appendChild(scriptElement);
   }, script => script.invoker === "Window.fetch.then", t);
-  assert_true(script.sourceLocation?.includes("promise-generates-loaf.js"));
+  assert_true(script.sourceURL.includes("promise-generates-loaf.js"));
 }, "Source location should be extracted for promises");
 
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/long-animation-frame/tentative/loaf-stream-source-location.html b/third_party/blink/web_tests/external/wpt/long-animation-frame/tentative/loaf-stream-source-location.html
index 79fb418f..0fd3085 100644
--- a/third_party/blink/web_tests/external/wpt/long-animation-frame/tentative/loaf-stream-source-location.html
+++ b/third_party/blink/web_tests/external/wpt/long-animation-frame/tentative/loaf-stream-source-location.html
@@ -11,8 +11,6 @@
 <div id="log"></div>
 <script>
 
-const source_location_regex = /^([^@]+\@)?https?\:\/\/[^\/]+[^\:]+\:\d*$/;
-
 promise_test(async t => {
   const scriptLocation = new URL("resources/stream-promise-generates-loaf.js", location.href);
   const [entry, script] = await expect_long_frame_with_script(() => {
@@ -21,7 +19,7 @@
     document.body.appendChild(scriptElement);
   }, script => script.invoker === "StreamPromise.resolve.then", t);
 
-  assert_true(script.sourceLocation?.includes("stream-promise-generates-loaf.js"));
+  assert_true(script.sourceURL.includes("stream-promise-generates-loaf.js"));
 }, "Source location should be extracted for stream promises");
 
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/longtask-timing/idlharness.window-expected.txt b/third_party/blink/web_tests/external/wpt/longtask-timing/idlharness.window-expected.txt
index db85b36..1b543275 100644
--- a/third_party/blink/web_tests/external/wpt/longtask-timing/idlharness.window-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/longtask-timing/idlharness.window-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 18 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 19 FAIL, 0 TIMEOUT, 0 NOTRUN.
 [FAIL] PerformanceLongTaskTiming interface: attribute startTime
   assert_own_property: expected property "startTime" missing
 [FAIL] PerformanceLongTaskTiming interface: attribute duration
@@ -36,5 +36,7 @@
   assert_own_property: expected property "name" missing
 [FAIL] PerformanceScriptTiming interface: attribute entryType
   assert_own_property: expected property "entryType" missing
+[FAIL] PerformanceScriptTiming interface: attribute sourceLocation
+  assert_true: The prototype object must have a property "sourceLocation" expected true got false
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/flag-specific/enable-skia-graphite/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png b/third_party/blink/web_tests/flag-specific/enable-skia-graphite/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
index 4ae1738..92ca1dd 100644
--- a/third_party/blink/web_tests/flag-specific/enable-skia-graphite/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
+++ b/third_party/blink/web_tests/flag-specific/enable-skia-graphite/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/http/tests/performance-timing/loaf-isolated-world.html b/third_party/blink/web_tests/http/tests/performance-timing/loaf-isolated-world.html
index 1f566fb7..22e2d199 100644
--- a/third_party/blink/web_tests/http/tests/performance-timing/loaf-isolated-world.html
+++ b/third_party/blink/web_tests/http/tests/performance-timing/loaf-isolated-world.html
@@ -17,7 +17,7 @@
   return new Promise(resolve => {
     new PerformanceObserver((entries, observer) => {
       if (entries.getEntriesByType("long-animation-frame").some(entry =>
-        entry.scripts.some(script => script.sourceLocation?.includes("from_main")))) {
+        entry.scripts.some(script => script.sourceFunctionName?.includes("from_main")))) {
           resolve();
           observer.disconnect();
         }
@@ -64,9 +64,9 @@
 
   const forbidden = performance.getEntriesByType("long-animation-frame")
     .flatMap(e => e.scripts)
-    .find(script => script.sourceLocation?.includes("forbidden"));
+    .find(script => script.sourceFunctionName?.includes("forbidden"));
 
-  assert_false(!!forbidden, `Forbidden script exposed ${forbidden?.sourceLocation}`);
+  assert_false(!!forbidden, `Forbidden script exposed ${forbidden?.sourceFunctionName}`);
   }, "LoAF should only expose HTTP(s) scripts");
 
 promise_test(async () => {
@@ -83,9 +83,9 @@
 
   const forbidden = performance.getEntriesByType("long-animation-frame")
     .flatMap(e => e.scripts)
-    .find(script => script.sourceLocation?.includes("from_isolated"));
+    .find(script => script.sourceFunctionName?.includes("from_isolated"));
 
-  assert_false(!!forbidden, `Forbidden script exposed ${forbidden?.sourceLocation} ${forbidden?.name}`);
+  assert_false(!!forbidden, `Forbidden script exposed ${forbidden?.sourceFunctionName} ${forbidden?.name}`);
 }, "Test that LoAF doesn't expose scripts from isolated worlds for callbacks");
 
 promise_test(async () => {
@@ -104,9 +104,9 @@
 
   const forbidden = performance.getEntriesByType("long-animation-frame")
     .flatMap(e => e.scripts)
-    .find(script => script.sourceLocation?.includes("from_isolated"));
+    .find(script => script.sourceFunctionName?.includes("from_isolated"));
 
-  assert_false(!!forbidden, `Forbidden script exposed ${forbidden?.sourceLocation} ${forbidden?.name}`);
+  assert_false(!!forbidden, `Forbidden script exposed ${forbidden?.sourceFunctionName} ${forbidden?.name}`);
 }, "Test that LoAF doesn't expose scripts from isolated worlds for events");
 
   promise_test(async () => {
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal1.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal1.bmp
new file mode 100644
index 0000000..4776f82
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal1.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal1bg.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal1bg.bmp
new file mode 100644
index 0000000..466d0ba
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal1bg.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal1wb.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal1wb.bmp
new file mode 100644
index 0000000..56cb932
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal1wb.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal4.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal4.bmp
new file mode 100644
index 0000000..7fd3630
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal4.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal4gs.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal4gs.bmp
new file mode 100644
index 0000000..813268ca
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal4gs.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal4rle.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal4rle.bmp
new file mode 100644
index 0000000..a5672ae
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal4rle.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal8-0.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal8-0.bmp
new file mode 100644
index 0000000..ab8815a
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal8-0.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal8.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal8.bmp
new file mode 100644
index 0000000..96b2f866
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal8.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal8gs.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal8gs.bmp
new file mode 100644
index 0000000..66a0d70
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal8gs.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal8nonsquare.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal8nonsquare.bmp
new file mode 100644
index 0000000..0aa8de0
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal8nonsquare.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal8os2.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal8os2.bmp
new file mode 100644
index 0000000..14901b3
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal8os2.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal8rle.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal8rle.bmp
new file mode 100644
index 0000000..d431014
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal8rle.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal8topdown.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal8topdown.bmp
new file mode 100644
index 0000000..4b2f8e0
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal8topdown.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal8v4.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal8v4.bmp
new file mode 100644
index 0000000..34ebb80
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal8v4.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal8v5.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal8v5.bmp
new file mode 100644
index 0000000..c54647a3
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal8v5.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal8w124.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal8w124.bmp
new file mode 100644
index 0000000..b7cc2d8b
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal8w124.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal8w125.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal8w125.bmp
new file mode 100644
index 0000000..06efed74
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal8w125.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/pal8w126.bmp b/third_party/blink/web_tests/images/bmp-suite/good/pal8w126.bmp
new file mode 100644
index 0000000..112aa9f
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/pal8w126.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/rgb16-565.bmp b/third_party/blink/web_tests/images/bmp-suite/good/rgb16-565.bmp
new file mode 100644
index 0000000..c03a279
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/rgb16-565.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/rgb16-565pal.bmp b/third_party/blink/web_tests/images/bmp-suite/good/rgb16-565pal.bmp
new file mode 100644
index 0000000..e7632e34
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/rgb16-565pal.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/rgb16.bmp b/third_party/blink/web_tests/images/bmp-suite/good/rgb16.bmp
new file mode 100644
index 0000000..6bfe47af
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/rgb16.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/rgb16bfdef.bmp b/third_party/blink/web_tests/images/bmp-suite/good/rgb16bfdef.bmp
new file mode 100644
index 0000000..30fe8bb8
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/rgb16bfdef.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/rgb24.bmp b/third_party/blink/web_tests/images/bmp-suite/good/rgb24.bmp
new file mode 100644
index 0000000..40f8bb0
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/rgb24.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/rgb24pal.bmp b/third_party/blink/web_tests/images/bmp-suite/good/rgb24pal.bmp
new file mode 100644
index 0000000..102e971
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/rgb24pal.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/rgb32.bmp b/third_party/blink/web_tests/images/bmp-suite/good/rgb32.bmp
new file mode 100644
index 0000000..5d57eaa
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/rgb32.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/rgb32bf.bmp b/third_party/blink/web_tests/images/bmp-suite/good/rgb32bf.bmp
new file mode 100644
index 0000000..20fa9a13
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/rgb32bf.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/good/rgb32bfdef.bmp b/third_party/blink/web_tests/images/bmp-suite/good/rgb32bfdef.bmp
new file mode 100644
index 0000000..d7e64e5
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/good/rgb32bfdef.bmp
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/reference/pal1.png b/third_party/blink/web_tests/images/bmp-suite/reference/pal1.png
new file mode 100644
index 0000000..89a433e
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/reference/pal1.png
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/reference/pal1bg.png b/third_party/blink/web_tests/images/bmp-suite/reference/pal1bg.png
new file mode 100644
index 0000000..20c4bb8
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/reference/pal1bg.png
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/reference/pal4.png b/third_party/blink/web_tests/images/bmp-suite/reference/pal4.png
new file mode 100644
index 0000000..188bb04
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/reference/pal4.png
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/reference/pal4gs.png b/third_party/blink/web_tests/images/bmp-suite/reference/pal4gs.png
new file mode 100644
index 0000000..a2558fa
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/reference/pal4gs.png
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/reference/pal8.png b/third_party/blink/web_tests/images/bmp-suite/reference/pal8.png
new file mode 100644
index 0000000..6dd99616
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/reference/pal8.png
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/reference/pal8gs.png b/third_party/blink/web_tests/images/bmp-suite/reference/pal8gs.png
new file mode 100644
index 0000000..a537e3a
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/reference/pal8gs.png
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/reference/pal8nonsquare-e.png b/third_party/blink/web_tests/images/bmp-suite/reference/pal8nonsquare-e.png
new file mode 100644
index 0000000..0e90cdc8
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/reference/pal8nonsquare-e.png
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/reference/pal8w124.png b/third_party/blink/web_tests/images/bmp-suite/reference/pal8w124.png
new file mode 100644
index 0000000..92bd357
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/reference/pal8w124.png
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/reference/pal8w125.png b/third_party/blink/web_tests/images/bmp-suite/reference/pal8w125.png
new file mode 100644
index 0000000..7e5e6b4
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/reference/pal8w125.png
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/reference/pal8w126.png b/third_party/blink/web_tests/images/bmp-suite/reference/pal8w126.png
new file mode 100644
index 0000000..1422f6e2
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/reference/pal8w126.png
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/reference/rgb16-565.png b/third_party/blink/web_tests/images/bmp-suite/reference/rgb16-565.png
new file mode 100644
index 0000000..04a3121d
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/reference/rgb16-565.png
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/reference/rgb16.png b/third_party/blink/web_tests/images/bmp-suite/reference/rgb16.png
new file mode 100644
index 0000000..d9545840
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/reference/rgb16.png
Binary files differ
diff --git a/third_party/blink/web_tests/images/bmp-suite/reference/rgb24.png b/third_party/blink/web_tests/images/bmp-suite/reference/rgb24.png
new file mode 100644
index 0000000..86a9c945
--- /dev/null
+++ b/third_party/blink/web_tests/images/bmp-suite/reference/rgb24.png
Binary files differ
diff --git a/third_party/blink/web_tests/paint/invalidation/filters/filter-repaint-accelerated-child-with-filter-child.html b/third_party/blink/web_tests/paint/invalidation/filters/filter-repaint-accelerated-child-with-filter-child.html
index fe8094b7..5b2de6c8 100644
--- a/third_party/blink/web_tests/paint/invalidation/filters/filter-repaint-accelerated-child-with-filter-child.html
+++ b/third_party/blink/web_tests/paint/invalidation/filters/filter-repaint-accelerated-child-with-filter-child.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<meta name="fuzzy" content="maxDifference=0-3;totalPixels=0-15000">
 <style>
 div {
     width: 200px;
diff --git a/third_party/blink/web_tests/platform/linux-chrome/external/wpt/console/console-log-symbol.any.shadowrealm-expected.txt b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/console/console-log-symbol.any.shadowrealm-expected.txt
new file mode 100644
index 0000000..5b37deb
--- /dev/null
+++ b/third_party/blink/web_tests/platform/linux-chrome/external/wpt/console/console-log-symbol.any.shadowrealm-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+All subtests passed and are omitted for brevity.
+See https://chromium.googlesource.com/chromium/src/+/HEAD/docs/testing/writing_web_tests.md#Text-Test-Baselines for details.
+Harness: the test ran to completion.
diff --git a/third_party/blink/web_tests/platform/linux/compositing/geometry/fixed-position-transform-composited-page-scale-expected.png b/third_party/blink/web_tests/platform/linux/compositing/geometry/fixed-position-transform-composited-page-scale-expected.png
index b0af027..69bc7c0 100644
--- a/third_party/blink/web_tests/platform/linux/compositing/geometry/fixed-position-transform-composited-page-scale-expected.png
+++ b/third_party/blink/web_tests/platform/linux/compositing/geometry/fixed-position-transform-composited-page-scale-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/linux/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png b/third_party/blink/web_tests/platform/linux/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
index e0e526b..9cad36a 100644
--- a/third_party/blink/web_tests/platform/linux/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png b/third_party/blink/web_tests/platform/mac/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
index 8d55b18..83e0410 100644
--- a/third_party/blink/web_tests/platform/mac/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
+++ b/third_party/blink/web_tests/platform/mac/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/svg/custom/getscreenctm-in-scrollable-div-area-expected.png b/third_party/blink/web_tests/platform/mac/svg/custom/getscreenctm-in-scrollable-div-area-expected.png
index ba11013..b35c451 100644
--- a/third_party/blink/web_tests/platform/mac/svg/custom/getscreenctm-in-scrollable-div-area-expected.png
+++ b/third_party/blink/web_tests/platform/mac/svg/custom/getscreenctm-in-scrollable-div-area-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/svg/custom/getscreenctm-in-scrollable-div-area-nested-expected.png b/third_party/blink/web_tests/platform/mac/svg/custom/getscreenctm-in-scrollable-div-area-nested-expected.png
index 1943f91..5be65220 100644
--- a/third_party/blink/web_tests/platform/mac/svg/custom/getscreenctm-in-scrollable-div-area-nested-expected.png
+++ b/third_party/blink/web_tests/platform/mac/svg/custom/getscreenctm-in-scrollable-div-area-nested-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/compositing/geometry/fixed-position-transform-composited-page-scale-expected.png b/third_party/blink/web_tests/platform/win/compositing/geometry/fixed-position-transform-composited-page-scale-expected.png
index 0a38bac..236a159 100644
--- a/third_party/blink/web_tests/platform/win/compositing/geometry/fixed-position-transform-composited-page-scale-expected.png
+++ b/third_party/blink/web_tests/platform/win/compositing/geometry/fixed-position-transform-composited-page-scale-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png b/third_party/blink/web_tests/platform/win/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
index 4806bb6..421dfb98 100644
--- a/third_party/blink/web_tests/platform/win/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
index 2a3e3b3..a79c1603 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -6312,7 +6312,9 @@
     getter invoker
     getter invokerType
     getter pauseDuration
-    getter sourceLocation
+    getter sourceCharPosition
+    getter sourceFunctionName
+    getter sourceURL
     getter window
     getter windowAttribution
     method constructor
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index cf9e7bb1..7a9557d 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -7259,7 +7259,9 @@
     getter invoker
     getter invokerType
     getter pauseDuration
-    getter sourceLocation
+    getter sourceCharPosition
+    getter sourceFunctionName
+    getter sourceURL
     getter window
     getter windowAttribution
     method constructor
diff --git a/third_party/chromite b/third_party/chromite
index c5ddc55..56df863a 160000
--- a/third_party/chromite
+++ b/third_party/chromite
@@ -1 +1 @@
-Subproject commit c5ddc55e66f3478f313ab0c369f96dd6f01c16fd
+Subproject commit 56df863a202389def3bba8925ba93bb2bbca7eda
diff --git a/third_party/chromium-variations b/third_party/chromium-variations
index 63ae3c8..a7a1e5e 160000
--- a/third_party/chromium-variations
+++ b/third_party/chromium-variations
@@ -1 +1 @@
-Subproject commit 63ae3c8064cb6e2bbf5d4557efc83d3fe82bb35a
+Subproject commit a7a1e5eaa6ed285b51271a665bfda91bc8fe7a3f
diff --git a/third_party/depot_tools b/third_party/depot_tools
index af6eabf..0696c428 160000
--- a/third_party/depot_tools
+++ b/third_party/depot_tools
@@ -1 +1 @@
-Subproject commit af6eabff5313cfdce7e4ff358e3490d1571ea4cd
+Subproject commit 0696c428b04513254d3b3e0b1fba5e5afdb11cf4
diff --git a/third_party/devtools-frontend-internal b/third_party/devtools-frontend-internal
index f75ab7f..abdf931 160000
--- a/third_party/devtools-frontend-internal
+++ b/third_party/devtools-frontend-internal
@@ -1 +1 @@
-Subproject commit f75ab7f452a6bfa2cb62be2dd570071cb2f2d9e9
+Subproject commit abdf93162b756e81b7d5d51ffa58bdf210cb4dde
diff --git a/third_party/devtools-frontend/src b/third_party/devtools-frontend/src
index 31ec4cf..294f601c 160000
--- a/third_party/devtools-frontend/src
+++ b/third_party/devtools-frontend/src
@@ -1 +1 @@
-Subproject commit 31ec4cf8104601c847e622449d9ec27abcaab832
+Subproject commit 294f601c1dc3f2eeafe0c65278cc65668eb2c865
diff --git a/third_party/libc++abi/src b/third_party/libc++abi/src
index 0c4e8fa..fb27868 160000
--- a/third_party/libc++abi/src
+++ b/third_party/libc++abi/src
@@ -1 +1 @@
-Subproject commit 0c4e8fac5c5b369eb27093c0bdeae5dfad1aaf5c
+Subproject commit fb278689d948a9a02e9c5ff4bdc9d524be2d4e82
diff --git a/third_party/pdfium b/third_party/pdfium
index 7c7a608..4282836 160000
--- a/third_party/pdfium
+++ b/third_party/pdfium
@@ -1 +1 @@
-Subproject commit 7c7a6087e09e1a344984a6d0c5fbc2af36eca7ea
+Subproject commit 4282836f334034e852b8bc4e38459fdf8ae644db
diff --git a/third_party/perfetto b/third_party/perfetto
index dd3f5e9..3deb235 160000
--- a/third_party/perfetto
+++ b/third_party/perfetto
@@ -1 +1 @@
-Subproject commit dd3f5e97f346774ecd3e49a2679f82cbc23fff98
+Subproject commit 3deb2358149574207c12407c7a6b2a885a5cef3b
diff --git a/third_party/skia b/third_party/skia
index 396f77d..de3f398 160000
--- a/third_party/skia
+++ b/third_party/skia
@@ -1 +1 @@
-Subproject commit 396f77db4a39189f39b63d0a00f103bcc7bc4491
+Subproject commit de3f3987a8365b51e4f0546442f8d990a376e192
diff --git a/third_party/webrtc b/third_party/webrtc
index 35fe958..1df2690 160000
--- a/third_party/webrtc
+++ b/third_party/webrtc
@@ -1 +1 @@
-Subproject commit 35fe95802df52d8d2d6fdbee4278663076caaac7
+Subproject commit 1df269099fcb3c8e7474786a21bcdd32335c43ed
diff --git a/tools/clang/plugins/FindBadConstructsAction.cpp b/tools/clang/plugins/FindBadConstructsAction.cpp
index 798def16..23a9ed1 100644
--- a/tools/clang/plugins/FindBadConstructsAction.cpp
+++ b/tools/clang/plugins/FindBadConstructsAction.cpp
@@ -83,6 +83,9 @@
     } else if (arg.starts_with(kBadRawPtrCastExcludePathArgPrefix)) {
       options_.check_bad_raw_ptr_cast_exclude_paths.push_back(
           arg.substr(strlen(kBadRawPtrCastExcludePathArgPrefix)).str());
+    } else if (arg == "check-allow-auto-typedefs") {
+      // TODO(danakj): Remove once enabled in Chromium.
+      options_.allow_auto_typedefs = true;
     } else if (arg == "check-base-classes") {
       // TODO(rsleevi): Remove this once http://crbug.com/123295 is fixed.
       options_.check_base_classes = true;
diff --git a/tools/clang/plugins/FindBadConstructsConsumer.cpp b/tools/clang/plugins/FindBadConstructsConsumer.cpp
index ccb3ab7..bbf322e 100644
--- a/tools/clang/plugins/FindBadConstructsConsumer.cpp
+++ b/tools/clang/plugins/FindBadConstructsConsumer.cpp
@@ -23,10 +23,12 @@
 // any namespace qualifiers. This is similar to desugaring, except that for
 // ElaboratedTypes, desugar will unwrap too much.
 const Type* UnwrapType(const Type* type) {
-  if (const ElaboratedType* elaborated = dyn_cast<ElaboratedType>(type))
+  if (const ElaboratedType* elaborated = dyn_cast<ElaboratedType>(type)) {
     return UnwrapType(elaborated->getNamedType().getTypePtr());
-  if (const TypedefType* typedefed = dyn_cast<TypedefType>(type))
+  }
+  if (const TypedefType* typedefed = dyn_cast<TypedefType>(type)) {
     return UnwrapType(typedefed->desugar().getTypePtr());
+  }
   return type;
 }
 
@@ -82,8 +84,9 @@
 std::set<FunctionDecl*> GetLateParsedFunctionDecls(TranslationUnitDecl* decl) {
   struct Visitor : public RecursiveASTVisitor<Visitor> {
     bool VisitFunctionDecl(FunctionDecl* function_decl) {
-      if (function_decl->isLateTemplateParsed())
+      if (function_decl->isLateTemplateParsed()) {
         late_parsed_decls.insert(function_decl);
+      }
       return true;
     }
 
@@ -93,24 +96,31 @@
   return v.late_parsed_decls;
 }
 
-std::string GetAutoReplacementTypeAsString(QualType type,
-                                           StorageClass storage_class) {
-  QualType non_reference_type = type.getNonReferenceType();
-  if (!non_reference_type->isPointerType())
+std::string GetAutoReplacementTypeAsString(QualType original_type,
+                                           StorageClass storage_class,
+                                           bool allow_typedefs) {
+  QualType non_reference_type = original_type.getNonReferenceType();
+  if (!non_reference_type->isPointerType() ||
+      (allow_typedefs && non_reference_type->getAs<clang::TypedefType>())) {
     return storage_class == SC_Static ? "static auto" : "auto";
+  }
 
   std::string result = GetAutoReplacementTypeAsString(
-      non_reference_type->getPointeeType(), storage_class);
+      non_reference_type->getPointeeType(), storage_class, allow_typedefs);
   result += "*";
-  if (non_reference_type.isConstQualified())
+  if (non_reference_type.isConstQualified()) {
     result += " const";
-  if (non_reference_type.isVolatileQualified())
+  }
+  if (non_reference_type.isVolatileQualified()) {
     result += " volatile";
-  if (type->isReferenceType() && !non_reference_type.isConstQualified()) {
-    if (type->isLValueReferenceType())
+  }
+  if (original_type->isReferenceType() &&
+      !non_reference_type.isConstQualified()) {
+    if (original_type->isLValueReferenceType()) {
       result += "&";
-    else if (type->isRValueReferenceType())
+    } else if (original_type->isRValueReferenceType()) {
       result += "&&";
+    }
   }
   return result;
 }
@@ -233,17 +243,20 @@
     layout_visitor_->VisitLayoutObjectMethods(context);
   }
   RecursiveASTVisitor::TraverseDecl(context.getTranslationUnitDecl());
-  if (ipc_visitor_)
+  if (ipc_visitor_) {
     ipc_visitor_->set_context(nullptr);
+  }
   FindBadRawPtrPatterns(options_, context, instance());
 }
 
 bool FindBadConstructsConsumer::TraverseDecl(Decl* decl) {
-  if (ipc_visitor_)
+  if (ipc_visitor_) {
     ipc_visitor_->BeginDecl(decl);
+  }
   bool result = RecursiveASTVisitor::TraverseDecl(decl);
-  if (ipc_visitor_)
+  if (ipc_visitor_) {
     ipc_visitor_->EndDecl();
+  }
   return result;
 }
 
@@ -261,26 +274,34 @@
 }
 
 bool FindBadConstructsConsumer::VisitTagDecl(clang::TagDecl* tag_decl) {
-  if (tag_decl->isCompleteDefinition())
+  if (tag_decl->isCompleteDefinition()) {
     CheckTag(tag_decl);
+  }
   return true;
 }
 
 bool FindBadConstructsConsumer::VisitTemplateSpecializationType(
     TemplateSpecializationType* spec) {
-  if (ipc_visitor_)
+  if (ipc_visitor_) {
     ipc_visitor_->VisitTemplateSpecializationType(spec);
+  }
   return true;
 }
 
 bool FindBadConstructsConsumer::VisitCallExpr(CallExpr* call_expr) {
-  if (ipc_visitor_)
+  if (ipc_visitor_) {
     ipc_visitor_->VisitCallExpr(call_expr);
+  }
   return true;
 }
 
 bool FindBadConstructsConsumer::VisitVarDecl(clang::VarDecl* var_decl) {
-  CheckVarDecl(var_decl);
+  if (options_.allow_auto_typedefs) {
+    CheckDeducedAutoPointer(var_decl);
+  } else {
+    // TODO(danakj): Remove this path once the other is enabled in Chromium.
+    CheckVarDecl(var_decl);
+  }
   return true;
 }
 
@@ -296,8 +317,9 @@
     // If this is a POD or a class template or a type dependent on a
     // templated class, assume there's no ctor/dtor/virtual method
     // optimization that we should do.
-    if (!IsPodOrTemplateType(*record))
+    if (!IsPodOrTemplateType(*record)) {
       CheckCtorDtorWeight(record_location, record);
+    }
   }
 
   bool warn_on_inline_bodies = !implementation_file;
@@ -306,15 +328,17 @@
   // does not always see the "override", so we get false positives.
   // See http://llvm.org/bugs/show_bug.cgi?id=18440 and
   //     http://llvm.org/bugs/show_bug.cgi?id=21942
-  if (!IsPodOrTemplateType(*record))
+  if (!IsPodOrTemplateType(*record)) {
     CheckVirtualMethods(record_location, record, warn_on_inline_bodies);
+  }
 
   // TODO(dcheng): This is needed because some of the diagnostics for refcounted
   // classes use DiagnosticsEngine::Report() directly, and there are existing
   // violations in Blink. This should be removed once the checks are
   // modularized.
-  if (location_type != LocationType::kBlink)
+  if (location_type != LocationType::kBlink) {
     CheckRefCountedDtors(record_location, record);
+  }
 
   if (blink_data_member_type_checker_ &&
       location_type == LocationType::kBlink) {
@@ -325,15 +349,17 @@
 }
 
 void FindBadConstructsConsumer::CheckEnumMaxValue(EnumDecl* decl) {
-  if (!decl->isScoped())
+  if (!decl->isScoped()) {
     return;
+  }
 
   clang::EnumConstantDecl* max_value = nullptr;
   std::set<clang::EnumConstantDecl*> max_enumerators;
   llvm::APSInt max_seen;
   for (clang::EnumConstantDecl* enumerator : decl->enumerators()) {
-    if (enumerator->getName() == "kMaxValue")
+    if (enumerator->getName() == "kMaxValue") {
       max_value = enumerator;
+    }
 
     llvm::APSInt current_value = enumerator->getInitVal();
     if (max_enumerators.empty()) {
@@ -344,8 +370,9 @@
 
     assert(max_seen.isSigned() == current_value.isSigned());
 
-    if (current_value < max_seen)
+    if (current_value < max_seen) {
       continue;
+    }
 
     if (current_value == max_seen) {
       max_enumerators.emplace(enumerator);
@@ -358,8 +385,9 @@
     max_seen = current_value;
   }
 
-  if (!max_value)
+  if (!max_value) {
     return;
+  }
 
   if (max_enumerators.find(max_value) == max_enumerators.end()) {
     ReportIfSpellingLocNotIgnored(max_value->getLocation(),
@@ -380,16 +408,19 @@
   // struct {
   //   ...
   // } name_;
-  if (record->getIdentifier() == NULL)
+  if (record->getIdentifier() == NULL) {
     return;
+  }
 
   // We don't handle unions.
-  if (record->isUnion())
+  if (record->isUnion()) {
     return;
+  }
 
   // Skip records that derive from ignored base classes.
-  if (HasIgnoredBases(record))
+  if (HasIgnoredBases(record)) {
     return;
+  }
 
   // Count the number of templated base classes as a feature of whether the
   // destructor can be inlined.
@@ -470,10 +501,11 @@
             // that's the better tradeoff at this point).
             // TODO(dcheng): With the RecursiveASTVisitor, these warnings might
             // be emitted on other platforms too, reevaluate if we want to keep
-            // surpressing this then http://crbug.com/467288
-            if (!record->hasAttr<DLLExportAttr>())
+            // suppressing this then http://crbug.com/467288
+            if (!record->hasAttr<DLLExportAttr>()) {
               ReportIfSpellingLocNotIgnored(record_location,
                                             diag_no_explicit_copy_ctor_);
+            }
           } else {
             // See the comment in the previous branch about copy constructors.
             // This does the same for implicit move constructors.
@@ -481,9 +513,10 @@
                 it->isMoveConstructor() &&
                 !record->hasUserDeclaredMoveConstructor() &&
                 record->hasAttr<DLLExportAttr>();
-            if (!is_likely_compiler_generated_dllexport_move_ctor)
+            if (!is_likely_compiler_generated_dllexport_move_ctor) {
               ReportIfSpellingLocNotIgnored(it->getInnerLocStart(),
                                             diag_inline_complex_ctor_);
+            }
           }
         } else if (it->isInlined() && !it->isInlineSpecified() &&
                    !it->isDeleted() &&
@@ -561,8 +594,9 @@
       continue;
     } else {
       CheckVirtualSpecifiers(*it);
-      if (warn_on_inline_bodies)
+      if (warn_on_inline_bodies) {
         CheckVirtualBodies(*it);
+      }
     }
   }
 }
@@ -578,8 +612,9 @@
   OverrideAttr* override_attr = method->getAttr<OverrideAttr>();
   FinalAttr* final_attr = method->getAttr<FinalAttr>();
 
-  if (IsMethodInTestingNamespace(method))
+  if (IsMethodInTestingNamespace(method)) {
     return;
+  }
 
   SourceManager& manager = instance().getSourceManager();
   const LangOptions& lang_opts = instance().getLangOpts();
@@ -589,16 +624,18 @@
   bool add_override = false;
 
   // Complain if a method is annotated virtual && (override || final).
-  if (has_virtual && (override_attr || final_attr))
+  if (has_virtual && (override_attr || final_attr)) {
     remove_virtual = true;
+  }
 
   // Complain if a method is an override and is not annotated with override or
   // final.
   if (is_override && !override_attr && !final_attr) {
     add_override = true;
     // Also remove the virtual in the same fixit if currently present.
-    if (has_virtual)
+    if (has_virtual) {
       remove_virtual = true;
+    }
   }
 
   if (final_attr && override_attr) {
@@ -608,8 +645,9 @@
         << FixItHint::CreateRemoval(override_attr->getRange());
   }
 
-  if (!remove_virtual && !add_override)
+  if (!remove_virtual && !add_override) {
     return;
+  }
 
   // Deletion of virtual and insertion of override are tricky. The AST does not
   // expose the location of `virtual` or `=`: the former is useful when trying
@@ -648,8 +686,9 @@
       } else if (token.is(tok::raw_identifier)) {
         // TODO(dcheng): Unclear if this needs to check for nested parentheses
         // as well?
-        if (token.getRawIdentifier() == "virtual")
+        if (token.getRawIdentifier() == "virtual") {
           virtual_loc = token.getLocation();
+        }
       }
     }
   }
@@ -689,18 +728,21 @@
         if (loc.isMacroID()) {
           SourceManager& manager = instance().getSourceManager();
           LocationType type = ClassifyLocation(manager.getSpellingLoc(loc));
-          if (type == LocationType::kThirdParty || type == LocationType::kBlink)
+          if (type == LocationType::kThirdParty ||
+              type == LocationType::kBlink) {
             emit = false;
-          else {
+          } else {
             StringRef name = Lexer::getImmediateMacroName(
                 loc, manager, instance().getLangOpts());
             if (name == "CR_BEGIN_MSG_MAP_EX" ||
-                name == "BEGIN_SAFE_MSG_MAP_EX")
+                name == "BEGIN_SAFE_MSG_MAP_EX") {
               emit = false;
+            }
           }
         }
-        if (emit)
+        if (emit) {
           ReportIfSpellingLocNotIgnored(loc, diag_virtual_with_inline_body_);
+        }
       }
     }
   }
@@ -717,8 +759,10 @@
       // it's counted, since the translation unit will fail to build. In that
       // case, just count it as a trivial member to avoid emitting warnings that
       // might be spurious.
-      if (!record_decl->hasDefinition() || record_decl->hasTrivialDestructor())
+      if (!record_decl->hasDefinition() ||
+          record_decl->hasTrivialDestructor()) {
         return TypeClassification::kTrivial;
+      }
 
       const auto name = record_decl->getQualifiedNameAsString();
 
@@ -727,8 +771,9 @@
       // `kNonTrivialTemplate`. The `kNonTrivialExternTemplate` classification
       // exists for this purpose.
       // https://github.com/llvm-mirror/libcxx/blob/78d6a7767ed57b50122a161b91f59f19c9bd0d19/include/string#L4317
-      if (name == "std::basic_string")
+      if (name == "std::basic_string") {
         return TypeClassification::kNonTrivialExternTemplate;
+      }
 
       // `base::raw_ptr` and `base::raw_ref` are non-trivial if the
       // `use_backup_ref_ptr` flag is enabled, and trivial otherwise. Since
@@ -769,8 +814,9 @@
       // using Foo = Bar<T>;
       //
       // Given `Foo<Baz>`, we want to classify it simply as `Bar<Baz>` would be.
-      if (template_type->isTypeAlias())
+      if (template_type->isTypeAlias()) {
         return ClassifyType(template_type->getAliasedType().getTypePtr());
+      }
 
       // Otherwise, classify the type produced by the template and apply the
       // corresponding template classification. For an example:
@@ -782,10 +828,12 @@
       // for `T`;
       const auto classification =
           ClassifyType(template_type->desugar().getTypePtr());
-      if (classification == TypeClassification::kTrivial)
+      if (classification == TypeClassification::kTrivial) {
         return TypeClassification::kTrivialTemplate;
-      if (classification == TypeClassification::kNonTrivial)
+      }
+      if (classification == TypeClassification::kNonTrivial) {
         return TypeClassification::kNonTrivialTemplate;
+      }
 
       return classification;
     }
@@ -900,8 +948,9 @@
   // only ones which will result in the destructor potentially being
   // exposed. This check is largely redundant, as Chromium code should be
   // exclusively using public inheritance.
-  if (path.Access != AS_public)
+  if (path.Access != AS_public) {
     return false;
+  }
 
   CXXRecordDecl* record =
       dyn_cast<CXXRecordDecl>(base->getType()->getAs<RecordType>()->getDecl());
@@ -944,8 +993,9 @@
     SourceLocation record_location,
     CXXRecordDecl* record) {
   // Skip anonymous structs.
-  if (record->getIdentifier() == NULL)
+  if (record->getIdentifier() == NULL) {
     return;
+  }
 
   // Determine if the current type is even ref-counted.
   CXXBasePaths refcounted_path;
@@ -998,8 +1048,9 @@
   //       new RefCountedInterface);
   //   // Calls SomeInterface::~SomeInterface(), which is unsafe.
   //   delete static_cast<SomeInterface*>(some_class.get());
-  if (!options_.check_base_classes)
+  if (!options_.check_base_classes) {
     return;
+  }
 
   // Find all public destructors. This will record the class hierarchy
   // that leads to the public destructor in |dtor_paths|.
@@ -1050,8 +1101,9 @@
     SourceLocation record_location,
     CXXRecordDecl* record) {
   // Skip anonymous structs.
-  if (record->getIdentifier() == NULL)
+  if (record->getIdentifier() == NULL) {
     return;
+  }
 
   // Iterate through members of the class.
   RecordDecl::field_iterator iter(record->field_begin()),
@@ -1097,8 +1149,9 @@
 // Copied from BlinkGCPlugin, see crrev.com/1135333007
 void FindBadConstructsConsumer::ParseFunctionTemplates(
     TranslationUnitDecl* decl) {
-  if (!instance().getLangOpts().DelayedTemplateParsing)
+  if (!instance().getLangOpts().DelayedTemplateParsing) {
     return;  // Nothing to do.
+  }
 
   std::set<FunctionDecl*> late_parsed_decls = GetLateParsedFunctionDecls(decl);
   clang::Sema& sema = instance().getSema();
@@ -1107,8 +1160,9 @@
     assert(fd->isLateTemplateParsed());
 
     if (instance().getSourceManager().isInSystemHeader(
-            instance().getSourceManager().getSpellingLoc(fd->getLocation())))
+            instance().getSourceManager().getSpellingLoc(fd->getLocation()))) {
       continue;
+    }
 
     // Parse and build AST for yet-uninstantiated template functions.
     clang::LateParsedTemplate* lpt = sema.LateParsedTemplateMap[fd].get();
@@ -1116,10 +1170,14 @@
   }
 }
 
+// OLD PATH: Replaced with CheckDeducedAutoPointer().
+// TODO(danakj): Remove once CheckDeducedAutoPointer() is enabled to replace
+// this method.
 void FindBadConstructsConsumer::CheckVarDecl(clang::VarDecl* var_decl) {
   // Lambda init-captures should be ignored.
-  if (var_decl->isInitCapture())
+  if (var_decl->isInitCapture()) {
     return;
+  }
 
   // Check whether auto deduces to a raw pointer.
   QualType non_reference_type = var_decl->getType().getNonReferenceType();
@@ -1153,9 +1211,9 @@
             ReportIfSpellingLocNotIgnored(range.getBegin(),
                                           diag_auto_deduced_to_a_pointer_type_)
                 << FixItHint::CreateReplacement(
-                       range,
-                       GetAutoReplacementTypeAsString(
-                           var_decl->getType(), var_decl->getStorageClass()));
+                       range, GetAutoReplacementTypeAsString(
+                                  var_decl->getType(),
+                                  var_decl->getStorageClass(), false));
           }
         }
       }
@@ -1167,4 +1225,99 @@
   }
 }
 
+// Check whether auto deduces to a raw pointer.
+void FindBadConstructsConsumer::CheckDeducedAutoPointer(
+    clang::VarDecl* var_decl) {
+  // Lambda init-captures should be ignored.
+  if (var_decl->isInitCapture()) {
+    return;
+  }
+
+  QualType qualtype = var_decl->getType().getNonReferenceType();
+  // Dependent types in templates can not be fully deduced as they depend on
+  // what the template parameter will be. They result in a 'null' deduced_type
+  // later. To catch this would require looking at each instantiation but then
+  // we could get inconsistent errors for some instantiations and not others.
+  if (qualtype->isDependentType()) {
+    return;
+  }
+
+  // Find the `clang::AutoType` which may be inside a `PointerType`. Since
+  // `AutoType` is 'sugar', care must be taken to not skip over it.
+  const clang::AutoType* auto_type = nullptr;
+  while (!auto_type) {
+    // We need to look for AutoType before looking for PointerType, or we will
+    // skip right past it, since AutoType is 'sugar'.
+    auto_type = qualtype->getAs<clang::AutoType>();
+    // If we have a type `auto*` then the pointer needs to be pulled off before
+    // we can find the AutoType. If we're not at a pointer, then stop searching
+    // for AutoType.
+    if (auto* ptr_type = qualtype->getAs<clang::PointerType>()) {
+      qualtype = ptr_type->getPointeeType();
+    } else {
+      break;
+    }
+  }
+  if (!auto_type) {
+    return;
+  }
+
+  // If not deduced yet, we can't tell if we require `auto*`.
+  if (!auto_type->isDeduced()) {
+    return;
+  }
+  // `Concept auto x` should be allowed even if the Concept matches to a pointer
+  // type.
+  if (auto_type->isConstrained()) {
+    return;
+  }
+
+  QualType deduced_type = auto_type->getDeducedType();
+  if (deduced_type.isNull()) {
+    var_decl->dump();
+    // return;
+  }
+  // If `auto` resolves to a function pointer, it's always allowed.
+  if (deduced_type.getCanonicalType()->isFunctionPointerType()) {
+    return;
+  }
+  // If `auto` resolves to a type alias, it's allowed, even if there's a pointer
+  // inside the alias, which would be an implementation detail of the alias
+  // type. This includes stdlib iterator aliases.
+  if (deduced_type->getAs<clang::TypedefType>()) {
+    return;
+  }
+  // Last, if it's not a pointer at all then `auto` is allowed.
+  if (!deduced_type->getAs<clang::PointerType>()) {
+    return;
+  }
+
+  // Check if we should even be considering this type. This is the most
+  // expensive check, so we check this last.
+  LocationType location_type = ClassifyLocation(var_decl->getBeginLoc());
+  // We don't generate errors in third-party code.
+  if (location_type == LocationType::kThirdParty) {
+    return;
+  }
+
+  // Report an error, the code should say `auto*` instead of `auto`.
+  //
+  // The range starts from |var_decl|'s loc start, which is the
+  // beginning of the full expression defining this |var_decl|. It
+  // ends, however, where this |var_decl|'s type loc ends, since
+  // that's the end of the type of |var_decl|.
+  // Note that the beginning source location of type loc omits cv
+  // qualifiers, which is why it's not a good candidate to use for the
+  // start of the range.
+  clang::SourceRange range(
+      var_decl->getBeginLoc(),
+      var_decl->getTypeSourceInfo()->getTypeLoc().getEndLoc());
+  ReportIfSpellingLocNotIgnored(range.getBegin(),
+                                diag_auto_deduced_to_a_pointer_type_)
+      << FixItHint::CreateReplacement(
+             range,
+             GetAutoReplacementTypeAsString(var_decl->getType(),
+                                            var_decl->getStorageClass(), true));
+}
+
 }  // namespace chrome_checker
diff --git a/tools/clang/plugins/FindBadConstructsConsumer.h b/tools/clang/plugins/FindBadConstructsConsumer.h
index 4ce8c62d..e33aab2 100644
--- a/tools/clang/plugins/FindBadConstructsConsumer.h
+++ b/tools/clang/plugins/FindBadConstructsConsumer.h
@@ -113,6 +113,7 @@
                                   clang::CXXRecordDecl* record);
   void CheckEnumMaxValue(clang::EnumDecl* decl);
   void CheckVarDecl(clang::VarDecl* decl);
+  void CheckDeducedAutoPointer(clang::VarDecl* decl);
 
   void ParseFunctionTemplates(clang::TranslationUnitDecl* decl);
 
diff --git a/tools/clang/plugins/Options.h b/tools/clang/plugins/Options.h
index 5cd7591e..877dc0a 100644
--- a/tools/clang/plugins/Options.h
+++ b/tools/clang/plugins/Options.h
@@ -11,6 +11,7 @@
 namespace chrome_checker {
 
 struct Options {
+  bool allow_auto_typedefs = false;
   bool check_base_classes = false;
   bool check_blink_data_member_type = false;
   bool check_ipc = false;
diff --git a/tools/clang/plugins/tests/auto_raw_pointer.cpp b/tools/clang/plugins/tests/auto_raw_pointer.cpp
index e3d4e565..1afd9a1 100644
--- a/tools/clang/plugins/tests/auto_raw_pointer.cpp
+++ b/tools/clang/plugins/tests/auto_raw_pointer.cpp
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-class Iteratable {
+class Iterable {
  public:
   using const_iterator = int* const*;
 
@@ -10,6 +10,19 @@
   const_iterator end() { return nullptr; }
 };
 
+using AliasWithPtr = int*;
+AliasWithPtr return_alias_with_ptr() {
+  return nullptr;
+}
+AliasWithPtr* return_ptr_to_alias_with_ptr() {
+  return nullptr;
+}
+
+typedef int* TypedefWithPtr;
+TypedefWithPtr return_typedef_with_ptr() {
+  return nullptr;
+}
+
 class Foo {
  public:
   void foo() {}
@@ -58,11 +71,52 @@
 
   static auto static_ptr = new int;
 
-  Iteratable iteratable;
-  for (auto& it : iteratable)
+  Iterable iterable;
+  for (auto& it : iterable) {
     (void)it;
+  }
+
+  // The alias itself contains a pointer, which is an implementation detail, so
+  // `auto` is allowed.
+  auto alias = return_alias_with_ptr();
+
+  // A pointer to an alias (of a pointer) still requires `auto*`. This will
+  // succeed.
+  auto* good_ptr_to_alias = return_ptr_to_alias_with_ptr();
+  // This will fail.
+  auto bad_ptr_to_alias = return_ptr_to_alias_with_ptr();
+
+  // `typedef` and `using` type aliases both work the same.
+  auto tdef = return_typedef_with_ptr();
 
   // This is a valid usecase of deducing a type to be a raw pointer and should
   // not trigger a warning / error.
   auto lambda = [foo_ptr = &foo] { return *foo_ptr; };
 }
+
+template <class T>
+struct WithDependentType {
+  void func() {
+    // The deduced type here is not known and `isNull()` will be true when
+    // parsing the template (but not instantiations).
+    auto x = T::foo();
+  }
+};
+
+void use_template() {
+  struct S {
+    static int* foo() { return nullptr; };
+  };
+  // The dependent type is instantiated, but no errors are produced.
+  WithDependentType<S>().func();
+}
+
+template <class T>
+concept Concept = true;
+
+void use_concept() {
+  int x = 0;
+  // No warning, because this is a constrained auto. Being a pointer or not is
+  // an implementation detail of the matching type.
+  Concept auto c = &x;
+}
diff --git a/tools/clang/plugins/tests/auto_raw_pointer.flags b/tools/clang/plugins/tests/auto_raw_pointer.flags
new file mode 100644
index 0000000..8b30644c
--- /dev/null
+++ b/tools/clang/plugins/tests/auto_raw_pointer.flags
@@ -0,0 +1 @@
+-Xclang -plugin-arg-find-bad-constructs -Xclang check-allow-auto-typedefs
diff --git a/tools/clang/plugins/tests/auto_raw_pointer.txt b/tools/clang/plugins/tests/auto_raw_pointer.txt
index 6683acdf..7ff656c0 100644
--- a/tools/clang/plugins/tests/auto_raw_pointer.txt
+++ b/tools/clang/plugins/tests/auto_raw_pointer.txt
@@ -1,61 +1,65 @@
-auto_raw_pointer.cpp:28:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+auto_raw_pointer.cpp:41:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
   auto raw_int_ptr = &integer;
   ^~~~
   auto*
-auto_raw_pointer.cpp:29:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+auto_raw_pointer.cpp:42:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
   const auto const_raw_int_ptr = &integer;
   ^~~~~~~~~~
   auto* const
-auto_raw_pointer.cpp:30:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+auto_raw_pointer.cpp:43:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
   const auto& const_raw_int_ptr_ref = &integer;
   ^~~~~~~~~~~
   auto* const
-auto_raw_pointer.cpp:35:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+auto_raw_pointer.cpp:48:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
   auto raw_foo_ptr = &foo;
   ^~~~
   auto*
-auto_raw_pointer.cpp:36:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+auto_raw_pointer.cpp:49:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
   const auto const_raw_foo_ptr = &foo;
   ^~~~~~~~~~
   auto* const
-auto_raw_pointer.cpp:37:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+auto_raw_pointer.cpp:50:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
   const auto& const_raw_foo_ptr_ref = &foo;
   ^~~~~~~~~~~
   auto* const
-auto_raw_pointer.cpp:44:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+auto_raw_pointer.cpp:57:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
   auto double_ptr_auto = &int_ptr;
   ^~~~
   auto**
-auto_raw_pointer.cpp:45:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+auto_raw_pointer.cpp:58:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
   auto* double_ptr_auto_ptr = &int_ptr;
   ^~~~~
   auto**
-auto_raw_pointer.cpp:52:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+auto_raw_pointer.cpp:65:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
   auto auto_awesome = pointer_awesomeness;
   ^~~~
   auto* const* const volatile** const*
-auto_raw_pointer.cpp:54:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+auto_raw_pointer.cpp:67:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
   auto& int_ptr_ref = int_ptr;
   ^~~~~
   auto*&
-auto_raw_pointer.cpp:55:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+auto_raw_pointer.cpp:68:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
   const auto& const_int_ptr_ref = int_ptr;
   ^~~~~~~~~~~
   auto* const
-auto_raw_pointer.cpp:56:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+auto_raw_pointer.cpp:69:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
   auto&& int_ptr_rref = static_cast<int*&&>(int_ptr);
   ^~~~~~
   auto*&&
-auto_raw_pointer.cpp:57:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+auto_raw_pointer.cpp:70:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
   const auto&& const_int_ptr_rref = static_cast<int*&&>(int_ptr);
   ^~~~~~~~~~~~
   auto* const
-auto_raw_pointer.cpp:59:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+auto_raw_pointer.cpp:72:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
   static auto static_ptr = new int;
   ^~~~~~~~~~~
   static auto*
-auto_raw_pointer.cpp:62:8: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
-  for (auto& it : iteratable)
+auto_raw_pointer.cpp:75:8: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  for (auto& it : iterable) {
        ^~~~~
        auto* const
-15 warnings generated.
+auto_raw_pointer.cpp:87:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  auto bad_ptr_to_alias = return_ptr_to_alias_with_ptr();
+  ^~~~
+  auto*
+16 warnings generated.
diff --git a/tools/clang/plugins/tests/auto_raw_pointer_no_typedefs.cpp b/tools/clang/plugins/tests/auto_raw_pointer_no_typedefs.cpp
new file mode 100644
index 0000000..43876986
--- /dev/null
+++ b/tools/clang/plugins/tests/auto_raw_pointer_no_typedefs.cpp
@@ -0,0 +1,114 @@
+// Copyright 2016 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+class Iterable {
+ public:
+  using const_iterator = int* const*;
+
+  const_iterator begin() { return nullptr; }
+  const_iterator end() { return nullptr; }
+};
+
+using AliasWithPtr = int*;
+AliasWithPtr return_alias_with_ptr() {
+  return nullptr;
+}
+AliasWithPtr* return_ptr_to_alias_with_ptr() {
+  return nullptr;
+}
+
+class Foo {
+ public:
+  void foo() {}
+};
+
+void f();
+
+int main() {
+  int integer;
+  Foo foo;
+
+  auto int_copy = integer;
+  const auto const_int_copy = integer;
+  const auto& const_int_ref = integer;
+
+  auto raw_int_ptr = &integer;
+  const auto const_raw_int_ptr = &integer;
+  const auto& const_raw_int_ptr_ref = &integer;
+
+  auto* raw_int_ptr_valid = &integer;
+  const auto* const_raw_int_ptr_valid = &integer;
+
+  auto raw_foo_ptr = &foo;
+  const auto const_raw_foo_ptr = &foo;
+  const auto& const_raw_foo_ptr_ref = &foo;
+
+  auto* raw_foo_ptr_valid = &foo;
+  const auto* const_raw_foo_ptr_valid = &foo;
+
+  int* int_ptr;
+
+  auto double_ptr_auto = &int_ptr;
+  auto* double_ptr_auto_ptr = &int_ptr;
+  auto** double_ptr_auto_double_ptr = &int_ptr;
+
+  auto function_ptr = &f;
+  auto method_ptr = &Foo::foo;
+
+  int* const* const volatile** const* pointer_awesomeness;
+  auto auto_awesome = pointer_awesomeness;
+
+  auto& int_ptr_ref = int_ptr;
+  const auto& const_int_ptr_ref = int_ptr;
+  auto&& int_ptr_rref = static_cast<int*&&>(int_ptr);
+  const auto&& const_int_ptr_rref = static_cast<int*&&>(int_ptr);
+
+  static auto static_ptr = new int;
+
+  Iterable iterable;
+  for (auto& it : iterable) {
+    (void)it;
+  }
+
+  // The alias itself contains a pointer, which is an implementation detail, so
+  // `auto` is allowed.
+  auto alias = return_alias_with_ptr();
+
+  // A pointer to an alias (of a pointer) still requires `auto*`. This will
+  // succeed.
+  auto* good_ptr_to_alias = return_ptr_to_alias_with_ptr();
+  // This will fail.
+  auto bad_ptr_to_alias = return_ptr_to_alias_with_ptr();
+
+  // This is a valid usecase of deducing a type to be a raw pointer and should
+  // not trigger a warning / error.
+  auto lambda = [foo_ptr = &foo] { return *foo_ptr; };
+}
+
+template <class T>
+struct WithDependentType {
+  void func() {
+    // The deduced type here is not known and `isNull()` will be true when
+    // parsing the template (but not instantiations).
+    auto x = T::foo();
+  }
+};
+
+void use_template() {
+  struct S {
+    static int* foo() { return nullptr; };
+  };
+  // The dependent type is instantiated, but no errors are produced.
+  WithDependentType<S>().func();
+}
+
+template <class T>
+concept Concept = true;
+
+void use_concept() {
+  int x = 0;
+  // No warning, because this is a constrained auto. Being a pointer or not is
+  // an implementation detail of the matching type.
+  Concept auto c = &x;
+}
diff --git a/tools/clang/plugins/tests/auto_raw_pointer_no_typedefs.txt b/tools/clang/plugins/tests/auto_raw_pointer_no_typedefs.txt
new file mode 100644
index 0000000..afca2ae
--- /dev/null
+++ b/tools/clang/plugins/tests/auto_raw_pointer_no_typedefs.txt
@@ -0,0 +1,77 @@
+auto_raw_pointer_no_typedefs.cpp:36:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  auto raw_int_ptr = &integer;
+  ^~~~
+  auto*
+auto_raw_pointer_no_typedefs.cpp:37:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  const auto const_raw_int_ptr = &integer;
+  ^~~~~~~~~~
+  auto* const
+auto_raw_pointer_no_typedefs.cpp:38:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  const auto& const_raw_int_ptr_ref = &integer;
+  ^~~~~~~~~~~
+  auto* const
+auto_raw_pointer_no_typedefs.cpp:43:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  auto raw_foo_ptr = &foo;
+  ^~~~
+  auto*
+auto_raw_pointer_no_typedefs.cpp:44:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  const auto const_raw_foo_ptr = &foo;
+  ^~~~~~~~~~
+  auto* const
+auto_raw_pointer_no_typedefs.cpp:45:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  const auto& const_raw_foo_ptr_ref = &foo;
+  ^~~~~~~~~~~
+  auto* const
+auto_raw_pointer_no_typedefs.cpp:52:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  auto double_ptr_auto = &int_ptr;
+  ^~~~
+  auto**
+auto_raw_pointer_no_typedefs.cpp:53:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  auto* double_ptr_auto_ptr = &int_ptr;
+  ^~~~~
+  auto**
+auto_raw_pointer_no_typedefs.cpp:60:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  auto auto_awesome = pointer_awesomeness;
+  ^~~~
+  auto* const* const volatile** const*
+auto_raw_pointer_no_typedefs.cpp:62:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  auto& int_ptr_ref = int_ptr;
+  ^~~~~
+  auto*&
+auto_raw_pointer_no_typedefs.cpp:63:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  const auto& const_int_ptr_ref = int_ptr;
+  ^~~~~~~~~~~
+  auto* const
+auto_raw_pointer_no_typedefs.cpp:64:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  auto&& int_ptr_rref = static_cast<int*&&>(int_ptr);
+  ^~~~~~
+  auto*&&
+auto_raw_pointer_no_typedefs.cpp:65:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  const auto&& const_int_ptr_rref = static_cast<int*&&>(int_ptr);
+  ^~~~~~~~~~~~
+  auto* const
+auto_raw_pointer_no_typedefs.cpp:67:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  static auto static_ptr = new int;
+  ^~~~~~~~~~~
+  static auto*
+auto_raw_pointer_no_typedefs.cpp:70:8: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  for (auto& it : iterable) {
+       ^~~~~
+       auto* const
+auto_raw_pointer_no_typedefs.cpp:76:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  auto alias = return_alias_with_ptr();
+  ^~~~
+  auto*
+auto_raw_pointer_no_typedefs.cpp:80:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  auto* good_ptr_to_alias = return_ptr_to_alias_with_ptr();
+  ^~~~~
+  auto**
+auto_raw_pointer_no_typedefs.cpp:82:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  auto bad_ptr_to_alias = return_ptr_to_alias_with_ptr();
+  ^~~~
+  auto**
+auto_raw_pointer_no_typedefs.cpp:113:3: warning: [chromium-style] auto variable type must not deduce to a raw pointer type.
+  Concept auto c = &x;
+  ^~~~~~~~~~~~
+  auto*
+19 warnings generated.
diff --git a/tools/json_schema_compiler/idl_schema.py b/tools/json_schema_compiler/idl_schema.py
index 1a37745..292d0cb3 100755
--- a/tools/json_schema_compiler/idl_schema.py
+++ b/tools/json_schema_compiler/idl_schema.py
@@ -123,23 +123,37 @@
       if parameter['name'] in self.comment:
         parameter['description'] = self.comment[parameter['name']]
       parameters.append(parameter)
-    # All functions with an asynchronous return are defined with a trailing
-    # callback in their parameters. For promise supporting functions, pull off
-    # the callback and put it into the separate returns async field.
+    # At the moment all functions in IDL with an asynchronous return are defined
+    # with a trailing callback in their parameters, but in our schema model we
+    # represent this with a separate returns 'async_field'. If there is a
+    # trailing callback, pop it off into the returns asyc property.
     # Note: We only do this for interface types of 'Functions' and 'Properties',
-    # not for 'Events' and callback definitions which have function parameters
-    # that can be called multiple times.
+    # not for 'Events' and IDL callback definitions (specified by the
+    # |use_returns_async parameter|) or for Function definitions with trailing
+    # callbacks which are not asynchronous returns (specified by the
+    # trailingCallbackIsFunctionParameter extended attribute).
+    # TODO(tjudkins): Once IDL definitions are changed to describe returning
+    # promises, we can condition on that instead.
     if (
         use_returns_async
+        and not self.node.GetProperty('trailingCallbackIsFunctionParameter')
         and len(parameters) > 0
         and parameters[-1].get('type') == 'function'
     ):
-      supports_promises = not self.node.GetProperty('doesNotSupportPromises')
-      if supports_promises:
-        returns_async = parameters.pop()
-        # The returns_async field is inherently a function, so doesn't need type
-        # specified on it.
-        returns_async.pop('type')
+      returns_async = parameters.pop()
+      # The returns_async field is inherently a function, so doesn't need type
+      # specified on it.
+      returns_async.pop('type')
+      does_not_support_promises = self.node.GetProperty(
+          'doesNotSupportPromises'
+      )
+      if does_not_support_promises is not None:
+        returns_async['does_not_support_promises'] = does_not_support_promises
+      else:
+        assert return_type is None, (
+            'Function "%s" cannot support promises and also have a '
+            'return value.' % self.node.GetName()
+        )
     else:
       assert not self.node.GetProperty('doesNotSupportPromises'), (
           'Callspec "%s" does not need to specify [doesNotSupportPromises] if '
@@ -263,9 +277,6 @@
           if return_type is not None:
             properties['returns'] = return_type
           if returns_async is not None:
-            assert return_type is None, (
-                'Function "%s" cannot support promises and also have a '
-                'return value.' % name)
             properties['returns_async'] = returns_async
 
     properties['name'] = name
@@ -502,8 +513,8 @@
     members = []
     # Callspec definitions for Functions and Properties with an asynchronous
     # return are defined with a trailing callback, but during parsing we move
-    # the details to a returns_async field. We need to pass this info along so
-    # we don't do this for Event definitions or callback definitions themselves.
+    # the details to a returns_async field. We only want to do this for Function
+    # and Property definitions, not for Event or IDL callback definitions.
     # TODO(tjudkins): Once IDL definitions are changed to describe returning
     # promises, we can condition on that rather than this special casing here.
     use_returns_async = node.GetName() in ['Functions', 'Properties']
diff --git a/tools/json_schema_compiler/idl_schema_test.py b/tools/json_schema_compiler/idl_schema_test.py
index 533e192..6538d15 100755
--- a/tools/json_schema_compiler/idl_schema_test.py
+++ b/tools/json_schema_compiler/idl_schema_test.py
@@ -447,33 +447,38 @@
   def testFunctionWithoutPromiseSupport(self):
     schema = idl_schema.Load('test/idl_function_types.idl')[0]
 
+    expected_params = []
+    expected_returns_async = {
+        'name': 'callback',
+        'parameters': [{'name': 'x', 'type': 'integer'}],
+        'does_not_support_promises': 'Test'
+    }
     params = getParams(schema, 'non_promise_supporting')
-    expected = [{
-            'name': 'callback',
-            'type': 'function',
-            'parameters': [{'name': 'x', 'type': 'integer'}]
-    }]
-    self.assertEqual(expected, params)
-    self.assertFalse(getReturnsAsync(schema, 'non_promise_supporting'))
+    returns_async = getReturnsAsync(schema, 'non_promise_supporting')
+
+    self.assertEqual(expected_params, params)
+    self.assertEqual(expected_returns_async, returns_async)
 
   def testFunctionWithoutPromiseSupportAndParams(self):
     schema = idl_schema.Load('test/idl_function_types.idl')[0]
 
-    params = getParams(schema, 'non_promise_supporting_with_params')
-    expected = [
+    expected_params = [
         {'name': 'z', 'type': 'integer'},
-        {'name': 'y', 'choices': [{'type': 'integer'}, {'type': 'string'}]},
-        {
-            'name': 'callback',
-            'type': 'function',
-            'parameters': [{'name': 'x', 'type': 'integer'}],
-        }
+        {'name': 'y', 'choices': [{'type': 'integer'}, {'type': 'string'}]}
     ]
-    self.assertEqual(expected, params)
-    self.assertFalse(
-        getReturnsAsync(schema, 'non_promise_supporting_with_params')
+    expected_returns_async = {
+        'name': 'callback',
+        'parameters': [{'name': 'x', 'type': 'integer'}],
+        'does_not_support_promises': 'Test'
+    }
+    params = getParams(schema, 'non_promise_supporting_with_params')
+    returns_async = getReturnsAsync(
+        schema, 'non_promise_supporting_with_params'
     )
 
+    self.assertEqual(expected_params, params)
+    self.assertEqual(expected_returns_async, returns_async)
+
   def testProperties(self):
     schema = idl_schema.Load('test/idl_properties.idl')[0]
     self.assertEqual(OrderedDict([
diff --git a/tools/json_schema_compiler/model.py b/tools/json_schema_compiler/model.py
index c801d9b1..b8246ce 100644
--- a/tools/json_schema_compiler/model.py
+++ b/tools/json_schema_compiler/model.py
@@ -333,9 +333,8 @@
              parameter is used for each choice of a 'choices' parameter
   - |deprecated| a reason and possible alternative for a deprecated function
   - |description| a description of the function (if provided)
-  - |returns_async| an asynchronous return for the function. This may be
-                    specified either though the returns_async field or a
-                    callback function at the end of the parameters, but not both
+  - |returns_async| an asynchronous return for the function. This is specified
+                    through the returns_async field
   - |optional| whether the Function is "optional"; this only makes sense to be
                present when the Function is representing a callback property
   - |simple_name| the name of this Function without a namespace
@@ -372,50 +371,31 @@
     self.filters = [GeneratePropertyFromParam(filter_instance)
                     for filter_instance in json.get('filters', [])]
 
+    # Any asynchronous return should be defined using the returns_async field.
     returns_async = json.get('returns_async', None)
     if returns_async:
-      returns_async_params = returns_async.get('parameters')
-      if (returns_async_params is None):
-        raise ValueError(
-            'parameters key not specified on returns_async: %s.%s in %s' %
-            (namespace.name, name, namespace.source_file))
-      if len(returns_async_params) > 1:
-        raise ValueError('Only a single parameter can be specific on '
-                         'returns_async: %s.%s in %s' %
-                         (namespace.name, name, namespace.source_file))
-      self.returns_async = ReturnsAsync(self, returns_async, namespace,
-                                        Origin(from_client=True), True)
+      self.returns_async = ReturnsAsync(
+          self,
+          returns_async,
+          namespace,
+          Origin(from_client=True),
+      )
       # TODO(https://crbug.com/1143032): Returning a synchronous value is
       # incompatible with returning a promise. There are APIs that specify this,
-      # though. Some appear to be incorrectly specified (i.e., don't return a
-      # value, but claim to), but others actually do return something. We'll
-      # need to handle those when converting them to allow promises.
-      if json.get('returns') is not None:
+      # though, so we make sure they have specified does_not_support_promises if
+      # they do.
+      if (
+          json.get('returns') is not None
+          and self.returns_async.can_return_promise
+      ):
         raise ValueError(
-            'Cannot specify both returns and returns_async: %s.%s'
-            % (namespace.name, name))
+            'Cannot specify both returns and returns_async on a function '
+            'which supports promies: %s.%s' % (namespace.name, name)
+        )
 
     params = json.get('parameters', [])
-    callback_param = None
     for i, param in enumerate(params):
-      # We consider the final function argument to the API to be the callback
-      # parameter if returns_async wasn't specified. Otherwise, we consider all
-      # function arguments to just be properties.
-      if i == len(params) - 1 and param.get(
-          'type') == 'function' and not self.returns_async:
-        callback_param = param
-      else:
-        # Treat all intermediate function arguments as properties. Certain APIs,
-        # such as the webstore, have these.
-        self.params.append(GeneratePropertyFromParam(param))
-
-    if callback_param:
-      # Even though we are creating a ReturnsAsync type here, this does not
-      # support being returned via a Promise, as this is implied by
-      # "returns_async" being found in the JSON.
-      # This is just a holder type for the callback.
-      self.returns_async = ReturnsAsync(self, callback_param, namespace,
-                                        Origin(from_client=True), False)
+      self.params.append(GeneratePropertyFromParam(param))
 
     self.returns = None
     if 'returns' in json:
@@ -441,23 +421,39 @@
              callbacks, or the list of properties on the returned object in the
              case of using promises
   - |can_return_promise| whether this can be treated as a Promise as well as
-                         callback
+                         callback. Currently only consumed for documentation
+                         purposes
   """
-  def __init__(self, parent, json, namespace, origin, can_return_promise):
+  def __init__(self, parent, json, namespace, origin):
     self.name = json.get('name')
     self.simple_name = _StripNamespace(self.name, namespace)
     self.description = json.get('description')
     self.optional = _GetWithDefaultChecked(parent, json, 'optional', False)
     self.nocompile = json.get('nocompile')
     self.parent = parent
-    self.can_return_promise = can_return_promise
+    self.can_return_promise = json.get('does_not_support_promises') is None
 
     if json.get('returns') is not None:
-      raise ValueError('Cannot return a value from an asynchronous return: '
-                       '%s.%s' % (namespace.name, self.name))
+      raise ValueError(
+          'Cannot return a value from an asynchronous return: %s.%s in %s'
+          % (namespace.name, parent.name, namespace.source_file)
+      )
     if json.get('deprecated') is not None:
-      raise ValueError('Cannot specify deprecated on an asynchronous return: '
-                       '%s.%s' % (namespace.name, self.name))
+      raise ValueError(
+          'Cannot specify deprecated on an asynchronous return: %s.%s in %s'
+          % (namespace.name, parent.name, namespace.source_file)
+      )
+    if json.get('parameters') is None:
+      raise ValueError(
+          'parameters key not specified on returns_async: %s.%s in %s'
+          % (namespace.name, parent.name, namespace.source_file)
+      )
+    if len(json.get('parameters')) > 1 and self.can_return_promise:
+      raise ValueError(
+          'Only a single parameter can be specific on a returns_async which'
+          ' supports promises: %s.%s in %s'
+          % (namespace.name, parent.name, namespace.source_file)
+      )
 
     def GeneratePropertyFromParam(p):
       return Property(self, p['name'], p, namespace, origin)
diff --git a/tools/json_schema_compiler/test/additional_properties.json b/tools/json_schema_compiler/test/additional_properties.json
index a40c479..f0cde94c 100644
--- a/tools/json_schema_compiler/test/additional_properties.json
+++ b/tools/json_schema_compiler/test/additional_properties.json
@@ -34,22 +34,20 @@
         "type": "function",
         "description": "Returns an object with additionalProperties.",
         "nodoc": "true",
-        "parameters": [
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {
-                "name": "resultObject",
-                "type": "object",
-                "properties": {
-                  "integer": {"type": "integer"}
-                },
-                "additionalProperties": {"type": "string"}
-              }
-            ]
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "resultObject",
+              "type": "object",
+              "properties": {
+                "integer": {"type": "integer"}
+              },
+              "additionalProperties": {"type": "string"}
+            }
+          ]
+        }
       }
     ]
   }
diff --git a/tools/json_schema_compiler/test/any.json b/tools/json_schema_compiler/test/any.json
index d836a04..55790549 100644
--- a/tools/json_schema_compiler/test/any.json
+++ b/tools/json_schema_compiler/test/any.json
@@ -24,31 +24,29 @@
             "type": "any",
             "name": "anyName",
             "optional": true
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "returnAny",
         "type": "function",
         "description": "Returns any.",
         "nodoc": "true",
-        "parameters": [
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {
-                "name": "result",
-                "type": "any"
-              }
-            ]
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "type": "function",
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "result",
+              "type": "any"
+            }
+          ]
+        }
       }
     ],
     "events": [
diff --git a/tools/json_schema_compiler/test/arrays.json b/tools/json_schema_compiler/test/arrays.json
index 3dfeeb890..709d84a6 100644
--- a/tools/json_schema_compiler/test/arrays.json
+++ b/tools/json_schema_compiler/test/arrays.json
@@ -103,13 +103,12 @@
             "name": "nums",
             "type": "array",
             "items": {"type": "integer"}
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "anyArray",
@@ -120,13 +119,12 @@
             "name": "anys",
             "type": "array",
             "items": {"type": "any"}
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "objectArray",
@@ -140,13 +138,12 @@
               "type": "object",
               "additionalProperties": {"type": "integer"}
             }
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "refArray",
@@ -157,13 +154,12 @@
             "name": "refs",
             "type": "array",
             "items": {"$ref": "Item"}
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "justChoices",
@@ -179,13 +175,12 @@
                 "items": {"$ref": "Item"}
               }
             ]
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "choicesArray",
@@ -204,49 +199,44 @@
                 }
               ]
             }
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "returnIntegerArray",
         "type": "function",
         "description": "Returns some integers.",
-        "parameters": [
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-              {
-                "name": "integers",
-                "type": "array",
-                "items": {"type": "integer"}
-              }
-            ]
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "integers",
+              "type": "array",
+              "items": {"type": "integer"}
+            }
+          ]
+        }
       },
       {
         "name": "returnRefArray",
         "type": "function",
         "description": "Returns some Items.",
-        "parameters": [
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-              {
-                "name": "refs",
-                "type": "array",
-                "items": {"$ref": "Item"}
-              }
-            ]
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "refs",
+              "type": "array",
+              "items": {"$ref": "Item"}
+            }
+          ]
+        }
       }
     ]
   }
diff --git a/tools/json_schema_compiler/test/callbacks.json b/tools/json_schema_compiler/test/callbacks.json
index dbd03c3..33a97a1 100644
--- a/tools/json_schema_compiler/test/callbacks.json
+++ b/tools/json_schema_compiler/test/callbacks.json
@@ -1,7 +1,7 @@
 [
   {
     "namespace": "callbacks",
-    "description": "The callbacks API.",
+    "description": "The callbacks API. Tests asynchronous returns",
     "types": [
       {
         "id": "Enumeration",
@@ -14,61 +14,56 @@
         "name": "returnsNothing",
         "type": "function",
         "description": "Takes nothing. Returns nothing.",
-        "parameters": [
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "returnsObject",
         "description": "Returns an object.",
         "type": "function",
-        "parameters": [
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-              {
-                "name": "someObject",
-                "type": "object",
-                "properties": {
-                  "state": {
-                    "$ref": "Enumeration"
-                  }
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "someObject",
+              "type": "object",
+              "properties": {
+                "state": {
+                  "$ref": "Enumeration"
                 }
               }
-            ]
-          }
-        ]
+            }
+          ]
+        }
       },
       {
         "name": "returnsMultiple",
-        "description": "Returns an object.",
+        "description": "Returns an integer and an object.",
         "type": "function",
-        "parameters": [
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-              {
-                "name": "someInteger",
-                "type": "integer"
-              },
-              {
-                "name": "someObject",
-                "type": "object",
-                "properties": {
-                  "state": {
-                    "$ref": "Enumeration"
-                  }
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "does_not_support_promises": "Multi-parameter callback for test",
+          "parameters": [
+            {
+              "name": "someInteger",
+              "type": "integer"
+            },
+            {
+              "name": "someObject",
+              "type": "object",
+              "properties": {
+                "state": {
+                  "$ref": "Enumeration"
                 }
               }
-            ]
-          }
-        ]
+            }
+          ]
+        }
       }
     ]
   }
diff --git a/tools/json_schema_compiler/test/choices.json b/tools/json_schema_compiler/test/choices.json
index e7e39e0..9d66bd8a 100644
--- a/tools/json_schema_compiler/test/choices.json
+++ b/tools/json_schema_compiler/test/choices.json
@@ -51,13 +51,12 @@
                {"type": "array", "items": {"type": "integer", "minimum": 0}},
                {"type": "integer"}
              ]
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "takesIntegersOptional",
@@ -71,13 +70,12 @@
                {"type": "integer"}
             ],
             "optional": true
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "objectWithChoices",
@@ -104,63 +102,59 @@
                  "optional": true
                }
              }
-           },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
-          }
-        ]
+           }
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "returnChoices",
         "type": "function",
         "description": "Gives back a string. Or not.",
-        "parameters": [
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-              {
-                "name": "result",
-                "choices": [
-                   {"type": "array", "items": {"type": "integer", "minimum": 0}},
-                   {"type": "integer"}
-                 ],
-                "description": "Some integers."
-              }
-            ]
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "result",
+              "choices": [
+                 {"type": "array", "items": {"type": "integer", "minimum": 0}},
+                 {"type": "integer"}
+               ],
+              "description": "Some integers."
+            }
+          ]
+        }
       },
       {
         "name": "returnMultipleChoices",
         "type": "function",
         "description": "Gives back two values where each is an integer or a list of integers.",
-        "parameters": [
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-              {
-                "name": "firstResult",
-                "choices": [
-                   {"type": "array", "items": {"type": "integer", "minimum": 0}},
-                   {"type": "integer"}
-                 ],
-                "description": "Some integers."
-              },
-              {
-                "name": "secondResult",
-                "choices": [
-                   {"type": "array", "items": {"type": "integer", "minimum": 0}},
-                   {"type": "integer"}
-                 ],
-                "description": "Some integers."
-              }
-            ]
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "does_not_support_promises": "Multi-parameter callback for test",
+          "parameters": [
+            {
+              "name": "firstResult",
+              "choices": [
+                 {"type": "array", "items": {"type": "integer", "minimum": 0}},
+                 {"type": "integer"}
+               ],
+              "description": "Some integers."
+            },
+            {
+              "name": "secondResult",
+              "choices": [
+                 {"type": "array", "items": {"type": "integer", "minimum": 0}},
+                 {"type": "integer"}
+               ],
+              "description": "Some integers."
+            }
+          ]
+        }
       }
     ]
   }
diff --git a/tools/json_schema_compiler/test/crossref.json b/tools/json_schema_compiler/test/crossref.json
index ca67a46..4dba01c 100644
--- a/tools/json_schema_compiler/test/crossref.json
+++ b/tools/json_schema_compiler/test/crossref.json
@@ -35,31 +35,28 @@
             "name": "testType",
             "$ref": "simple_api.TestType",
             "optional": true
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+      "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "getTestType",
         "type": "function",
         "description": "Return a TestType.",
-        "parameters": [
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-              {
-                "name": "result",
-                "$ref": "simple_api.TestType",
-                "description": "A TestType."
-              }
-            ]
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "result",
+              "$ref": "simple_api.TestType",
+              "description": "A TestType."
+            }
+          ]
+        }
       },
       {
         "name": "testTypeInObject",
@@ -73,13 +70,12 @@
               "testType": {"$ref": "simple_api.TestType", "optional": true},
               "boolean": {"type": "boolean"}
             }
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       }
     ]
   }
diff --git a/tools/json_schema_compiler/test/enums.json b/tools/json_schema_compiler/test/enums.json
index c60b4b2d..2873e72 100644
--- a/tools/json_schema_compiler/test/enums.json
+++ b/tools/json_schema_compiler/test/enums.json
@@ -78,13 +78,12 @@
           {
             "name": "state",
             "$ref": "Enumeration"
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "takesEnumArray",
@@ -97,13 +96,12 @@
             "items": {
               "$ref": "Enumeration"
             }
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "takesEnumAsType",
@@ -113,13 +111,12 @@
           {
             "name": "enumeration",
             "$ref": "Enumeration"
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "takesEnumArrayAsType",
@@ -132,68 +129,62 @@
             "items": {
               "$ref": "Enumeration"
             }
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "returnsEnum",
         "type": "function",
         "description": "Returns an enum through the callback",
-        "parameters": [
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-              {
-                "name": "state",
-                "$ref": "Enumeration"
-              }
-            ]
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "state",
+              "$ref": "Enumeration"
+            }
+          ]
+        }
       },
       {
         "name": "returnsEnumAsType",
         "type": "function",
         "description": "Returns an enum through the callback",
-        "parameters": [
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-              {
-                "name": "enumeration",
-                "$ref": "Enumeration"
-              }
-            ]
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "enumeration",
+              "$ref": "Enumeration"
+            }
+          ]
+        }
       },
       {
         "name": "returnsTwoEnums",
         "type": "function",
         "description": "Returns two enums through the callback",
-        "parameters": [
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-              {
-                "name": "firstState",
-                "$ref": "Enumeration"
-              },
-              {
-                "name": "secondState",
-                "$ref": "OtherEnumeration"
-              }
-            ]
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "does_not_support_promises": "Multi-parameter callback for test",
+          "parameters": [
+            {
+              "name": "firstState",
+              "$ref": "Enumeration"
+            },
+            {
+              "name": "secondState",
+              "$ref": "OtherEnumeration"
+            }
+          ]
+        }
       },
       {
         "name": "takesOptionalEnum",
@@ -204,13 +195,12 @@
             "name": "state",
             "$ref": "Enumeration",
             "optional": true
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "takesMultipleOptionalEnums",
@@ -226,13 +216,12 @@
             "name": "type",
             "$ref": "OtherEnumeration",
             "optional": true
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       }
     ],
     "events": [
diff --git a/tools/json_schema_compiler/test/functions_on_types.json b/tools/json_schema_compiler/test/functions_on_types.json
index a20a75f..e1077fd 100644
--- a/tools/json_schema_compiler/test/functions_on_types.json
+++ b/tools/json_schema_compiler/test/functions_on_types.json
@@ -25,22 +25,21 @@
                 ],
                 "description": "A single key to get, list of keys to get, or a dictionary specifying default values (see description of the object).  An empty list or object will return an empty result object.  Pass in <code>null</code> to get the entire contents of storage.",
                 "optional": true
-              },
-              {
-                "name": "callback",
-                "type": "function",
-                "description": "Callback with storage items, or on failure (in which case lastError will be set).",
-                "parameters": [
-                  {
-                    "name": "items",
-                    "type": "object",
-                    "properties": {},
-                    "additionalProperties": { "type": "any" },
-                    "description": "Object with items in their key-value mappings."
-                  }
-                ]
               }
-            ]
+            ],
+            "returns_async": {
+              "name": "callback",
+              "description": "Callback with storage items, or on failure (in which case lastError will be set).",
+              "parameters": [
+                {
+                  "name": "items",
+                  "type": "object",
+                  "properties": {},
+                  "additionalProperties": { "type": "any" },
+                  "description": "Object with items in their key-value mappings."
+                }
+              ]
+            }
           }
         ]
       },
diff --git a/tools/json_schema_compiler/test/objects.json b/tools/json_schema_compiler/test/objects.json
index dd0bb207..6097ad6 100644
--- a/tools/json_schema_compiler/test/objects.json
+++ b/tools/json_schema_compiler/test/objects.json
@@ -35,66 +35,62 @@
                 "type": "boolean"
               }
             }
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "returnsObject",
         "description": "Returns an object.",
         "type": "function",
-        "parameters": [
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-              {
-                "name": "info",
-                "type": "object",
-                "properties": {
-                  "state": {
-                    "$ref": "firstState"
-                  }
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "info",
+              "type": "object",
+              "properties": {
+                "state": {
+                  "$ref": "firstState"
                 }
               }
-            ]
-          }
-        ]
+            }
+          ]
+        }
       },
       {
         "name": "returnsTwoObjects",
         "description": "Return two objects.",
         "type": "function",
-        "parameters": [
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-              {
-                "name": "firstInfo",
-                "type": "object",
-                "properties": {
-                  "state": {
-                    "$ref": "firstState"
-                  }
-                }
-              },
-              {
-                "name": "secondInfo",
-                "type": "object",
-                "properties": {
-                  "state": {
-                    "$ref": "secondState"
-                  }
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "does_not_support_promises": "Multi-parameter callback for test",
+          "parameters": [
+            {
+              "name": "firstInfo",
+              "type": "object",
+              "properties": {
+                "state": {
+                  "$ref": "firstState"
                 }
               }
-            ]
-          }
-        ]
+            },
+            {
+              "name": "secondInfo",
+              "type": "object",
+              "properties": {
+                "state": {
+                  "$ref": "secondState"
+                }
+              }
+            }
+          ]
+        }
       }
     ],
     "events": [
diff --git a/tools/json_schema_compiler/test/permissions.json b/tools/json_schema_compiler/test/permissions.json
index df97441..74671a0f 100644
--- a/tools/json_schema_compiler/test/permissions.json
+++ b/tools/json_schema_compiler/test/permissions.json
@@ -53,19 +53,17 @@
         "name": "getAll",
         "type": "function",
         "description": "Gets the extension's current set of permissions.",
-        "parameters": [
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-               {
-                "name": "permissions",
-                "$ref": "Permissions",
-                "description": "The extension's active permissions."
-              }
-            ]
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+             {
+              "name": "permissions",
+              "$ref": "Permissions",
+              "description": "The extension's active permissions."
+            }
+          ]
+        }
       },
       {
         "name": "contains",
@@ -75,19 +73,18 @@
           {
             "name": "permissions",
             "$ref": "Permissions"
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-              {
-                "name": "result",
-                "type": "boolean",
-                "description": "True if the extension has the specified permissions."
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "result",
+              "type": "boolean",
+              "description": "True if the extension has the specified permissions."
+            }
+          ]
+        }
       },
       {
         "name": "request",
@@ -97,20 +94,19 @@
           {
             "name": "permissions",
             "$ref": "Permissions"
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "optional": true,
-            "parameters": [
-              {
-                "name": "granted",
-                "type": "boolean",
-                "description": "True if the user granted the specified permissions."
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": [
+            {
+              "name": "granted",
+              "type": "boolean",
+              "description": "True if the user granted the specified permissions."
+            }
+          ]
+        }
       },
       {
         "name": "remove",
@@ -120,20 +116,19 @@
           {
             "name": "permissions",
             "$ref": "Permissions"
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "optional": true,
-            "parameters": [
-              {
-                "name": "removed",
-                "type": "boolean",
-                "description": "True if the permissions were removed."
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": [
+            {
+              "name": "removed",
+              "type": "boolean",
+              "description": "True if the permissions were removed."
+            }
+          ]
+        }
       }
     ]
   }
diff --git a/tools/json_schema_compiler/test/returns_async.json b/tools/json_schema_compiler/test/returns_async.json
index d0b2e6d8..4b0cfd5b 100644
--- a/tools/json_schema_compiler/test/returns_async.json
+++ b/tools/json_schema_compiler/test/returns_async.json
@@ -33,23 +33,22 @@
         "name": "doesNotSupportPromises",
         "description": "Returns an object via callback.",
         "type": "function",
-        "parameters": [
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-              {
-                "name": "someObject",
-                "type": "object",
-                "properties": {
-                  "state": {
-                    "$ref": "Enumeration"
-                  }
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "does_not_support_promises": "For testing",
+          "parameters": [
+            {
+              "name": "someObject",
+              "type": "object",
+              "properties": {
+                "state": {
+                  "$ref": "Enumeration"
                 }
               }
-            ]
-          }
-        ]
+            }
+          ]
+        }
       }
     ]
   }
diff --git a/tools/json_schema_compiler/test/simple_api.json b/tools/json_schema_compiler/test/simple_api.json
index 7ea5355e..3006571 100644
--- a/tools/json_schema_compiler/test/simple_api.json
+++ b/tools/json_schema_compiler/test/simple_api.json
@@ -107,19 +107,18 @@
           {
             "name": "num",
             "type": "integer"
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-              {
-                "name": "result",
-                "type": "integer",
-                "description": "The incremented value."
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "result",
+              "type": "integer",
+              "description": "The incremented value."
+            }
+          ]
+        }
       },
       {
         "name": "optionalString",
@@ -130,13 +129,12 @@
             "name": "str",
             "type": "string",
             "optional": true
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "optionalBeforeRequired",
@@ -151,13 +149,12 @@
           {
             "name": "second",
             "type": "string"
-          },
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": []
+        }
       },
       {
         "name": "requiredFunctionParameter",
@@ -179,37 +176,33 @@
         "name": "optionalCallbackParams",
         "type": "function",
         "description": "Gives back a string. Or not.",
-        "parameters": [
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-              {
-                "name": "result",
-                "$ref": "TestType",
-                "description": "True if the extension has the specified permissions."
-              }
-            ]
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "result",
+              "$ref": "TestType",
+              "description": "True if the extension has the specified permissions."
+            }
+          ]
+        }
       },
       {
         "name": "getTestType",
         "type": "function",
         "description": "Return a TestType.",
-        "parameters": [
-          {
-            "name": "callback",
-            "type": "function",
-            "parameters": [
-              {
-                "name": "result",
-                "$ref": "TestType",
-                "description": "A TestType."
-              }
-            ]
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "result",
+              "$ref": "TestType",
+              "description": "A TestType."
+            }
+          ]
+        }
       }
     ],
     "events": [
diff --git a/tools/json_schema_compiler/test/tabs.json b/tools/json_schema_compiler/test/tabs.json
index d75e110e..e03e6b78 100644
--- a/tools/json_schema_compiler/test/tabs.json
+++ b/tools/json_schema_compiler/test/tabs.json
@@ -32,33 +32,30 @@
             "type": "integer",
             "name": "tabId",
             "minimum": 0
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {"name": "tab", "$ref": "Tab"}
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {"name": "tab", "$ref": "Tab"}
+          ]
+        }
       },
       {
         "name": "getCurrent",
         "type": "function",
         "description": "Gets the tab that this script call is being made from. May be undefined if called from a non-tab context (for example: a background page or popup view).",
-        "parameters": [
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {
-                "name": "tab",
-                "$ref": "Tab",
-                "optional": true
-              }
-            ]
-          }
-        ]
+        "parameters": [],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "tab",
+              "$ref": "Tab",
+              "optional": true
+            }
+          ]
+        }
       },
       {
         "name": "connect",
@@ -99,20 +96,19 @@
           {
             "type": "any",
             "name": "request"
-          },
-          {
-            "type": "function",
-            "name": "responseCallback",
-            "optional": true,
-            "parameters": [
-              {
-                "name": "response",
-                "type": "any",
-                "description": "The JSON response object sent by the handler of the request. If an error occurs while connecting to the specified tab, the callback will be called with no arguments and <a href='extension.html#property-lastError'>chrome.runtime.lastError</a> will be set to the error message."
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "responseCallback",
+          "optional": true,
+          "parameters": [
+            {
+              "name": "response",
+              "type": "any",
+              "description": "The JSON response object sent by the handler of the request. If an error occurs while connecting to the specified tab, the callback will be called with no arguments and <a href='extension.html#property-lastError'>chrome.runtime.lastError</a> will be set to the error message."
+            }
+          ]
+        }
       },
       {
         "name": "getSelected",
@@ -126,15 +122,14 @@
             "minimum": 0,
             "optional": true,
             "description": "Defaults to the <a href='windows.html#current-window'>current window</a>."
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {"name": "tab", "$ref": "Tab"}
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {"name": "tab", "$ref": "Tab"}
+          ]
+        }
       },
       {
         "name": "getAllInWindow",
@@ -148,15 +143,14 @@
             "minimum": 0,
             "optional": true,
             "description": "Defaults to the <a href='windows.html#current-window'>current window</a>."
-            },
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {"name": "tabs", "type": "array", "items": { "$ref": "Tab" } }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {"name": "tabs", "type": "array", "items": { "$ref": "Tab" } }
+          ]
+        }
       },
       {
         "name": "create",
@@ -201,20 +195,19 @@
                 "description": "Whether the tab should be pinned. Defaults to <var>false</var>"
               }
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "parameters": [
-              {
-                "name": "tab",
-                "$ref": "Tab",
-                "description": "Details about the created tab. Will contain the ID of the new tab."
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": [
+            {
+              "name": "tab",
+              "$ref": "Tab",
+              "description": "Details about the created tab. Will contain the ID of the new tab."
+            }
+          ]
+        }
       },
       {
         "name": "query",
@@ -269,21 +262,20 @@
                 "description": "The type of window the tabs are in."
               }
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {
-                "name": "result",
-                "type": "array",
-                "items": {
-                  "$ref": "Tab"
-                }
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "result",
+              "type": "array",
+              "items": {
+                "$ref": "Tab"
+              }
+            }
+          ]
+        }
       },
       {
         "name": "highlight",
@@ -306,19 +298,18 @@
                  ]
                }
              }
-           },
-           {
-             "type": "function",
-             "name": "callback",
-             "parameters": [
-               {
-                 "name": "window",
-                 "$ref": "Window",
-                 "description": "Contains details about the window whose tabs were highlighted."
-               }
-             ]
            }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "window",
+              "$ref": "Window",
+              "description": "Contains details about the window whose tabs were highlighted."
+            }
+          ]
+        }
       },
       {
         "name": "update",
@@ -363,21 +354,20 @@
                 "description": "Whether the tab should be pinned."
               }
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "parameters": [
-              {
-                "name": "tab",
-                "$ref": "Tab",
-                "optional": true,
-                "description": "Details about the updated tab, or <code>null</code> if the 'tabs' permission has not been requested."
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": [
+            {
+              "name": "tab",
+              "$ref": "Tab",
+              "optional": true,
+              "description": "Details about the updated tab, or <code>null</code> if the 'tabs' permission has not been requested."
+            }
+          ]
+        }
       },
       {
         "name": "move",
@@ -408,23 +398,22 @@
                 "description": "The position to move the window to. The provided value will be clamped to between zero and the number of tabs in the window."
               }
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "parameters": [
-              {
-                "name": "tabs",
-                "description": "Details about the moved tabs.",
-                "choices": [
-                  {"$ref": "Tab"},
-                  {"type": "array", "items": {"$ref": "Tab"}}
-                ]
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": [
+            {
+              "name": "tabs",
+              "description": "Details about the moved tabs.",
+              "choices": [
+                {"$ref": "Tab"},
+                {"type": "array", "items": {"$ref": "Tab"}}
+              ]
+            }
+          ]
+        }
       },
       {
         "name": "reload",
@@ -443,9 +432,13 @@
                 "description": "Whether using any local cache. Default is false."
               }
             }
-          },
-          {"type": "function", "name": "callback", "optional": true, "parameters": []}
-        ]
+          }
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": []
+        }
       },
       {
         "name": "remove",
@@ -459,9 +452,13 @@
               {"type": "integer", "minimum": 0},
               {"type": "array", "items": {"type": "integer", "minimum": 0}}
             ]
-          },
-          {"type": "function", "name": "callback", "optional": true, "parameters": []}
-        ]
+          }
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": []
+        }
       },
       {
         "name": "detectLanguage",
@@ -474,19 +471,18 @@
             "minimum": 0,
             "optional": true,
             "description": "Defaults to the active tab of the <a href='windows.html#current-window'>current window</a>."
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {
-                "type": "string",
-                "name": "language",
-                "description": "An ISO language code such as <code>en</code> or <code>fr</code>. For a complete list of languages supported by this method, see <a href='http://src.chromium.org/viewvc/chrome/trunk/src/third_party/cld/languages/internal/languages.cc'>kLanguageInfoTable</a>. The 2nd to 4th columns will be checked and the first non-NULL value will be returned except for Simplified Chinese for which zh-CN will be returned. For an unknown language, <code>und</code> will be returned."
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "type": "string",
+              "name": "language",
+              "description": "An ISO language code such as <code>en</code> or <code>fr</code>. For a complete list of languages supported by this method, see <a href='http://src.chromium.org/viewvc/chrome/trunk/src/third_party/cld/languages/internal/languages.cc'>kLanguageInfoTable</a>. The 2nd to 4th columns will be checked and the first non-NULL value will be returned except for Simplified Chinese for which zh-CN will be returned. For an unknown language, <code>und</code> will be returned."
+            }
+          ]
+        }
       },
       {
         "name": "captureVisibleTab",
@@ -521,13 +517,14 @@
                 "description": "When format is 'jpeg', controls the quality of the resulting image.  This value is ignored for PNG images.  As quality is decreased, the resulting image will have more visual artifacts, and the number of bytes needed to store it will decrease."
               }
             }
-          },
-          {
-            "type": "function", "name": "callback", "parameters": [
-              {"type": "string", "name": "dataUrl", "description": "A data URL which encodes an image of the visible area of the captured tab. May be assigned to the 'src' property of an HTML Image element for display."}
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {"type": "string", "name": "dataUrl", "description": "A data URL which encodes an image of the visible area of the captured tab. May be assigned to the 'src' property of an HTML Image element for display."}
+          ]
+        }
       },
       {
         "name": "executeScript",
@@ -544,15 +541,14 @@
               "file": {"type": "string", "optional": true, "description": "JavaScript file to execute."},
               "allFrames": {"type": "boolean", "optional": true, "description": "If allFrames is true, this function injects script into all frames of current page. By default, it's false and script is injected only into the top main frame."}
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "description": "Called after all the JavaScript has been executed.",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "description": "Called after all the JavaScript has been executed.",
+          "parameters": []
+        }
       },
       {
         "name": "insertCSS",
@@ -569,15 +565,14 @@
               "file": {"type": "string", "optional": true, "description": "CSS file to be injected."},
               "allFrames": {"type": "boolean", "optional": true, "description": "If allFrames is true, this function injects CSS text into all frames of current page. By default, it's false and CSS is injected only into the top main frame."}
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "description": "Called when all the CSS has been inserted.",
-            "parameters": []
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "description": "Called when all the CSS has been inserted.",
+          "parameters": []
+        }
       }
     ],
     "events": [
diff --git a/tools/json_schema_compiler/test/windows.json b/tools/json_schema_compiler/test/windows.json
index a3a3a46..0680b56 100644
--- a/tools/json_schema_compiler/test/windows.json
+++ b/tools/json_schema_compiler/test/windows.json
@@ -50,17 +50,16 @@
             "properties": {
               "populate": {"type": "boolean", "optional": true, "description": "If true, the window object will have a <var>tabs</var> property that contains a list of the $ref:Tab objects" }
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {
-                "name": "window", "$ref": "Window"
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "window", "$ref": "Window"
+            }
+          ]
+        }
       },
       {
         "name": "getCurrent",
@@ -75,17 +74,16 @@
             "properties": {
               "populate": {"type": "boolean", "optional": true, "description": "If true, the window object will have a <var>tabs</var> property that contains a list of the $ref:Tab objects" }
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {
-                "name": "window", "$ref": "Window"
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "window", "$ref": "Window"
+            }
+          ]
+        }
       },
       {
         "name": "getLastFocused",
@@ -100,17 +98,16 @@
             "properties": {
               "populate": {"type": "boolean", "optional": true, "description": "If true, the window object will have a <var>tabs</var> property that contains a list of the $ref:Tab objects" }
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {
-                "name": "window", "$ref": "Window"
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "window", "$ref": "Window"
+            }
+          ]
+        }
       },
       {
         "name": "getAll",
@@ -125,17 +122,16 @@
             "properties": {
               "populate": {"type": "boolean", "optional": true, "description": "If true, each window object will have a <var>tabs</var> property that contains a list of the $ref:Tab objects for that window." }
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {
-                "name": "windows", "type": "array", "items": { "$ref": "Window" }
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "parameters": [
+            {
+              "name": "windows", "type": "array", "items": { "$ref": "Window" }
+            }
+          ]
+        }
       },
       {
         "name": "create",
@@ -171,19 +167,18 @@
               }
             },
             "optional": true
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "parameters": [
-              {
-                "name": "window", "$ref": "Window", "description": "Contains details about the created window.",
-                "optional": true
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": [
+            {
+              "name": "window", "$ref": "Window", "description": "Contains details about the created window.",
+              "optional": true
+            }
+          ]
+        }
       },
       {
         "name": "update",
@@ -208,27 +203,34 @@
                 "enum": ["normal", "minimized", "maximized"]
               }
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "optional": true,
-            "parameters": [
-              {
-                "name": "window", "$ref": "Window"
-              }
-            ]
           }
-        ]
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": [
+            {
+              "name": "window", "$ref": "Window"
+            }
+          ]
+        }
       },
       {
         "name": "remove",
         "type": "function",
         "description": "Removes (closes) a window, and all the tabs inside it.",
         "parameters": [
-          {"type": "integer", "name": "windowId", "minimum": 0},
-          {"type": "function", "name": "callback", "optional": true, "parameters": []}
-        ]
+          {
+            "type": "integer",
+            "name": "windowId",
+            "minimum": 0
+          }
+        ],
+        "returns_async": {
+          "name": "callback",
+          "optional": true,
+          "parameters": []
+        }
       }
     ],
     "events": [
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index d950d1a9..3605a2f 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -10862,9 +10862,9 @@
       label="ServiceWorkerEventHandlerModifiedAfterInitialization"/>
   <int value="4470" label="AuthorizationCrossOrigin"/>
   <int value="4471" label="CSSColorMixFunction"/>
-  <int value="4472" label="OBSOLETE_CSSColorColorSpecifiedSpace"/>
-  <int value="4473" label="OBSOLETE_CSSColorLabOklab"/>
-  <int value="4474" label="OBSOLETE_CSSColorLchOklch"/>
+  <int value="4472" label="CSSColorColorSpecifiedSpace"/>
+  <int value="4473" label="CSSColorLabOklab"/>
+  <int value="4474" label="CSSColorLchOklch"/>
   <int value="4475" label="OBSOLETE_CreateNSResolverWithNonElements2"/>
   <int value="4476" label="GetDisplayMediaWithPreferCurrentTabTrue"/>
   <int value="4477" label="FencedFrameConfigAttribute"/>
@@ -11274,11 +11274,6 @@
   <int value="4831" label="SpeculationRulesBrowserPrefetchRule"/>
   <int value="4832" label="SpeculationRulesBrowserPrerenderRule"/>
   <int value="4833" label="FirstPartySharedWorkerSameSiteCookiesNone"/>
-  <int value="4834" label="CSSCustomStateDeprecatedSyntax"/>
-  <int value="4835" label="CSSColor_SpaceRGB"/>
-  <int value="4836" label="CSSColor_SpaceRGB_outOfRec2020"/>
-  <int value="4837" label="CSSColor_SpaceOkLxx"/>
-  <int value="4838" label="CSSColor_SpaceOkLxx_OutOfRange"/>
 </enum>
 
 <enum name="FeaturePolicyFeature">
@@ -29281,6 +29276,7 @@
   <int value="4" label="Failed, Invalid HTTP response code"/>
   <int value="5" label="Failed, Invalid change in network context"/>
   <int value="6" label="Failed, Redirect was ineligible"/>
+  <int value="7" label="Failed, Insufficient referrer policy"/>
 </enum>
 
 <enum name="PrefetchStreamingURLLoaderStatus">
diff --git a/tools/metrics/histograms/metadata/blink/histograms.xml b/tools/metrics/histograms/metadata/blink/histograms.xml
index cbccab9..ac03ec1 100644
--- a/tools/metrics/histograms/metadata/blink/histograms.xml
+++ b/tools/metrics/histograms/metadata/blink/histograms.xml
@@ -1763,7 +1763,7 @@
   <owner>web-identity-eng@google.com</owner>
   <summary>
     Records the time from when a call to the API was made to when the accounts
-    dialog is shown. Only records a sample when the dialog is shown.
+    dialog is ready to be shown. Only records a sample when the dialog is shown.
   </summary>
 </histogram>
 
@@ -1825,6 +1825,16 @@
   </summary>
 </histogram>
 
+<histogram name="Blink.FedCm.WebContentsActive" enum="Boolean"
+    expires_after="2024-06-19">
+  <owner>yigu@chromium.org</owner>
+  <owner>web-identity-eng@google.com</owner>
+  <summary>
+    Records whether the WebContents is active when the browser is ready to show
+    the accounts dialog to the user. Records at most one sample per API call.
+  </summary>
+</histogram>
+
 <histogram name="Blink.FedCm.WebContentsVisible" enum="Boolean"
     expires_after="2024-06-19">
   <owner>yigu@chromium.org</owner>
@@ -3193,7 +3203,7 @@
 </histogram>
 
 <histogram name="Blink.NotifyScriptLoaded.TabSearch" units="ms"
-    expires_after="2024-03-01">
+    expires_after="2025-01-01">
   <owner>dayeung@chromium.org</owner>
   <owner>tluk@chromium.org</owner>
   <owner>robliao@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/browser/histograms.xml b/tools/metrics/histograms/metadata/browser/histograms.xml
index 26d7905..43bbd4f 100644
--- a/tools/metrics/histograms/metadata/browser/histograms.xml
+++ b/tools/metrics/histograms/metadata/browser/histograms.xml
@@ -333,6 +333,37 @@
   </token>
 </histogram>
 
+<histogram
+    name="Browser.MainThreadsCongestion.ExtensionContentScripts.{NumExtensions}"
+    units="janks" expires_after="2024-06-02">
+  <owner>joenotcharles@google.com</owner>
+  <owner>catan-team@chromium.org</owner>
+  <summary>
+    This metric is emitted every 30 seconds after main message loop start, when
+    there is user activity. Each 30 second duration is divided into 100ms
+    intervals. This metric counts the number of these intervals that were
+    &quot;congested&quot;. An interval is congested if during it the UI or IO
+    thread executes an &quot;important&quot; task that was queued more than 100
+    ms ago, or a non-important task or a native event handler that started
+    executing more than 100 ms ago. See
+    https://docs.google.com/document/d/1vDSGFvJblh7yJ3U3RVB_7qZLubyfTbQdQjuN1GoUNkc/edit
+    for more details.
+
+    This metric is split by the estimated number of extensions that ran content
+    scripts in the pages that were visible during the interval. It is only
+    recorded if there were visible pages. This version is used for
+    {NumExtensions}.
+  </summary>
+  <token key="NumExtensions">
+    <variant name="0" summary="0 extensions"/>
+    <variant name="1" summary="1 extension"/>
+    <variant name="2" summary="2 or 3 extensions"/>
+    <variant name="4" summary="4 to 7 extensions"/>
+    <variant name="8" summary="8 to 15 extensions"/>
+    <variant name="16" summary="16 or more extensions"/>
+  </token>
+</histogram>
+
 <histogram name="Browser.MainThreadsCongestion.Used" units="janks"
     expires_after="2024-06-02">
   <owner>pmonette@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/chromeos/enums.xml b/tools/metrics/histograms/metadata/chromeos/enums.xml
index 38fd57b..eeb21658 100644
--- a/tools/metrics/histograms/metadata/chromeos/enums.xml
+++ b/tools/metrics/histograms/metadata/chromeos/enums.xml
@@ -626,6 +626,22 @@
   <int value="2" label="SAML Chrome Credentials Passing API not used"/>
 </enum>
 
+<enum name="ChromeOSSamlIncorrectAttestation">
+  <summary>
+    Different method that can repport Incorrect Attestation in SamlHandler in
+    ChromeOS.
+  </summary>
+  <int value="0" label="onBeforeRequest">
+    SamlHandler repport Incorrect Attestation in onBeforeRequest_.
+  </int>
+  <int value="1" label="onBeforeSendHeaders">
+    SamlHandler repport Incorrect Attestation in onBeforeSendHeaders_.
+  </int>
+  <int value="2" label="continueDelayedRedirect">
+    SamlHandler repport Incorrect Attestation in continueDelayedRedirect_.
+  </int>
+</enum>
+
 <enum name="ChromeOSSamlProvider">
   <summary>
     Different SAML providers that uses SAML login flow in ChromeOS.
diff --git a/tools/metrics/histograms/metadata/chromeos/histograms.xml b/tools/metrics/histograms/metadata/chromeos/histograms.xml
index 271f005..79e97b1d 100644
--- a/tools/metrics/histograms/metadata/chromeos/histograms.xml
+++ b/tools/metrics/histograms/metadata/chromeos/histograms.xml
@@ -2269,6 +2269,13 @@
   </summary>
 </histogram>
 
+<histogram name="ChromeOS.SAML.IncorrectAttestation"
+    enum="ChromeOSSamlIncorrectAttestation" expires_after="2024-07-28">
+  <owner>mohammedabdon@chromium.org</owner>
+  <owner>bchikhaoui@google.com</owner>
+  <summary>Recorded Incorrect Attestation in SamlHandler.</summary>
+</histogram>
+
 <histogram name="ChromeOS.SAML.InSessionPasswordChangeEvent"
     enum="SamlInSessionPasswordChangeEvent" expires_after="2024-06-30">
   <owner>mslus@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/compose/enums.xml b/tools/metrics/histograms/metadata/compose/enums.xml
index 8ce1f54..355f478 100644
--- a/tools/metrics/histograms/metadata/compose/enums.xml
+++ b/tools/metrics/histograms/metadata/compose/enums.xml
@@ -59,6 +59,13 @@
              suggestion"/>
 </enum>
 
+<enum name="ComposeRequestFeedback">
+  <int value="0" label="No feedback given"/>
+  <int value="1" label="Postivie feedback given"/>
+  <int value="2" label="Negative feedback given"/>
+  <int value="3" label="Request error"/>
+</enum>
+
 <enum name="ComposeRequestReason">
   <int value="0" label="Create button"/>
   <int value="1" label="Retry button"/>
diff --git a/tools/metrics/histograms/metadata/compose/histograms.xml b/tools/metrics/histograms/metadata/compose/histograms.xml
index 069642d..8e11c897 100644
--- a/tools/metrics/histograms/metadata/compose/histograms.xml
+++ b/tools/metrics/histograms/metadata/compose/histograms.xml
@@ -252,6 +252,17 @@
   </token>
 </histogram>
 
+<histogram name="Compose.{ComposeEvalLocation}Request.Feedback"
+    enum="ComposeRequestFeedback" expires_after="2024-07-02">
+  <owner>perrier@chromium.org</owner>
+  <owner>chrome-compose-frontend@google.com</owner>
+  <summary>
+    Records the final value of feedback for each Compose Request. Logged when
+    feedback can no longer be modified for a request. {ComposeEvalLocation}.
+  </summary>
+  <token key="ComposeEvalLocation" variants="ComposeEvalLocation"/>
+</histogram>
+
 <histogram name="Compose.{ComposeEvalLocation}Request.Status"
     enum="ComposeResponseStatus" expires_after="2024-06-02">
   <owner>cuianthony@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/compositing/histograms.xml b/tools/metrics/histograms/metadata/compositing/histograms.xml
index a2515d7..9ad912a 100644
--- a/tools/metrics/histograms/metadata/compositing/histograms.xml
+++ b/tools/metrics/histograms/metadata/compositing/histograms.xml
@@ -368,38 +368,15 @@
   </summary>
 </histogram>
 
-<histogram name="Compositing.Display.OverlayCombinationCache.NumIdsEvicted"
-    units="units" expires_after="2023-06-04">
-  <owner>khaslett@chromium.org</owner>
-  <owner>kylechar@chromium.org</owner>
-  <summary>
-    Logged zero or one times per frame, the number of stale candidate ids we are
-    evicting from the OverlayCombinationCache this frame. If this number is high
-    the CandidateIds or the candidates proposed are likely less stable than they
-    should be.
-  </summary>
-</histogram>
-
 <histogram name="Compositing.Display.OverlayProcessorOzone.MaxPlanesSupported"
-    units="units" expires_after="2023-08-20">
+    units="units" expires_after="2024-06-30">
   <owner>khaslett@chromium.org</owner>
   <owner>kylechar@chromium.org</owner>
   <summary>
     This is logged every time a valid HardwareCapabilities is received from DRM
     when display configuration may have changed. It records the number of
     overlay planes we have available on this device including the primary plane.
-  </summary>
-</histogram>
-
-<histogram
-    name="Compositing.Display.OverlayProcessorUsingStrategy.CandidateCombinationPreviouslySucceeded"
-    enum="Boolean" expires_after="2023-08-20">
-  <owner>khaslett@chromium.org</owner>
-  <owner>kylechar@chromium.org</owner>
-  <summary>
-    Logged zero or one times per frame, whether or not the candidate combination
-    being tested this frame was successfully promoted to overlays on the
-    previous frame.
+    Note: this metric was expired from 2023-08-20 to 2024-02-05.
   </summary>
 </histogram>
 
@@ -471,12 +448,12 @@
 
 <histogram
     name="Compositing.Display.OverlayProcessorUsingStrategy.NumQuadsConsidered"
-    units="units" expires_after="2023-06-18">
+    units="units" expires_after="2024-06-30">
   <owner>khaslett@chromium.org</owner>
   <owner>kylechar@chromium.org</owner>
   <summary>
     Logged once per frame, the number of quads considered for promotion to
-    overlay.
+    overlay. Note: this metric was expired from 2023-06-18 to 2024-02-05.
   </summary>
 </histogram>
 
@@ -789,20 +766,6 @@
   </summary>
 </histogram>
 
-<histogram name="Compositing.SurfaceAggregator.DeclareResourcesUs"
-    units="microseconds" expires_after="2024-02-01">
-  <owner>kylechar@chromium.org</owner>
-  <owner>jonross@chromium.org</owner>
-  <summary>
-    Time spent declaring resources as used during surface aggregation. This is a
-    subset of the time recorded for Compositing.SurfaceAggregator.AggregateUs
-    and is logged once per frame.
-
-    Warning: This metric does not include reports from clients with
-    low-resolution clocks.
-  </summary>
-</histogram>
-
 <histogram name="Compositing.SurfaceAggregator.Has{Type}PerFrame"
     enum="Boolean" expires_after="2024-06-30">
   <owner>magchen@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/cross_device/enums.xml b/tools/metrics/histograms/metadata/cross_device/enums.xml
index 48639b31..af8f53b 100644
--- a/tools/metrics/histograms/metadata/cross_device/enums.xml
+++ b/tools/metrics/histograms/metadata/cross_device/enums.xml
@@ -349,6 +349,7 @@
   <int value="2" label="Internal error"/>
   <int value="3" label="Network Connection Handler Failed"/>
   <int value="4" label="Network State was null"/>
+  <int value="5" label="Wifi failed to enable"/>
 </enum>
 
 <enum name="InstantTethering_ConnectionToHostResult_Failure_TetheringTimeout">
diff --git a/tools/metrics/histograms/metadata/enterprise/histograms.xml b/tools/metrics/histograms/metadata/enterprise/histograms.xml
index 4097f04..2975ca05 100644
--- a/tools/metrics/histograms/metadata/enterprise/histograms.xml
+++ b/tools/metrics/histograms/metadata/enterprise/histograms.xml
@@ -434,17 +434,6 @@
   </summary>
 </histogram>
 
-<histogram name="Enterprise.CloudExtensionRequestDialogAction"
-    enum="BooleanSent" expires_after="2023-10-01">
-  <owner>zmin@chromium.org</owner>
-  <owner>hur.ims@navercorp.com</owner>
-  <owner>src/chrome/browser/enterprise/reporting/OWNERS</owner>
-  <summary>
-    Recorded once each time the extension request prompt dialog is accepted or
-    aborted.
-  </summary>
-</histogram>
-
 <histogram name="Enterprise.CloudExtensionRequestUpdated"
     enum="EnterpriseCloudExtensionRequestListUpdate" expires_after="2024-10-01">
   <owner>zmin@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/password/histograms.xml b/tools/metrics/histograms/metadata/password/histograms.xml
index 1e658ac..2c9d0d43 100644
--- a/tools/metrics/histograms/metadata/password/histograms.xml
+++ b/tools/metrics/histograms/metadata/password/histograms.xml
@@ -726,7 +726,7 @@
 </histogram>
 
 <histogram name="PasswordManager.AffiliationFetcher.FailedToParseResponse"
-    enum="Boolean" expires_after="M123">
+    enum="Boolean" expires_after="M126">
   <owner>vsemeniuk@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
@@ -783,7 +783,7 @@
 </histogram>
 
 <histogram name="PasswordManager.AffiliationFetcher.ResponseSize.{Status}"
-    units="bytes" expires_after="M123">
+    units="bytes" expires_after="M126">
   <owner>vsemeniuk@google.com</owner>
   <owner>vasilii@chromium.org</owner>
   <summary>
diff --git a/tools/metrics/histograms/metadata/prefetch/histograms.xml b/tools/metrics/histograms/metadata/prefetch/histograms.xml
index 6ea57c9e..1f719a5 100644
--- a/tools/metrics/histograms/metadata/prefetch/histograms.xml
+++ b/tools/metrics/histograms/metadata/prefetch/histograms.xml
@@ -148,7 +148,7 @@
 </histogram>
 
 <histogram name="PrefetchProxy.AfterClick.RedirectChainSize" units="count"
-    expires_after="2024-04-25">
+    expires_after="2024-06-30">
   <owner>liviutinta@chromium.org</owner>
   <owner>jbroman@chromium.org</owner>
   <owner>curranmax@chromium.org</owner>
@@ -178,7 +178,7 @@
 </histogram>
 
 <histogram name="PrefetchProxy.AfterClick.WasFullRedirectChainServed"
-    enum="Boolean" expires_after="2024-04-25">
+    enum="Boolean" expires_after="2024-06-30">
   <owner>liviutinta@chromium.org</owner>
   <owner>jbroman@chromium.org</owner>
   <owner>curranmax@chromium.org</owner>
@@ -476,7 +476,7 @@
 </histogram>
 
 <histogram name="PrefetchProxy.Prefetch.RedirectChainSize" units="count"
-    expires_after="2024-04-25">
+    expires_after="2024-06-30">
   <owner>liviutinta@chromium.org</owner>
   <owner>jbroman@chromium.org</owner>
   <owner>curranmax@chromium.org</owner>
@@ -526,7 +526,7 @@
 </histogram>
 
 <histogram name="PrefetchProxy.Redirect.NetworkContextStateTransition"
-    enum="PrefetchRedirectNetworkContextTransition" expires_after="2024-04-25">
+    enum="PrefetchRedirectNetworkContextTransition" expires_after="2024-06-30">
   <owner>liviutinta@chromium.org</owner>
   <owner>jbroman@chromium.org</owner>
   <owner>curranmax@chromium.org</owner>
@@ -539,7 +539,7 @@
 </histogram>
 
 <histogram name="PrefetchProxy.Redirect.Result" enum="PrefetchRedirectResult"
-    expires_after="2024-03-17">
+    expires_after="2024-06-30">
   <owner>liviutinta@chromium.org</owner>
   <owner>jbroman@chromium.org</owner>
   <owner>curranmax@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/sb_client/histograms.xml b/tools/metrics/histograms/metadata/sb_client/histograms.xml
index 40ad415..da5fb82 100644
--- a/tools/metrics/histograms/metadata/sb_client/histograms.xml
+++ b/tools/metrics/histograms/metadata/sb_client/histograms.xml
@@ -616,6 +616,28 @@
   </summary>
 </histogram>
 
+<histogram name="SBClientPhishing.LiveClientPhishingModelCountAtCreation"
+    units="models" expires_after="2024-04-01">
+  <owner>drubery@chromium.org</owner>
+  <owner>chrome-counter-abuse-alerts@google.com</owner>
+  <summary>
+    Record how many ClientPhishingModels are alive. This is logged each time a
+    new ClientPhishingModel is created. This is temporarily logged to
+    investigate https://crbug.com/1515348.
+  </summary>
+</histogram>
+
+<histogram name="SBClientPhishing.LiveScorersAtCreation" units="models"
+    expires_after="2024-04-01">
+  <owner>drubery@chromium.org</owner>
+  <owner>chrome-counter-abuse-alerts@google.com</owner>
+  <summary>
+    Record how many Scorers are alive within a single renderer process. This is
+    logged each time a new Scorer is created. This is temporarily logged to
+    investigate https://crbug.com/1515348.
+  </summary>
+</histogram>
+
 <histogram name="SBClientPhishing.LocalModelDetectsPhishing"
     enum="BooleanIsPhishing" expires_after="2024-06-30">
   <owner>drubery@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/storage/histograms.xml b/tools/metrics/histograms/metadata/storage/histograms.xml
index a7de073..b21f1cc0 100644
--- a/tools/metrics/histograms/metadata/storage/histograms.xml
+++ b/tools/metrics/histograms/metadata/storage/histograms.xml
@@ -50,6 +50,7 @@
   <variant name="Thumbnail" summary="Thumbnail"/>
   <variant name="TopSites" summary="TopSites"/>
   <variant name="TrustTokens" summary="TrustTokens"/>
+  <variant name="UKMMetrics" summary="UKMMetrics"/>
   <variant name="UserNotes" summary="UserNotes"/>
   <variant name="Web" summary="Web"/>
 </variants>
diff --git a/tools/metrics/histograms/metadata/tab/enums.xml b/tools/metrics/histograms/metadata/tab/enums.xml
index 40de15d..6808541 100644
--- a/tools/metrics/histograms/metadata/tab/enums.xml
+++ b/tools/metrics/histograms/metadata/tab/enums.xml
@@ -191,6 +191,12 @@
   <int value="1" label="Switch to tab from filtered search tab list"/>
 </enum>
 
+<enum name="TabStateFlatBufferDeserializeResult">
+  <int value="0" label="Success"/>
+  <int value="1" label="Failure Unknown Reason"/>
+  <int value="2" label="Failure IndexOutOfBoundsException"/>
+</enum>
+
 <enum name="TabStatus">
   <int value="0" label="Memory resident"/>
   <int value="1" label="Evicted and reloaded"/>
diff --git a/tools/metrics/histograms/metadata/tab/histograms.xml b/tools/metrics/histograms/metadata/tab/histograms.xml
index 45ef440..aefccf90 100644
--- a/tools/metrics/histograms/metadata/tab/histograms.xml
+++ b/tools/metrics/histograms/metadata/tab/histograms.xml
@@ -2149,6 +2149,19 @@
   </summary>
 </histogram>
 
+<histogram name="Tabs.TabState.FlatBufferDeserializeResult"
+    enum="TabStateFlatBufferDeserializeResult" expires_after="2025-02-02">
+  <owner>davidjm@chromium.org</owner>
+  <owner>clank-tab-dev@google.com</owner>
+  <summary>
+    TabState (Tab metadata persistence system on Chrome for Android) is being
+    migrated from a hand-written schema to a FlatBuffer based schema. This
+    metric records the number of successful and unsuccessful FlatBuffer
+    deserializations and where known what the reason for unsuccessful
+    deserialization was.
+  </summary>
+</histogram>
+
 <histogram name="Tabs.TabState.LoadTime" units="ms" expires_after="2024-06-30">
   <owner>yusufo@chromium.org</owner>
   <owner>nyquist@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/web_apk/histograms.xml b/tools/metrics/histograms/metadata/web_apk/histograms.xml
index d2861dd..826409e 100644
--- a/tools/metrics/histograms/metadata/web_apk/histograms.xml
+++ b/tools/metrics/histograms/metadata/web_apk/histograms.xml
@@ -325,7 +325,7 @@
 </histogram>
 
 <histogram name="WebApk.Update.UpdateEmptyUniqueId.NeedsUpgrade" enum="Boolean"
-    expires_after="2024-03-17">
+    expires_after="2024-06-17">
   <owner>eirage@chromium.org</owner>
   <owner>hartmanng@chromium.org</owner>
   <owner>src/chrome/android/webapk/OWNERS</owner>
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 32cebf1c..6b76de26 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,24 +5,24 @@
             "full_remote_path": "perfetto-luci-artifacts/v42.0/linux-arm64/trace_processor_shell"
         },
         "win": {
-            "hash": "0848c65e4edacfda688ec2869692e2ec7140b86f",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/94d269b51dfdc63c3b7b8a000a777c82220c1624/trace_processor_shell.exe"
+            "hash": "59fbcac876764b24915cc1c341d53efe7ed92a5b",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/win/4ab344d392b8b390c73132128d3c23e7f03818fe/trace_processor_shell.exe"
         },
         "linux_arm": {
             "hash": "46739eeb4b8f2a65a8a0aac57743767e6407f7bb",
             "full_remote_path": "perfetto-luci-artifacts/v42.0/linux-arm/trace_processor_shell"
         },
         "mac": {
-            "hash": "a4fa9ac8f03a742d22f7e1ce003c689335a132c8",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/94d269b51dfdc63c3b7b8a000a777c82220c1624/trace_processor_shell"
+            "hash": "d28db75c3eac5b5bbf79825576bc5cd3926728e5",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/4ab344d392b8b390c73132128d3c23e7f03818fe/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "789f24a091d50faafdd5d7c5096bb923d073f138",
             "full_remote_path": "perfetto-luci-artifacts/v42.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "a7b3f81f6c9299d8d4b74a9d6e7e016673bd4bd0",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/94d269b51dfdc63c3b7b8a000a777c82220c1624/trace_processor_shell"
+            "hash": "01b55669645cf3d35cb61b06d0f27f4467a16e85",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/3deb2358149574207c12407c7a6b2a885a5cef3b/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index 982306f..1f843f9f 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -276,7 +276,7 @@
  <item id="tachyon_ice_config_fetcher" added_in_milestone="98" content_hash_code="01e878b0" os_list="chromeos" file_path="chrome/browser/nearby_sharing/tachyon_ice_config_fetcher.cc" />
  <item id="launcher_item_suggest" added_in_milestone="98" content_hash_code="04a4041e" os_list="chromeos" file_path="chrome/browser/ash/file_suggest/item_suggest_cache.cc" />
  <item id="ambient_client" added_in_milestone="98" content_hash_code="062d821f" os_list="chromeos" file_path="chrome/browser/ui/ash/ambient/ambient_client_impl.cc" />
- <item id="calendar_get_events" added_in_milestone="98" content_hash_code="0298bde5" os_list="chromeos" file_path="chrome/browser/ui/ash/calendar/calendar_keyed_service.cc" />
+ <item id="calendar_get_events" added_in_milestone="98" content_hash_code="0757d834" os_list="chromeos" file_path="chrome/browser/ui/ash/calendar/calendar_keyed_service.cc" />
  <item id="edu_account_login_profile_image_fetcher" added_in_milestone="98" content_hash_code="0689301c" os_list="chromeos" file_path="chrome/browser/ui/webui/ash/edu_account_login_handler.cc" />
  <item id="management_ui_customer_logo" added_in_milestone="98" content_hash_code="01a17a58" os_list="chromeos" file_path="chrome/browser/ui/webui/management/management_ui_handler_chromeos.cc" />
  <item id="gaia_reauth_token_fetcher" added_in_milestone="98" content_hash_code="04ff463d" os_list="chromeos" file_path="chrome/browser/ash/login/gaia_reauth_token_fetcher.cc" />
@@ -326,7 +326,6 @@
  <item id="pending_beacon_api" added_in_milestone="105" content_hash_code="02288864" os_list="linux,windows,android,chromeos" file_path="content/browser/renderer_host/pending_beacon_service.cc" />
  <item id="printing_server_printers_query" added_in_milestone="106" content_hash_code="06f4759c" os_list="chromeos" file_path="chrome/browser/ash/printing/server_printers_fetcher.cc" />
  <item id="download_bubble_retry_download" added_in_milestone="105" content_hash_code="000b1439" os_list="linux,windows,chromeos" file_path="chrome/browser/download/bubble/download_bubble_ui_controller.cc" />
- <item id="wilco_dtc_supportd" added_in_milestone="108" content_hash_code="068635d9" os_list="chromeos" file_path="chrome/browser/ash/wilco_dtc_supportd/wilco_dtc_supportd_web_request_service.cc" />
  <item id="quick_answers_loader" added_in_milestone="105" content_hash_code="06f129c2" os_list="chromeos" file_path="chromeos/components/quick_answers/result_loader.cc" />
  <item id="chrome_search_suggest_service" added_in_milestone="105" content_hash_code="04d973c6" os_list="linux,windows,android,chromeos" file_path="components/search/start_suggest_service.cc" />
  <item id="speculation_rules_prefetch_probe" added_in_milestone="105" content_hash_code="04341e21" os_list="linux,windows,android,chromeos" file_path="content/browser/preloading/prefetch/prefetch_origin_prober.cc" />
diff --git a/tools/traffic_annotation/summary/grouping.xml b/tools/traffic_annotation/summary/grouping.xml
index 1db8401..15cd1e4e 100644
--- a/tools/traffic_annotation/summary/grouping.xml
+++ b/tools/traffic_annotation/summary/grouping.xml
@@ -80,7 +80,6 @@
       <annotation id="wallpaper_google_photos_albums"/>
       <annotation id="wallpaper_google_photos_enabled"/>
       <annotation id="wallpaper_google_photos_photos"/>
-      <annotation id="wilco_dtc_supportd"/>
       <annotation id="search_and_assistant_enabled_checker"/>
       <annotation id="wallpaper_online_downloader"/>
     </sender>
diff --git a/tools/visual_debugger/app.html b/tools/visual_debugger/app.html
index f302b24..5847af7 100644
--- a/tools/visual_debugger/app.html
+++ b/tools/visual_debugger/app.html
@@ -359,181 +359,202 @@
       const f = document.createElement('input');
       f.type = 'file';
       f.addEventListener('change', () => {
-        const file = new FileReader(f.files[0]);
-        file.addEventListener('load', () => {
-          const json = JSON.parse(file.result);
-          const traceEvents = json.traceEvents;
-
-          curr_frame = "0";
-          curr_draws = [];
-          sources_this_frame = []
-          global_sources_mapping = []
-          // Get the source index for an annotation, updating `global_sources_mapping` as needed.
-          let getSourceIndex = (anno) => {
-            let found_source_index = global_sources_mapping.indexOf(anno);
-            if (found_source_index === -1) {
-              global_sources_mapping.push(anno);
-              found_source_index = global_sources_mapping.length - 1;
-              sources_this_frame.push({
-                "anno": anno,
-                "file": "none",
-                "func": "none",
-                "index": found_source_index,
-                "line": -1,
-              });
-            }
-            return found_source_index;
-          };
-
-          threads_this_frame = new Set();
-          global_threads = {};
-          global_processes = {};
-          // Return a faux thread ID that also includes the process ID.
-          // VizDebugger only tracks a thread ID, but we know the process ID as well in this case.
-          let trackThreadAndProcessId = (event) => {
-            // We're assuming the thread ID is not going to exceed u16.
-            let thread_id = (event.pid * 65536) + event.tid;
-            threads_this_frame.add({
-              "thread_id": thread_id,
-              "thread_name": `${global_processes[event.pid]}/${global_threads[event.tid]}`,
-            })
-            return thread_id;
-          }
-          let resolveThreadNamesAndResetThreadIdsThisFrame = () => {
-            let threads = [];
-            for (const thread of threads_this_frame) {
-              threads.push(thread);
-            }
-            threads_this_frame.clear();
-            return threads;
-          };
-
-
-          for (const event of traceEvents) {
-            if (event.name === "thread_name") {
-              global_threads[event.tid] = event.args.name;
-            } else if (event.name === "process_name") {
-              global_processes[event.pid] = event.args.name;
-            } else if (event.cat.includes("viz.visual_debugger")) {
-              if (event.name == "visual_debugger_sync") {
-                single_frame = { "drawcalls": [], "frame": "none", "logs": [], "new_sources": [], "time": "0", "version": 1, "windowx": 2400, "windowy": 1600, "threads": [{ "thread_id": "1", "thread_name": "allthreads" }] };
-                single_frame.drawcalls = curr_draws;
-                curr_frame = event.args.last_presented_trace_id;
-                single_frame.frame = curr_frame;
-                single_frame.new_sources = sources_this_frame;
-                single_frame.windowx = parseInt(event.args.display_size.split("x")[0]);
-                single_frame.windowy = parseInt(event.args.display_size.split("x")[1]);
-                processIncomingFrame(single_frame);
-                curr_draws = [];
-                sources_this_frame = [];
-                threads_this_frame.clear();
-              }
-              else {
-                const single_call = { "drawindex": curr_draws.length, "option": { "alpha": 5, "color": "#ff0000" }, "pos": [-1, -1], "size": [-1, -1], "source_index": -1, "thread_id": 1, "buff_id": -1 };
-
-                single_call.pos[0] = parseFloat(event.args.args.pos_x);
-                single_call.pos[1] = parseFloat(event.args.args.pos_y);
-                single_call.size[0] = parseFloat(event.args.args.size_x);
-                single_call.size[1] = parseFloat(event.args.args.size_y);
-                single_call.text = event.args.args.text;
-                single_call.source_index = getSourceIndex(event.name);
-                curr_draws.push(single_call);
-              }
-            } else if (event.cat.includes("viz.quads")) {
-              if (event.name === "cc::LayerTreeHostImpl") {
-                let draw_calls = [];
-                let logs = [];
-
-                let render_pass_count = event.args.snapshot.frame.render_passes.length;
-                for (let i = 0; i < render_pass_count; i++) {
-                  let render_pass = event.args.snapshot.frame.render_passes[i];
-
-                  logs.push({
-                    "source_index": getSourceIndex("frame.render_pass.meta"),
-                    "drawindex": draw_calls.length + logs.length,
-                    "option": { "alpha": 0, "color": "#0000ff" },
-                    "thread_id": trackThreadAndProcessId(event),
-                    "value": `Render pass id=${render_pass.id}, output_rect=${render_pass.output_rect}, damage_rect=${render_pass.damage_rect}, quad_list.size=${render_pass.quad_list.length}, copy_requests=${render_pass.copy_requests}`,
-                  });
-
-                  if (i < render_pass_count - 1) {
-                    // Skip non-root render pass quads to reduce visual noise.
-                    continue;
-                  }
-
-                  draw_calls.push({
-                    "source_index": getSourceIndex("frame.render_pass.output_rect"),
-                    "drawindex": draw_calls.length + logs.length,
-                    "option": { "alpha": 5, "color": "#000000" },
-                    "pos": [render_pass.output_rect[0], render_pass.output_rect[1]],
-                    "size": [render_pass.output_rect[2], render_pass.output_rect[3]],
-                    "thread_id": trackThreadAndProcessId(event),
-                    "buff_id": -1,
-                  });
-
-                  draw_calls.push({
-                    "source_index": getSourceIndex("frame.render_pass.damage"),
-                    "drawindex": draw_calls.length + logs.length,
-                    "option": { "alpha": 5, "color": "#000000" },
-                    "pos": [render_pass.damage_rect[0], render_pass.damage_rect[1]],
-                    "size": [render_pass.damage_rect[2], render_pass.damage_rect[3]],
-                    "thread_id": trackThreadAndProcessId(event),
-                    "buff_id": -1,
-                  });
-
-                  for (let quad of render_pass.quad_list) {
-                    draw_calls.push({
-                      "source_index": getSourceIndex("frame.render_pass.quad"),
-                      "drawindex": draw_calls.length + logs.length,
-                      "option": { "alpha": 5, "color": "#000000" },
-                      "pos": [
-                        quad.rect_as_target_space_quad[0],
-                        quad.rect_as_target_space_quad[1],
-                      ],
-                      "size": [
-                        quad.rect_as_target_space_quad[2] - quad.rect_as_target_space_quad[0],
-                        quad.rect_as_target_space_quad[5] - quad.rect_as_target_space_quad[1],
-                      ],
-                      "thread_id": trackThreadAndProcessId(event),
-                      "buff_id": -1,
-                    });
-
-                    draw_calls.push({
-                      "source_index": getSourceIndex("frame.render_pass.material"),
-                      "drawindex": draw_calls.length + logs.length,
-                      "option": { "alpha": 0, "color": "#00ff00" },
-                      "pos": [
-                        quad.rect_as_target_space_quad[0],
-                        quad.rect_as_target_space_quad[1],
-                      ],
-                      "size": [0, 0],
-                      "text": `${quad.material}`,
-                      "thread_id": trackThreadAndProcessId(event),
-                      "buff_id": -1,
-                    });
-                  }
-                }
-
-                // This event contains a snapshot of the layer tree, so we can process a full frame immediately.
-                processIncomingFrame({
-                  "drawcalls": draw_calls,
-                  "frame": event.tts,
-                  "logs": logs,
-                  "new_sources": sources_this_frame,
-                  "time": event.ts,
-                  "version": 1,
-                  "windowx": event.args.snapshot.device_viewport_size.width,
-                  "windowy": event.args.snapshot.device_viewport_size.height,
-                  "threads": resolveThreadNamesAndResetThreadIdsThisFrame(),
-                });
-                sources_this_frame = [];
+        async function handleBlob(blob) {
+          if (blob.type === 'application/x-gzip') {
+            // If the blob is gzipped, decompress it and recurse.
+            const ds = new DecompressionStream("gzip");
+            const decompressedBlob = blob.stream().pipeThrough(ds);
+            handleBlob(await new Response(decompressedBlob).blob());
+          } else {
+            try {
+              const json = JSON.parse(await blob.text());
+              handleImportTraceJson(json);
+            } catch (e) {
+              if (e instanceof SyntaxError) {
+                // Not all JSON blobs have the right mimetype, so we just try
+                // and fail to check.
+                alert("Not valid JSON: " + blob.message);
+              } else {
+                throw e;
               }
             }
           }
-        });
-        file.readAsText(f.files[0]);
+        }
+
+        handleBlob(f.files[0]);
       });
       f.click();
+
+      function handleImportTraceJson(json) {
+        const traceEvents = json.traceEvents;
+
+        curr_frame = "0";
+        curr_draws = [];
+        sources_this_frame = []
+        global_sources_mapping = []
+        // Get the source index for an annotation, updating `global_sources_mapping` as needed.
+        let getSourceIndex = (anno) => {
+          let found_source_index = global_sources_mapping.indexOf(anno);
+          if (found_source_index === -1) {
+            global_sources_mapping.push(anno);
+            found_source_index = global_sources_mapping.length - 1;
+            sources_this_frame.push({
+              "anno": anno,
+              "file": "none",
+              "func": "none",
+              "index": found_source_index,
+              "line": -1,
+            });
+          }
+          return found_source_index;
+        };
+
+        threads_this_frame = new Set();
+        global_threads = {};
+        global_processes = {};
+        // Return a faux thread ID that also includes the process ID.
+        // VizDebugger only tracks a thread ID, but we know the process ID as well in this case.
+        let trackThreadAndProcessId = (event) => {
+          // We're assuming the thread ID is not going to exceed u16.
+          let thread_id = (event.pid * 65536) + event.tid;
+          threads_this_frame.add({
+            "thread_id": thread_id,
+            "thread_name": `${global_processes[event.pid]}/${global_threads[event.tid]}`,
+          })
+          return thread_id;
+        }
+        let resolveThreadNamesAndResetThreadIdsThisFrame = () => {
+          let threads = [];
+          for (const thread of threads_this_frame) {
+            threads.push(thread);
+          }
+          threads_this_frame.clear();
+          return threads;
+        };
+
+
+        for (const event of traceEvents) {
+          if (event.name === "thread_name") {
+            global_threads[event.tid] = event.args.name;
+          } else if (event.name === "process_name") {
+            global_processes[event.pid] = event.args.name;
+          } else if (event.cat.includes("viz.visual_debugger")) {
+            if (event.name == "visual_debugger_sync") {
+              single_frame = { "drawcalls": [], "frame": "none", "logs": [], "new_sources": [], "time": "0", "version": 1, "windowx": 2400, "windowy": 1600, "threads": [{ "thread_id": "1", "thread_name": "allthreads" }] };
+              single_frame.drawcalls = curr_draws;
+              curr_frame = event.args.last_presented_trace_id;
+              single_frame.frame = curr_frame;
+              single_frame.new_sources = sources_this_frame;
+              single_frame.windowx = parseInt(event.args.display_size.split("x")[0]);
+              single_frame.windowy = parseInt(event.args.display_size.split("x")[1]);
+              processIncomingFrame(single_frame);
+              curr_draws = [];
+              sources_this_frame = [];
+              threads_this_frame.clear();
+            }
+            else {
+              const single_call = { "drawindex": curr_draws.length, "option": { "alpha": 5, "color": "#ff0000" }, "pos": [-1, -1], "size": [-1, -1], "source_index": -1, "thread_id": 1, "buff_id": -1 };
+
+              single_call.pos[0] = parseFloat(event.args.args.pos_x);
+              single_call.pos[1] = parseFloat(event.args.args.pos_y);
+              single_call.size[0] = parseFloat(event.args.args.size_x);
+              single_call.size[1] = parseFloat(event.args.args.size_y);
+              single_call.text = event.args.args.text;
+              single_call.source_index = getSourceIndex(event.name);
+              curr_draws.push(single_call);
+            }
+          } else if (event.cat.includes("viz.quads")) {
+            if (event.name === "cc::LayerTreeHostImpl") {
+              let draw_calls = [];
+              let logs = [];
+
+              let render_pass_count = event.args.snapshot.frame.render_passes.length;
+              for (let i = 0; i < render_pass_count; i++) {
+                let render_pass = event.args.snapshot.frame.render_passes[i];
+
+                logs.push({
+                  "source_index": getSourceIndex("frame.render_pass.meta"),
+                  "drawindex": draw_calls.length + logs.length,
+                  "option": { "alpha": 0, "color": "#0000ff" },
+                  "thread_id": trackThreadAndProcessId(event),
+                  "value": `Render pass id=${render_pass.id}, output_rect=${render_pass.output_rect}, damage_rect=${render_pass.damage_rect}, quad_list.size=${render_pass.quad_list.length}, copy_requests=${render_pass.copy_requests}`,
+                });
+
+                if (i < render_pass_count - 1) {
+                  // Skip non-root render pass quads to reduce visual noise.
+                  continue;
+                }
+
+                draw_calls.push({
+                  "source_index": getSourceIndex("frame.render_pass.output_rect"),
+                  "drawindex": draw_calls.length + logs.length,
+                  "option": { "alpha": 5, "color": "#000000" },
+                  "pos": [render_pass.output_rect[0], render_pass.output_rect[1]],
+                  "size": [render_pass.output_rect[2], render_pass.output_rect[3]],
+                  "thread_id": trackThreadAndProcessId(event),
+                  "buff_id": -1,
+                });
+
+                draw_calls.push({
+                  "source_index": getSourceIndex("frame.render_pass.damage"),
+                  "drawindex": draw_calls.length + logs.length,
+                  "option": { "alpha": 5, "color": "#000000" },
+                  "pos": [render_pass.damage_rect[0], render_pass.damage_rect[1]],
+                  "size": [render_pass.damage_rect[2], render_pass.damage_rect[3]],
+                  "thread_id": trackThreadAndProcessId(event),
+                  "buff_id": -1,
+                });
+
+                for (let quad of render_pass.quad_list) {
+                  draw_calls.push({
+                    "source_index": getSourceIndex("frame.render_pass.quad"),
+                    "drawindex": draw_calls.length + logs.length,
+                    "option": { "alpha": 5, "color": "#000000" },
+                    "pos": [
+                      quad.rect_as_target_space_quad[0],
+                      quad.rect_as_target_space_quad[1],
+                    ],
+                    "size": [
+                      quad.rect_as_target_space_quad[2] - quad.rect_as_target_space_quad[0],
+                      quad.rect_as_target_space_quad[5] - quad.rect_as_target_space_quad[1],
+                    ],
+                    "thread_id": trackThreadAndProcessId(event),
+                    "buff_id": -1,
+                  });
+
+                  draw_calls.push({
+                    "source_index": getSourceIndex("frame.render_pass.material"),
+                    "drawindex": draw_calls.length + logs.length,
+                    "option": { "alpha": 0, "color": "#00ff00" },
+                    "pos": [
+                      quad.rect_as_target_space_quad[0],
+                      quad.rect_as_target_space_quad[1],
+                    ],
+                    "size": [0, 0],
+                    "text": `${quad.material}`,
+                    "thread_id": trackThreadAndProcessId(event),
+                    "buff_id": -1,
+                  });
+                }
+              }
+
+              // This event contains a snapshot of the layer tree, so we can process a full frame immediately.
+              processIncomingFrame({
+                "drawcalls": draw_calls,
+                "frame": event.tts,
+                "logs": logs,
+                "new_sources": sources_this_frame,
+                "time": event.ts,
+                "version": 1,
+                "windowx": event.args.snapshot.device_viewport_size.width,
+                "windowy": event.args.snapshot.device_viewport_size.height,
+                "threads": resolveThreadNamesAndResetThreadIdsThisFrame(),
+              });
+              sources_this_frame = [];
+            }
+          }
+        }
+      }
     });
 
     document.querySelectorAll('.panelSection.collapsible').forEach(
diff --git a/ui/accessibility/extensions/BUILD.gn b/ui/accessibility/extensions/BUILD.gn
index d8a948f..16a8f9c7 100644
--- a/ui/accessibility/extensions/BUILD.gn
+++ b/ui/accessibility/extensions/BUILD.gn
@@ -92,6 +92,36 @@
 ]
 
 #
+# Animation Policy
+#
+
+group("animation") {
+  deps = [
+    ":animation_copy",
+    ":animation_strings",
+  ]
+}
+
+grit("animation_strings") {
+  source = "strings/accessibility_extensions_strings.grd"
+  outputs = locale_files
+  output_dir = "$root_out_dir/animation"
+  resource_ids = ""
+}
+
+animation_files = [
+  "animation/animation.png",
+  "animation/manifest.json",
+  "animation/popup.html",
+  "animation/popup.js",
+]
+
+copy("animation_copy") {
+  sources = animation_files
+  outputs = [ "$root_out_dir/{{source_target_relative}}" ]
+}
+
+#
 # Color Enhancer
 #
 
diff --git a/ui/display/manager/display_change_observer_unittest.cc b/ui/display/manager/display_change_observer_unittest.cc
index 9813fe8..c3ea747 100644
--- a/ui/display/manager/display_change_observer_unittest.cc
+++ b/ui/display/manager/display_change_observer_unittest.cc
@@ -533,7 +533,7 @@
 
   const auto color_space = display_color_spaces.GetRasterColorSpace();
   EXPECT_TRUE(color_space.IsValid());
-  EXPECT_EQ(color_space.GetPrimaryID(), gfx::ColorSpace::PrimaryID::P3);
+  EXPECT_EQ(color_space.GetPrimaryID(), gfx::ColorSpace::PrimaryID::BT709);
   EXPECT_EQ(color_space.GetTransferID(), gfx::ColorSpace::TransferID::SRGB);
 }
 
diff --git a/ui/display/util/display_util.cc b/ui/display/util/display_util.cc
index 1aad38fc..349bab47 100644
--- a/ui/display/util/display_util.cc
+++ b/ui/display/util/display_util.cc
@@ -301,8 +301,9 @@
                                    DisplaySnapshot::PrimaryFormat());
   }
 
-  skcms_Matrix3x3 primary_matrix{};
-  snapshot_color_space.GetPrimaryMatrix(&primary_matrix);
+  // Make all displays report that they have sRGB primaries. Hardware color
+  // management will convert to the device's color primaries.
+  skcms_Matrix3x3 primary_matrix = SkNamedGamut::kSRGB;
 
   // Reconstruct the native colorspace with an IEC61966 2.1 transfer function
   // for SDR content (matching that of sRGB).
diff --git a/ui/ozone/platform/drm/gpu/drm_display.cc b/ui/ozone/platform/drm/gpu/drm_display.cc
index 9f798307..38eaae5 100644
--- a/ui/ozone/platform/drm/gpu/drm_display.cc
+++ b/ui/ozone/platform/drm/gpu/drm_display.cc
@@ -141,9 +141,11 @@
   is_hdr_capable_ = display_snapshot.bits_per_channel() > 8 &&
                     display_snapshot.color_space().IsHDR();
   hdr_static_metadata_ = display_snapshot.hdr_static_metadata();
-  current_color_space_ = gfx::ColorSpace::CreateSRGB();
   privacy_screen_property_ =
       std::make_unique<PrivacyScreenProperty>(drm_, connector_.get());
+
+  SkColorSpacePrimaries output_primaries =
+      display_snapshot.color_info().edid_primaries;
 #if BUILDFLAG(IS_CHROMEOS_ASH)
   is_hdr_capable_ =
       is_hdr_capable_ &&
@@ -152,11 +154,12 @@
   if (is_hdr_capable_ &&
       base::FeatureList::IsEnabled(
           display::features::kEnableExternalDisplayHDR10Mode)) {
-    current_color_space_ = display_snapshot.color_space();
+    output_primaries = SkNamedPrimariesExt::kRec2020;
     SetColorspaceProperty(display_snapshot.color_space());
     SetHdrOutputMetadata(display_snapshot.color_space());
   }
 #endif
+  drm_->plane_manager()->SetOutputColorSpace(crtc_, output_primaries);
 }
 
 DrmDisplay::~DrmDisplay() = default;
diff --git a/ui/ozone/platform/drm/gpu/drm_display.h b/ui/ozone/platform/drm/gpu/drm_display.h
index 498a645c..087dc606 100644
--- a/ui/ozone/platform/drm/gpu/drm_display.h
+++ b/ui/ozone/platform/drm/gpu/drm_display.h
@@ -113,7 +113,6 @@
   std::vector<drmModeModeInfo> modes_;
   gfx::Point origin_;
   bool is_hdr_capable_ = false;
-  gfx::ColorSpace current_color_space_;
   absl::optional<gfx::HDRStaticMetadata> hdr_static_metadata_;
   std::unique_ptr<PrivacyScreenProperty> privacy_screen_property_;
 };
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc
index 972e46e..200380c 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.cc
@@ -240,6 +240,14 @@
     hw_plane->set_owning_crtc(crtc_id);
     hw_plane->set_in_use(true);
   }
+
+  // This assumes that all planes have the same primaries. This assumption will
+  // need to be enforced in the compositor's overlay processor.
+  if (!overlay_list.empty()) {
+    SetColorSpaceForAllPlanes(crtc_id,
+                              overlay_list[0].color_space.GetPrimaries());
+  }
+
   return true;
 }
 
@@ -298,6 +306,40 @@
   return valid_ids;
 }
 
+void HardwareDisplayPlaneManager::SetOutputColorSpace(
+    uint32_t crtc_id,
+    const SkColorSpacePrimaries& primaries) {
+  const auto crtc_index = LookupCrtcIndex(crtc_id);
+  DCHECK(crtc_index.has_value());
+  CrtcState* crtc_state = &crtc_state_[*crtc_index];
+  if (crtc_state->output_primaries == primaries) {
+    return;
+  }
+
+  LOG(ERROR) << "Output SkColorSpacePrimaries";
+  LOG(ERROR) << skia::SkColorSpacePrimariesToString(primaries);
+
+  crtc_state->output_primaries = primaries;
+  UpdateAndCommitCrtcState(crtc_id, crtc_state);
+}
+
+void HardwareDisplayPlaneManager::SetColorSpaceForAllPlanes(
+    uint32_t crtc_id,
+    const SkColorSpacePrimaries& primaries) {
+  const auto crtc_index = LookupCrtcIndex(crtc_id);
+  DCHECK(crtc_index.has_value());
+  CrtcState* crtc_state = &crtc_state_[*crtc_index];
+  if (crtc_state->planes_primaries == primaries) {
+    return;
+  }
+
+  LOG(ERROR) << "Plane SkColorSpacePrimaries";
+  LOG(ERROR) << skia::SkColorSpacePrimariesToString(primaries);
+
+  crtc_state->planes_primaries = primaries;
+  UpdateAndCommitCrtcState(crtc_id, crtc_state);
+}
+
 void HardwareDisplayPlaneManager::SetColorTemperatureAdjustment(
     uint32_t crtc_id,
     const display::ColorTemperatureAdjustment& cta) {
@@ -514,13 +556,30 @@
     CrtcState* crtc_state) {
   CrtcProperties* crtc_props = &crtc_state->properties;
 
-  // Set the CTM to the concatenation of the color profile matrix and the color
-  // temperature adjustment matrix.
-  // TODO(https://crbug.com/1505062): This is incorrect if the color profile
-  // DEGAMMA/GAMMA curves are ever not the identity.
-  const skcms_Matrix3x3 ctm = skcms_Matrix3x3_concat(
-      &crtc_state->color_calibration.srgb_to_device_matrix,
-      &crtc_state->color_temperature_adjustment.srgb_matrix);
+  // Set the CTM to convert from the planes' color space primaries to the
+  // output color space primaries, followed by application of the color
+  // temperature adjustment matrix. This is wrong in the following ways:
+  //   * The primary conversion should be done in linear space. This can only
+  //     be done if both DEGAMMA and GAMMA are functional, but DEGAMMA is
+  //     very often broken.
+  //   * The color temperature adjustment matrix is computed to be applied in
+  //     sRGB space, not the output space.
+  skcms_Matrix3x3 ctm = SkNamedGamut::kXYZ;  // Start with the identity
+  {
+    skcms_Matrix3x3 plane_to_xyzd50;
+    crtc_state->planes_primaries.toXYZD50(&plane_to_xyzd50);
+
+    skcms_Matrix3x3 output_to_xyzd50;
+    crtc_state->output_primaries.toXYZD50(&output_to_xyzd50);
+
+    skcms_Matrix3x3 xyzd50_to_output;
+    skcms_Matrix3x3_invert(&output_to_xyzd50, &xyzd50_to_output);
+
+    ctm = skcms_Matrix3x3_concat(&plane_to_xyzd50, &ctm);
+    ctm = skcms_Matrix3x3_concat(&xyzd50_to_output, &ctm);
+    ctm = skcms_Matrix3x3_concat(
+        &crtc_state->color_temperature_adjustment.srgb_matrix, &ctm);
+  }
   if (crtc_state->properties.ctm.id) {
     ScopedDrmColorCtmPtr ctm_blob_data = CreateCTMBlob(ctm);
     crtc_state->pending_ctm_blob =
@@ -548,7 +607,6 @@
 
   // Set the GAMMA curve to the concatenation of the color profile with the
   // gamma adjustment.
-  // TODO(https://crbug.com/1505062):
   const auto gamma_curve = display::GammaCurve::MakeConcat(
       crtc_state->color_calibration.linear_to_device,
       crtc_state->gamma_adjustment.curve);
diff --git a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.h b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.h
index 859dfe67..30dad16 100644
--- a/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.h
+++ b/ui/ozone/platform/drm/gpu/hardware_display_plane_manager.h
@@ -108,6 +108,13 @@
     display::ColorCalibration color_calibration;
     display::GammaAdjustment gamma_adjustment;
 
+    // The color space of all input planes. This assumes that all planes have
+    // the same color space.
+    SkColorSpacePrimaries planes_primaries = SkNamedPrimariesExt::kSRGB;
+
+    // The color space of the output.
+    SkColorSpacePrimaries output_primaries = SkNamedPrimariesExt::kSRGB;
+
     // Cached blobs for the properties to commit in CommitCrtcProperties.
     // * If a property is `absl::nullopt`, then it should be left unchanged.
     // * If a property is `nullptr` then it should be set to 0.
@@ -141,6 +148,15 @@
   // calls.
   void BeginFrame(HardwareDisplayPlaneList* plane_list);
 
+  // Sets the input color space for all planes. This assumes that all planes on
+  // a CRTC have the same color space.
+  void SetColorSpaceForAllPlanes(uint32_t crtc_id,
+                                 const SkColorSpacePrimaries& primaries);
+
+  // Sets the output color space for the given CRTC.
+  void SetOutputColorSpace(uint32_t crtc_id,
+                           const SkColorSpacePrimaries& primaries);
+
   // Sets the color temperature adjustment for a given CRTC.
   void SetColorTemperatureAdjustment(
       uint32_t crtc_id,
diff --git a/ui/views/accessibility/view_accessibility.cc b/ui/views/accessibility/view_accessibility.cc
index 7a65e21..8dfbd1a 100644
--- a/ui/views/accessibility/view_accessibility.cc
+++ b/ui/views/accessibility/view_accessibility.cc
@@ -182,8 +182,9 @@
   }
 
   view_->GetAccessibleNodeData(data);
-  if (custom_data_.role != ax::mojom::Role::kUnknown)
-    data->role = custom_data_.role;
+  if (override_data_.role != ax::mojom::Role::kUnknown) {
+    data->role = override_data_.role;
+  }
   if (data->role == ax::mojom::Role::kAlertDialog) {
     // When an alert dialog is used, indicate this with xml-roles. This helps
     // JAWS understand that it's a dialog and not just an ordinary alert, even
@@ -195,8 +196,8 @@
   }
 
   std::string name;
-  if (custom_data_.GetStringAttribute(ax::mojom::StringAttribute::kName,
-                                      &name)) {
+  if (override_data_.GetStringAttribute(ax::mojom::StringAttribute::kName,
+                                        &name)) {
     if (!name.empty())
       data->SetNameChecked(name);
     else
@@ -204,16 +205,17 @@
   }
 
   std::string description;
-  if (custom_data_.GetStringAttribute(ax::mojom::StringAttribute::kDescription,
-                                      &description)) {
+  if (override_data_.GetStringAttribute(
+          ax::mojom::StringAttribute::kDescription, &description)) {
     if (!description.empty())
       data->SetDescription(description);
     else
       data->SetDescriptionExplicitlyEmpty();
   }
 
-  if (custom_data_.GetHasPopup() != ax::mojom::HasPopup::kFalse)
-    data->SetHasPopup(custom_data_.GetHasPopup());
+  if (override_data_.GetHasPopup() != ax::mojom::HasPopup::kFalse) {
+    data->SetHasPopup(override_data_.GetHasPopup());
+  }
 
   static constexpr ax::mojom::IntAttribute kOverridableIntAttributes[]{
       ax::mojom::IntAttribute::kDescriptionFrom,
@@ -222,8 +224,10 @@
       ax::mojom::IntAttribute::kSetSize,
   };
   for (auto attribute : kOverridableIntAttributes) {
-    if (custom_data_.HasIntAttribute(attribute))
-      data->AddIntAttribute(attribute, custom_data_.GetIntAttribute(attribute));
+    if (override_data_.HasIntAttribute(attribute)) {
+      data->AddIntAttribute(attribute,
+                            override_data_.GetIntAttribute(attribute));
+    }
   }
 
   static constexpr ax::mojom::IntListAttribute kOverridableIntListAttributes[]{
@@ -234,9 +238,10 @@
       ax::mojom::IntListAttribute::kWordEnds,
   };
   for (auto attribute : kOverridableIntListAttributes) {
-    if (custom_data_.HasIntListAttribute(attribute))
+    if (override_data_.HasIntListAttribute(attribute)) {
       data->AddIntListAttribute(attribute,
-                                custom_data_.GetIntListAttribute(attribute));
+                                override_data_.GetIntListAttribute(attribute));
+    }
   }
 
   if (!data->HasStringAttribute(ax::mojom::StringAttribute::kDescription)) {
@@ -251,15 +256,16 @@
     }
   }
 
-  if (custom_data_.HasBoolAttribute(ax::mojom::BoolAttribute::kSelected)) {
+  if (override_data_.HasBoolAttribute(ax::mojom::BoolAttribute::kSelected)) {
     data->AddBoolAttribute(
         ax::mojom::BoolAttribute::kSelected,
-        custom_data_.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected));
+        override_data_.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected));
   }
 
   data->relative_bounds.bounds = gfx::RectF(view_->GetBoundsInScreen());
-  if (!custom_data_.relative_bounds.bounds.IsEmpty())
-    data->relative_bounds.bounds = custom_data_.relative_bounds.bounds;
+  if (!override_data_.relative_bounds.bounds.IsEmpty()) {
+    data->relative_bounds.bounds = override_data_.relative_bounds.bounds;
+  }
 
   // We need to add the ignored state to all ignored Views, similar to how Blink
   // exposes ignored DOM nodes. Calling AXNodeData::IsIgnored() would also check
@@ -362,7 +368,7 @@
 
 void ViewAccessibility::OverrideRole(const ax::mojom::Role role) {
   DCHECK(IsValidRoleForViews(role)) << "Invalid role for Views.";
-  custom_data_.role = role;
+  override_data_.role = role;
 }
 
 void ViewAccessibility::OverrideName(const std::string& name,
@@ -375,14 +381,14 @@
   // |AXNodeData::SetName| expects a valid role. Some Views call |OverrideRole|
   // prior to overriding the name. For those that don't, see if we can get the
   // default role from the View.
-  if (custom_data_.role == ax::mojom::Role::kUnknown) {
+  if (override_data_.role == ax::mojom::Role::kUnknown) {
     ui::AXNodeData data;
     view_->GetAccessibleNodeData(&data);
-    custom_data_.role = data.role;
+    override_data_.role = data.role;
   }
 
-  custom_data_.SetNameFrom(name_from);
-  custom_data_.SetNameChecked(name);
+  override_data_.SetNameFrom(name_from);
+  override_data_.SetNameChecked(name);
 }
 
 void ViewAccessibility::OverrideName(const std::u16string& name,
@@ -401,7 +407,7 @@
   //
   // |ViewAccessibility::GetAccessibleNodeData| gets properties from: 1) The
   // View's implementation of |View::GetAccessibleNodeData| and 2) the
-  // custom_data_ set via ViewAccessibility's various Override functions.
+  // override_data_ set via ViewAccessibility's various Override functions.
   // HOWEVER, it returns early prior to checking either of those sources if the
   // Widget does not exist or is closed. Thus given a View whose Widget is about
   // to be created, we cannot use |ViewAccessibility::GetAccessibleNodeData| to
@@ -413,19 +419,19 @@
   const std::string& label =
       label_data.GetStringAttribute(ax::mojom::StringAttribute::kName).empty()
           ? labelled_by_view->GetViewAccessibility()
-                .custom_data_.GetStringAttribute(
+                .override_data_.GetStringAttribute(
                     ax::mojom::StringAttribute::kName)
           : label_data.GetStringAttribute(ax::mojom::StringAttribute::kName);
 
-  // |OverrideName| includes logic to populate custom_data_.role with the
+  // |OverrideName| includes logic to populate override_data_.role with the
   // View's default role in cases where |OverrideRole| was not called (yet).
   // This ensures |AXNodeData::SetName| is not called with |Role::kUnknown|.
   OverrideName(label, name_from);
 
   int32_t labelled_by_id =
       labelled_by_view->GetViewAccessibility().GetUniqueId().Get();
-  custom_data_.AddIntListAttribute(ax::mojom::IntListAttribute::kLabelledbyIds,
-                                   {labelled_by_id});
+  override_data_.AddIntListAttribute(
+      ax::mojom::IntListAttribute::kLabelledbyIds, {labelled_by_id});
 }
 
 void ViewAccessibility::OverrideDescription(
@@ -436,8 +442,8 @@
       description_from == ax::mojom::DescriptionFrom::kAttributeExplicitlyEmpty)
       << "If the description is being removed to improve the user experience, "
          "|description_from| should be set to |kAttributeExplicitlyEmpty|.";
-  custom_data_.SetDescriptionFrom(description_from);
-  custom_data_.SetDescription(description);
+  override_data_.SetDescriptionFrom(description_from);
+  override_data_.SetDescription(description);
 }
 
 void ViewAccessibility::OverrideDescription(
@@ -492,7 +498,7 @@
 }
 
 void ViewAccessibility::OverrideIsEnabled(bool enabled) {
-  // Cannot store this value in `custom_data_` because
+  // Cannot store this value in `override_data_` because
   // `AXNodeData::AddIntAttribute` will DCHECK if you add an IntAttribute that
   // is equal to kNone. Adding an IntAttribute that is equal to kNone is
   // ambiguous, since it is unclear what would be the difference between doing
@@ -507,25 +513,27 @@
 }
 
 void ViewAccessibility::OverrideBounds(const gfx::RectF& bounds) {
-  custom_data_.relative_bounds.bounds = bounds;
+  override_data_.relative_bounds.bounds = bounds;
 }
 
 void ViewAccessibility::OverrideHasPopup(const ax::mojom::HasPopup has_popup) {
-  custom_data_.SetHasPopup(has_popup);
+  override_data_.SetHasPopup(has_popup);
 }
 
 void ViewAccessibility::OverridePosInSet(int pos_in_set, int set_size) {
-  custom_data_.AddIntAttribute(ax::mojom::IntAttribute::kPosInSet, pos_in_set);
-  custom_data_.AddIntAttribute(ax::mojom::IntAttribute::kSetSize, set_size);
+  override_data_.AddIntAttribute(ax::mojom::IntAttribute::kPosInSet,
+                                 pos_in_set);
+  override_data_.AddIntAttribute(ax::mojom::IntAttribute::kSetSize, set_size);
 }
 
 void ViewAccessibility::ClearPosInSetOverride() {
-  custom_data_.RemoveIntAttribute(ax::mojom::IntAttribute::kPosInSet);
-  custom_data_.RemoveIntAttribute(ax::mojom::IntAttribute::kSetSize);
+  override_data_.RemoveIntAttribute(ax::mojom::IntAttribute::kPosInSet);
+  override_data_.RemoveIntAttribute(ax::mojom::IntAttribute::kSetSize);
 }
 
 void ViewAccessibility::OverrideIsSelected(bool selected) {
-  custom_data_.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected, selected);
+  override_data_.AddBoolAttribute(ax::mojom::BoolAttribute::kSelected,
+                                  selected);
 }
 
 void ViewAccessibility::OverrideNextFocus(Widget* widget) {
@@ -563,26 +571,27 @@
 
 void ViewAccessibility::OverrideCharacterOffsets(
     const std::vector<int32_t>& offsets) {
-  custom_data_.AddIntListAttribute(
+  override_data_.AddIntListAttribute(
       ax::mojom::IntListAttribute::kCharacterOffsets, offsets);
 }
 
 void ViewAccessibility::OverrideWordStarts(
     const std::vector<int32_t>& offsets) {
-  custom_data_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
-                                   offsets);
+  override_data_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
+                                     offsets);
 }
 
 void ViewAccessibility::OverrideWordEnds(const std::vector<int32_t>& offsets) {
-  custom_data_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
-                                   offsets);
+  override_data_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
+                                     offsets);
 }
 
 void ViewAccessibility::ClearTextOffsets() {
-  custom_data_.RemoveIntListAttribute(
+  override_data_.RemoveIntListAttribute(
       ax::mojom::IntListAttribute::kCharacterOffsets);
-  custom_data_.RemoveIntListAttribute(ax::mojom::IntListAttribute::kWordStarts);
-  custom_data_.RemoveIntListAttribute(ax::mojom::IntListAttribute::kWordEnds);
+  override_data_.RemoveIntListAttribute(
+      ax::mojom::IntListAttribute::kWordStarts);
+  override_data_.RemoveIntListAttribute(ax::mojom::IntListAttribute::kWordEnds);
 }
 
 gfx::NativeViewAccessible ViewAccessibility::GetNativeObject() const {
diff --git a/ui/views/accessibility/view_accessibility.h b/ui/views/accessibility/view_accessibility.h
index 9f826a98..9b81d4e7 100644
--- a/ui/views/accessibility/view_accessibility.h
+++ b/ui/views/accessibility/view_accessibility.h
@@ -340,7 +340,7 @@
 
   // Contains data set explicitly via OverrideRole, OverrideName, etc. that
   // overrides anything provided by GetAccessibleNodeData().
-  ui::AXNodeData custom_data_;
+  ui::AXNodeData override_data_;
 
   // If set to true, anything that is a descendant of this view will be hidden
   // from accessibility.
diff --git a/ui/views/layout/animating_layout_manager.cc b/ui/views/layout/animating_layout_manager.cc
index df78da5..04a7e84 100644
--- a/ui/views/layout/animating_layout_manager.cc
+++ b/ui/views/layout/animating_layout_manager.cc
@@ -24,6 +24,7 @@
 #include "ui/views/animation/animation_delegate_views.h"
 #include "ui/views/layout/normalized_geometry.h"
 #include "ui/views/view.h"
+#include "ui/views/view_class_properties.h"
 
 namespace views {
 
@@ -945,7 +946,8 @@
     View* const child = fade_info.child_view;
     if (fade_info.fade_type == LayoutFadeType::kFadingOut &&
         host_view()->GetIndexOf(child).has_value() &&
-        !IsChildViewIgnoredByLayout(child) && !IsChildIncludedInLayout(child)) {
+        !child->GetProperty(kViewIgnoredByLayoutKey) &&
+        !IsChildIncludedInLayout(child)) {
       SetViewVisibility(child, false);
     }
   }
diff --git a/ui/views/layout/animating_layout_manager_unittest.cc b/ui/views/layout/animating_layout_manager_unittest.cc
index 4f96bd3..e68d377 100644
--- a/ui/views/layout/animating_layout_manager_unittest.cc
+++ b/ui/views/layout/animating_layout_manager_unittest.cc
@@ -2139,7 +2139,7 @@
   EXPECT_FALSE(layout()->is_animating());
   EnsureLayout(expected_start);
 
-  layout()->SetChildViewIgnoredByLayout(child(0), true);
+  child(0)->SetProperty(kViewIgnoredByLayoutKey, true);
 
   test::RunScheduledLayout(view());
   EXPECT_TRUE(layout()->is_animating());
diff --git a/ui/views/layout/box_layout.cc b/ui/views/layout/box_layout.cc
index 81db621..33bc04d 100644
--- a/ui/views/layout/box_layout.cc
+++ b/ui/views/layout/box_layout.cc
@@ -5,96 +5,85 @@
 #include "ui/views/layout/box_layout.h"
 
 #include <algorithm>
-#include <numeric>
-#include <string>
-#include <vector>
 
 #include "base/containers/adapters.h"
 #include "base/ranges/algorithm.h"
-#include "base/strings/strcat.h"
 #include "ui/gfx/geometry/rect.h"
-#include "ui/views/layout/normalized_geometry.h"
 #include "ui/views/view_class_properties.h"
 
 namespace views {
 
 namespace {
 
-struct BoxChildData {
-  BoxChildData() = default;
+// Returns the maximum of the given insets along the given |axis|.
+// NOTE: |axis| is different from |orientation_|; it specifies the actual
+// desired axis.
+enum class Axis { kHorizontal, kVertical };
 
-  // Copying this struct would be expensive and they only ever live in a vector
-  // in Layout (see below) so we'll only allow move semantics.
-  BoxChildData(const BoxChildData&) = delete;
-  BoxChildData& operator=(const BoxChildData&) = delete;
-
-  BoxChildData(BoxChildData&& other) = default;
-
-  std::string ToString() const {
-    return base::StrCat({"{ preferred ", preferred_size.ToString(), " margins ",
-                         margins.ToString(), " bounds ",
-                         actual_bounds.ToString(), " }"});
+gfx::Insets MaxAxisInsets(Axis axis,
+                          const gfx::Insets& leading1,
+                          const gfx::Insets& leading2,
+                          const gfx::Insets& trailing1,
+                          const gfx::Insets& trailing2) {
+  if (axis == Axis::kHorizontal) {
+    return gfx::Insets::TLBR(0, std::max(leading1.left(), leading2.left()), 0,
+                             std::max(trailing1.right(), trailing2.right()));
   }
-
-  NormalizedSize preferred_size;
-  NormalizedInsets margins;
-  NormalizedRect actual_bounds;
-  int flex;
-};
-
-NormalizedInsets MaxAxisInsets(const NormalizedInsets& leading1,
-                               const NormalizedInsets& leading2,
-                               const NormalizedInsets& trailing1,
-                               const NormalizedInsets& trailing2) {
-  return NormalizedInsets(
-      std::max(leading1.main_leading(), leading2.main_leading()), 0,
-      std::max(trailing1.main_trailing(), trailing2.main_trailing()), 0);
+  return gfx::Insets::TLBR(std::max(leading1.top(), leading2.top()), 0,
+                           std::max(trailing1.bottom(), trailing2.bottom()), 0);
 }
 
 }  // namespace
 
-struct BoxLayout::BoxLayoutData {
-  BoxLayoutData() = default;
+BoxLayout::ViewWrapper::ViewWrapper() = default;
 
-  BoxLayoutData(const BoxLayoutData&) = delete;
-  BoxLayoutData& operator=(const BoxLayoutData&) = delete;
+BoxLayout::ViewWrapper::ViewWrapper(const BoxLayout* layout, View* view)
+    : view_(view), layout_(layout) {
+  gfx::Insets* margins = view_ ? view_->GetProperty(kMarginsKey) : nullptr;
+  if (margins)
+    margins_ = *margins;
+}
 
-  ~BoxLayoutData() = default;
+BoxLayout::ViewWrapper::~ViewWrapper() = default;
 
-  size_t num_children() const { return child_data.size(); }
+int BoxLayout::ViewWrapper::GetHeightForWidth(int width) const {
+  // When collapse_margins_spacing_ is true, the BoxLayout handles the margin
+  // calculations because it has to compare and use only the largest of several
+  // adjacent margins or border insets.
+  if (layout_->collapse_margins_spacing_)
+    return view_->GetHeightForWidth(width);
+  // When collapse_margins_spacing_ is false, the view margins are included in
+  // the "virtual" size of the view. The view itself is unaware of this, so this
+  // information has to be excluded before the call to View::GetHeightForWidth()
+  // and added back in to the result.
+  return view_->GetHeightForWidth(std::max(0, width - margins_.width())) +
+         margins_.height();
+}
 
-  std::string ToString() const {
-    std::string result = base::StrCat({"{ host_size ", total_size.ToString(),
-                                       ", cross_center_pos ",
-                                       base::NumberToString(cross_center_pos),
-                                       "\n", layout.ToString(), " {\n"});
-    bool first = true;
-    for (const BoxChildData& flex_child : child_data) {
-      if (first) {
-        first = false;
-      } else {
-        base::StrAppend(&result, {",\n"});
-      }
-      base::StrAppend(&result, {flex_child.ToString()});
+gfx::Size BoxLayout::ViewWrapper::GetPreferredSize() const {
+  gfx::Size preferred_size = view_->GetPreferredSize();
+  if (!layout_->collapse_margins_spacing_)
+    preferred_size.Enlarge(margins_.width(), margins_.height());
+  return preferred_size;
+}
+
+void BoxLayout::ViewWrapper::SetBoundsRect(const gfx::Rect& bounds) {
+  gfx::Rect new_bounds = bounds;
+  if (!layout_->collapse_margins_spacing_) {
+    if (layout_->orientation_ == Orientation::kHorizontal) {
+      new_bounds.set_x(bounds.x() + margins_.left());
+      new_bounds.set_width(std::max(0, bounds.width() - margins_.width()));
+    } else {
+      new_bounds.set_y(bounds.y() + margins_.top());
+      new_bounds.set_height(std::max(0, bounds.height() - margins_.height()));
     }
-    base::StrAppend(&result,
-                    {"}\nmargin ", interior_margin.ToString(), " insets ",
-                     host_insets.ToString(), "\nmax_cross_margin ",
-                     max_cross_margin.ToString()});
-    return result;
   }
+  view_->SetBoundsRect(new_bounds);
+}
 
-  ProposedLayout layout;
-
-  // Holds additional information about the child views of this layout.
-  std::vector<BoxChildData> child_data;
-
-  NormalizedSize total_size;
-  NormalizedInsets host_insets;
-  NormalizedInsets interior_margin;
-  Inset1D max_cross_margin;
-  int cross_center_pos;
-};
+bool BoxLayout::ViewWrapper::VisibleToLayout() const {
+  return view_->GetVisible() && !view_->GetProperty(kViewIgnoredByLayoutKey);
+}
 
 // BoxLayoutFlexSpecification --------------------------------------------------
 
@@ -133,7 +122,7 @@
 void BoxLayout::SetOrientation(Orientation orientation) {
   if (orientation_ != orientation) {
     orientation_ = orientation;
-    InvalidateHost(true);
+    InvalidateLayout();
   }
 }
 
@@ -141,32 +130,10 @@
   return orientation_;
 }
 
-void BoxLayout::set_main_axis_alignment(MainAxisAlignment main_axis_alignment) {
-  if (main_axis_alignment_ != main_axis_alignment) {
-    main_axis_alignment_ = main_axis_alignment;
-    InvalidateHost(true);
-  }
-}
-
-void BoxLayout::set_cross_axis_alignment(
-    CrossAxisAlignment cross_axis_alignment) {
-  if (cross_axis_alignment_ != cross_axis_alignment) {
-    cross_axis_alignment_ = cross_axis_alignment;
-    InvalidateHost(true);
-  }
-}
-
-void BoxLayout::set_inside_border_insets(const gfx::Insets& insets) {
-  if (inside_border_insets_ != insets) {
-    inside_border_insets_ = insets;
-    InvalidateHost(true);
-  }
-}
-
 void BoxLayout::SetCollapseMarginsSpacing(bool collapse_margins_spacing) {
   if (collapse_margins_spacing != collapse_margins_spacing_) {
     collapse_margins_spacing_ = collapse_margins_spacing;
-    InvalidateHost(true);
+    InvalidateLayout();
   }
 }
 
@@ -177,8 +144,9 @@
 void BoxLayout::SetFlexForView(const View* view,
                                int flex_weight,
                                bool use_min_size) {
+  DCHECK(host_);
   DCHECK(view);
-  DCHECK_EQ(host_view(), view->parent());
+  DCHECK_EQ(host_, view->parent());
   DCHECK_GE(flex_weight, 0);
   const_cast<View*>(view)->SetProperty(kBoxLayoutFlexKey,
                                        BoxLayoutFlexSpecification()
@@ -200,68 +168,242 @@
   return default_flex_;
 }
 
-ProposedLayout BoxLayout::CalculateProposedLayout(
-    const SizeBounds& size_bounds) const {
-  BoxLayoutData data;
-  InitializeChildData(data);
+void BoxLayout::Layout(View* host) {
+  DCHECK_EQ(host_, host);
+  gfx::Rect child_area(host->GetContentsBounds());
 
-  gfx::Insets insets = host_view()->GetInsets();
-  data.interior_margin = Normalize(orientation_, inside_border_insets_);
+  AdjustMainAxisForMargin(&child_area);
+  gfx::Insets max_cross_axis_margin;
+  if (!collapse_margins_spacing_) {
+    AdjustCrossAxisForInsets(&child_area);
+    max_cross_axis_margin = CrossAxisMaxViewMargin();
+  }
+  if (child_area.IsEmpty())
+    return;
 
-  // TODO(crbug.com/1346889): In a vertical layout, if the width is not
-  // specified, we need to first calculate the maximum width of the view, which
-  // makes it convenient for us to call GetHeightForWidth later. If all views
-  // are modified to GetPreferredSize(const SizeBounds&), we might consider
-  // removing this part.
-  SizeBounds new_bounds(size_bounds);
-  if (!new_bounds.width().is_bounded() &&
-      orientation_ == Orientation::kVertical) {
-    new_bounds.set_width(CalculateMaxChildWidth(data));
+  int total_main_axis_size = 0;
+  int num_visible = 0;
+  int flex_sum = 0;
+  // Calculate the total size of children in the main axis.
+  for (auto i = host->children().cbegin(); i != host->children().cend(); ++i) {
+    const ViewWrapper child(this, *i);
+    if (!child.VisibleToLayout())
+      continue;
+    int flex = GetFlexForView(child.view());
+    int child_main_axis_size = MainAxisSizeForView(child, child_area.width());
+    if (child_main_axis_size == 0 && flex == 0)
+      continue;
+    total_main_axis_size +=
+        child_main_axis_size +
+        MainAxisMarginBetweenViews(
+            child, ViewWrapper(this, NextVisibleView(std::next(i))));
+    ++num_visible;
+    flex_sum += flex;
   }
 
-  NormalizedSizeBounds bounds = Normalize(orientation_, new_bounds);
+  if (!num_visible)
+    return;
 
-  // When |collapse_margins_spacing_ = true|, the host_insets include the
-  // leading of the first element and the trailing of the last one. It's crucial
-  // to keep this in mind while reading here. Conversely, they are not included.
-  if (collapse_margins_spacing_) {
-    NormalizedInsets main_axis_insets =
-        MaxAxisInsets(data.interior_margin,
-                      data.child_data.empty() ? NormalizedInsets()
-                                              : data.child_data.front().margins,
-                      data.interior_margin,
-                      data.child_data.empty() ? NormalizedInsets()
-                                              : data.child_data.back().margins);
-    data.host_insets = Normalize(
-        orientation_, insets + Denormalize(orientation_, main_axis_insets));
-  } else {
-    data.host_insets = Normalize(orientation_, insets + inside_border_insets_);
+  total_main_axis_size -= between_child_spacing_;
+  // Free space can be negative indicating that the views want to overflow.
+  int main_free_space = MainAxisSize(child_area) - total_main_axis_size;
+  int main_position = MainAxisPosition(child_area);
+  {
+    int size = MainAxisSize(child_area);
+    if (!flex_sum) {
+      switch (main_axis_alignment_) {
+        case MainAxisAlignment::kStart:
+          break;
+        case MainAxisAlignment::kCenter:
+          main_position += main_free_space / 2;
+          size = total_main_axis_size;
+          break;
+        case MainAxisAlignment::kEnd:
+          main_position += main_free_space;
+          size = total_main_axis_size;
+          break;
+        default:
+          NOTREACHED_NORETURN();
+      }
+    }
+    gfx::Rect new_child_area(child_area);
+    SetMainAxisPosition(main_position, &new_child_area);
+    SetMainAxisSize(size, &new_child_area);
+    child_area.Intersect(new_child_area);
   }
-  bounds.Inset(data.host_insets);
 
-  CalculatePreferredSize(Denormalize(orientation_, bounds), data);
+  int total_padding = 0;
+  int current_flex = 0;
+  for (auto i = host->children().cbegin(); i != host->children().cend(); ++i) {
+    ViewWrapper child(this, *i);
+    if (!child.VisibleToLayout())
+      continue;
 
-  // Calculate the size of the view without boundary constraints. This is the
-  // default size of our view.
-  CalculatePreferredTotalSize(data);
+    // TODO(bruthig): Fix this. The main axis should be calculated before
+    // the cross axis size because child Views may calculate their cross axis
+    // size based on their main axis size. See https://crbug.com/682266.
 
-  // Update the position information of the child views.
-  UpdateFlexLayout(bounds, data);
+    // Calculate cross axis size.
+    gfx::Rect bounds(child_area);
+    gfx::Rect min_child_area(child_area);
+    gfx::Insets child_margins;
+    if (collapse_margins_spacing_) {
+      child_margins = MaxAxisInsets(orientation_ == Orientation::kVertical
+                                        ? Axis::kHorizontal
+                                        : Axis::kVertical,
+                                    child.margins(), inside_border_insets_,
+                                    child.margins(), inside_border_insets_);
+    } else {
+      child_margins = child.margins();
+    }
 
-  NormalizedSize host_size = data.total_size;
-  host_size.Enlarge(data.host_insets.main_size(),
-                    data.host_insets.cross_size());
+    if (cross_axis_alignment_ == CrossAxisAlignment::kStretch ||
+        cross_axis_alignment_ == CrossAxisAlignment::kCenter) {
+      InsetCrossAxis(&min_child_area, CrossAxisLeadingInset(child_margins),
+                     CrossAxisTrailingInset(child_margins));
+    }
 
-  // TODO(weidongliu): see crbugs.com/1514004#c5, we handle compatibility here.
-  // Maybe we can remove this in the future.
-  if (collapse_margins_spacing_) {
-    host_size.Enlarge(data.interior_margin.main_size(), 0);
+    SetMainAxisPosition(main_position, &bounds);
+    if (cross_axis_alignment_ != CrossAxisAlignment::kStretch) {
+      int cross_axis_margin_size = CrossAxisMarginSizeForView(child);
+      int view_cross_axis_size =
+          CrossAxisSizeForView(child) - cross_axis_margin_size;
+      int free_space = CrossAxisSize(bounds) - view_cross_axis_size;
+      int position = CrossAxisPosition(bounds);
+      if (cross_axis_alignment_ == CrossAxisAlignment::kCenter) {
+        if (view_cross_axis_size > CrossAxisSize(min_child_area))
+          view_cross_axis_size = CrossAxisSize(min_child_area);
+        position += free_space / 2;
+        position = std::max(position, CrossAxisLeadingEdge(min_child_area));
+      } else if (cross_axis_alignment_ == CrossAxisAlignment::kEnd) {
+        position += free_space - CrossAxisTrailingInset(max_cross_axis_margin);
+        if (!collapse_margins_spacing_)
+          InsetCrossAxis(&min_child_area,
+                         CrossAxisLeadingInset(child.margins()),
+                         CrossAxisTrailingInset(max_cross_axis_margin));
+      } else {
+        position += CrossAxisLeadingInset(max_cross_axis_margin);
+        if (!collapse_margins_spacing_)
+          InsetCrossAxis(&min_child_area,
+                         CrossAxisLeadingInset(max_cross_axis_margin),
+                         CrossAxisTrailingInset(child.margins()));
+      }
+      SetCrossAxisPosition(position, &bounds);
+      SetCrossAxisSize(view_cross_axis_size, &bounds);
+    }
+
+    // Calculate flex padding.
+    int current_padding = 0;
+    int child_flex = GetFlexForView(child.view());
+    if (child_flex > 0) {
+      current_flex += child_flex;
+      int quot = (main_free_space * current_flex) / flex_sum;
+      int rem = (main_free_space * current_flex) % flex_sum;
+      current_padding = quot - total_padding;
+      // Use the current remainder to round to the nearest pixel.
+      if (std::abs(rem) * 2 >= flex_sum)
+        current_padding += main_free_space > 0 ? 1 : -1;
+      total_padding += current_padding;
+    }
+
+    // Set main axis size.
+    // TODO(bruthig): Use the allocated width to determine the cross axis size.
+    // See https://crbug.com/682266.
+    int child_main_axis_size = MainAxisSizeForView(child, child_area.width());
+    int child_min_size = GetMinimumSizeForView(child.view());
+    if (child_min_size > 0 && !collapse_margins_spacing_)
+      child_min_size += child.margins().width();
+    SetMainAxisSize(
+        std::max(child_min_size, child_main_axis_size + current_padding),
+        &bounds);
+    if (MainAxisSize(bounds) > 0 || GetFlexForView(child.view()) > 0) {
+      main_position +=
+          MainAxisSize(bounds) +
+          MainAxisMarginBetweenViews(
+              child, ViewWrapper(this, NextVisibleView(std::next(i))));
+    }
+
+    // Clamp child view bounds to |child_area|.
+    bounds.Intersect(min_child_area);
+    child.SetBoundsRect(bounds);
   }
-  data.layout.host_size = Denormalize(orientation_, host_size);
 
-  CalculateChildBounds(size_bounds, data);
+  // Flex views should have grown/shrunk to consume all free space.
+  if (flex_sum)
+    DCHECK_EQ(total_padding, main_free_space);
+}
 
-  return data.layout;
+gfx::Size BoxLayout::GetPreferredSize(const View* host) const {
+  DCHECK_EQ(host_, host);
+  // Calculate the child views' preferred width.
+  int width = 0;
+  if (orientation_ == Orientation::kVertical) {
+    // Calculating the child views' overall preferred width is a little involved
+    // because of the way the margins interact with |cross_axis_alignment_|.
+    int leading = 0;
+    int trailing = 0;
+    gfx::Rect child_view_area;
+    for (View* view : host_->children()) {
+      const ViewWrapper child(this, view);
+      if (!child.VisibleToLayout())
+        continue;
+
+      // We need to bypass the ViewWrapper GetPreferredSize() to get the actual
+      // raw view size because the margins along the cross axis are handled
+      // below.
+      gfx::Size child_size = child.view()->GetPreferredSize();
+      gfx::Insets child_margins;
+      if (collapse_margins_spacing_) {
+        child_margins = MaxAxisInsets(Axis::kHorizontal, child.margins(),
+                                      inside_border_insets_, child.margins(),
+                                      inside_border_insets_);
+      } else {
+        child_margins = child.margins();
+      }
+
+      // The value of |cross_axis_alignment_| will determine how the view's
+      // margins interact with each other or the |inside_border_insets_|.
+      if (cross_axis_alignment_ == CrossAxisAlignment::kStart) {
+        leading = std::max(leading, CrossAxisLeadingInset(child_margins));
+        width = std::max(
+            width, child_size.width() + CrossAxisTrailingInset(child_margins));
+      } else if (cross_axis_alignment_ == CrossAxisAlignment::kEnd) {
+        trailing = std::max(trailing, CrossAxisTrailingInset(child_margins));
+        width = std::max(
+            width, child_size.width() + CrossAxisLeadingInset(child_margins));
+      } else {
+        // We don't have a rectangle which can be used to calculate a common
+        // center-point, so a single known point (0) along the horizontal axis
+        // is used. This is OK because we're only interested in the overall
+        // width and not the position.
+        gfx::Rect child_bounds =
+            gfx::Rect(-(child_size.width() / 2), 0, child_size.width(),
+                      child_size.height());
+        child_bounds.Inset(gfx::Insets::TLBR(0, -child.margins().left(), 0,
+                                             -child.margins().right()));
+        child_view_area.Union(child_bounds);
+        width = std::max(width, child_view_area.width());
+      }
+    }
+    width = std::max(width + leading + trailing, minimum_cross_axis_size_);
+  }
+
+  return GetPreferredSizeForChildWidth(host, width);
+}
+
+int BoxLayout::GetPreferredHeightForWidth(const View* host, int width) const {
+  DCHECK_EQ(host_, host);
+  int child_width = width - NonChildSize(host).width();
+  return GetPreferredSizeForChildWidth(host, child_width).height();
+}
+
+void BoxLayout::Installed(View* host) {
+  DCHECK(!host_);
+  host_ = host;
+}
+
+void BoxLayout::ViewRemoved(View* host, View* view) {
+  ClearFlexForView(view);
 }
 
 int BoxLayout::GetFlexForView(const View* view) const {
@@ -284,458 +426,282 @@
              : view->GetMinimumSize().height();
 }
 
-gfx::Size BoxLayout::GetPreferredSizeForView(
-    const View* view,
-    const NormalizedSizeBounds& size_bounds) const {
-  // TODO(crbug.com/1346889): If all views are migrated to
-  // GetPreferredSize(const SizeBounds&), simplify the processing here.
-  if (orientation_ == Orientation::kVertical &&
-      cross_axis_alignment_ == CrossAxisAlignment::kStretch) {
-    int width = size_bounds.cross().value();
-    return gfx::Size(width, view->GetHeightForWidth(width));
+int BoxLayout::MainAxisSize(const gfx::Rect& rect) const {
+  return orientation_ == Orientation::kHorizontal ? rect.width()
+                                                  : rect.height();
+}
+
+int BoxLayout::MainAxisPosition(const gfx::Rect& rect) const {
+  return orientation_ == Orientation::kHorizontal ? rect.x() : rect.y();
+}
+
+void BoxLayout::SetMainAxisSize(int size, gfx::Rect* rect) const {
+  if (orientation_ == Orientation::kHorizontal)
+    rect->set_width(size);
+  else
+    rect->set_height(size);
+}
+
+void BoxLayout::SetMainAxisPosition(int position, gfx::Rect* rect) const {
+  if (orientation_ == Orientation::kHorizontal)
+    rect->set_x(position);
+  else
+    rect->set_y(position);
+}
+
+int BoxLayout::CrossAxisSize(const gfx::Rect& rect) const {
+  return orientation_ == Orientation::kVertical ? rect.width() : rect.height();
+}
+
+int BoxLayout::CrossAxisPosition(const gfx::Rect& rect) const {
+  return orientation_ == Orientation::kVertical ? rect.x() : rect.y();
+}
+
+void BoxLayout::SetCrossAxisSize(int size, gfx::Rect* rect) const {
+  if (orientation_ == Orientation::kVertical)
+    rect->set_width(size);
+  else
+    rect->set_height(size);
+}
+
+void BoxLayout::SetCrossAxisPosition(int position, gfx::Rect* rect) const {
+  if (orientation_ == Orientation::kVertical)
+    rect->set_x(position);
+  else
+    rect->set_y(position);
+}
+
+int BoxLayout::MainAxisSizeForView(const ViewWrapper& view,
+                                   int child_area_width) const {
+  if (orientation_ == Orientation::kHorizontal) {
+    return view.GetPreferredSize().width();
   } else {
-    gfx::Size bounded_preferred_size =
-        view->GetPreferredSize(Denormalize(orientation_, size_bounds));
-    if (orientation_ == Orientation::kHorizontal) {
-      return bounded_preferred_size;
-    } else {
-      int width = size_bounds.cross().min_of(bounded_preferred_size.width());
-      return gfx::Size(width, view->GetHeightForWidth(width));
-    }
+    // To calculate the height we use the preferred width of the child
+    // unless we're asked to stretch or the preferred width exceeds the
+    // available width.
+    return view.GetHeightForWidth(
+        cross_axis_alignment_ == CrossAxisAlignment::kStretch
+            ? child_area_width
+            : std::min(child_area_width, view.GetPreferredSize().width()));
   }
 }
 
-void BoxLayout::EnsureCrossSize(BoxLayoutData& data) const {
-  if (minimum_cross_axis_size_ > data.total_size.cross()) {
-    if (cross_axis_alignment_ == CrossAxisAlignment::kCenter) {
-      data.cross_center_pos +=
-          (minimum_cross_axis_size_ - data.total_size.cross()) / 2;
-    }
-    data.total_size.set_cross(minimum_cross_axis_size_);
-  }
+int BoxLayout::MainAxisLeadingInset(const gfx::Insets& insets) const {
+  return orientation_ == Orientation::kHorizontal ? insets.left()
+                                                  : insets.top();
 }
 
-void BoxLayout::InitializeChildData(BoxLayoutData& data) const {
+int BoxLayout::MainAxisTrailingInset(const gfx::Insets& insets) const {
+  return orientation_ == Orientation::kHorizontal ? insets.right()
+                                                  : insets.bottom();
+}
+
+int BoxLayout::CrossAxisLeadingEdge(const gfx::Rect& rect) const {
+  return orientation_ == Orientation::kVertical ? rect.x() : rect.y();
+}
+
+int BoxLayout::CrossAxisLeadingInset(const gfx::Insets& insets) const {
+  return orientation_ == Orientation::kVertical ? insets.left() : insets.top();
+}
+
+int BoxLayout::CrossAxisTrailingInset(const gfx::Insets& insets) const {
+  return orientation_ == Orientation::kVertical ? insets.right()
+                                                : insets.bottom();
+}
+
+int BoxLayout::MainAxisMarginBetweenViews(const ViewWrapper& leading,
+                                          const ViewWrapper& trailing) const {
+  if (!collapse_margins_spacing_ || !leading.view() || !trailing.view())
+    return between_child_spacing_;
+  return std::max(between_child_spacing_,
+                  std::max(MainAxisTrailingInset(leading.margins()),
+                           MainAxisLeadingInset(trailing.margins())));
+}
+
+gfx::Insets BoxLayout::MainAxisOuterMargin() const {
+  if (collapse_margins_spacing_) {
+    const ViewWrapper first(this, FirstVisibleView());
+    const ViewWrapper last(this, LastVisibleView());
+    return MaxAxisInsets(orientation_ == Orientation::kHorizontal
+                             ? Axis::kHorizontal
+                             : Axis::kVertical,
+                         inside_border_insets_, first.margins(),
+                         inside_border_insets_, last.margins());
+  }
+  return MaxAxisInsets(orientation_ == Orientation::kHorizontal
+                           ? Axis::kHorizontal
+                           : Axis::kVertical,
+                       inside_border_insets_, gfx::Insets(),
+                       inside_border_insets_, gfx::Insets());
+}
+
+gfx::Insets BoxLayout::CrossAxisMaxViewMargin() const {
   int leading = 0;
   int trailing = 0;
-
-  for (View* child : host_view()->children()) {
-    // If we calculate here in View::ChildVisibilityChanged, LayoutManagerBase
-    // will not modify the visibility property of the view in time, so we need
-    // to explicitly judge here.
-    if (!IsChildIncludedInLayout(child, true) || !child->GetVisible()) {
+  for (View* view : host_->children()) {
+    const ViewWrapper child(this, view);
+    if (!child.VisibleToLayout())
       continue;
-    }
-
-    data.child_data.emplace_back();
-    BoxChildData& child_data = data.child_data.back();
-
-    data.layout.child_layouts.emplace_back(child, true);
-
-    gfx::Insets* margins = child ? child->GetProperty(kMarginsKey) : nullptr;
-    if (margins) {
-      child_data.margins = Normalize(orientation_, *margins);
-    }
-
-    child_data.flex = GetFlexForView(child);
-
-    leading = std::max(leading, child_data.margins.cross_leading());
-    trailing = std::max(trailing, child_data.margins.cross_trailing());
+    leading = std::max(leading, CrossAxisLeadingInset(child.margins()));
+    trailing = std::max(trailing, CrossAxisTrailingInset(child.margins()));
   }
-
-  data.max_cross_margin.set_leading(leading);
-  data.max_cross_margin.set_trailing(trailing);
-  data.cross_center_pos = 0;
+  if (orientation_ == Orientation::kVertical)
+    return gfx::Insets::TLBR(0, leading, 0, trailing);
+  return gfx::Insets::TLBR(leading, 0, trailing, 0);
 }
 
-SizeBound BoxLayout::CalculateMaxChildWidth(BoxLayoutData& data) const {
-  gfx::Rect child_view_area;
-  SizeBound width = 0;
-
-  for (size_t i = 0; i < data.num_children(); ++i) {
-    ChildLayout& child_layout = data.layout.child_layouts[i];
-
-    gfx::Size child_size =
-        child_layout.child_view->GetPreferredSize({/* Unbounded */});
-    NormalizedInsets child_margins = GetChildMargins(data, i);
-
-    // The value of |cross_axis_alignment_| will determine how the view's
-    // margins interact with each other or the |inside_border_insets_|.
-    if (cross_axis_alignment_ == CrossAxisAlignment::kStart) {
-      width = std::max<SizeBound>(
-          width, child_size.width() + child_margins.cross_trailing());
-    } else if (cross_axis_alignment_ == CrossAxisAlignment::kEnd) {
-      width = std::max<SizeBound>(
-          width, child_size.width() + child_margins.cross_leading());
-    } else {
-      // We don't have a rectangle which can be used to calculate a common
-      // center-point, so a single known point (0) along the horizontal axis
-      // is used. This is OK because we're only interested in the overall
-      // width and not the position.
-      gfx::Rect child_bounds =
-          gfx::Rect(-(child_size.width() / 2), 0, child_size.width(),
-                    child_size.height());
-      child_bounds.Inset(gfx::Insets::TLBR(0, -child_margins.cross_leading(), 0,
-                                           -child_margins.cross_trailing()));
-      child_view_area.Union(child_bounds);
-      width = std::max<SizeBound>(width, child_view_area.width());
-    }
-  }
-
-  int extra_cross_margin = host_view()->GetInsets().width();
-  if (cross_axis_alignment_ == CrossAxisAlignment::kStart) {
-    extra_cross_margin = data.max_cross_margin.leading();
-  } else if (cross_axis_alignment_ == CrossAxisAlignment::kEnd) {
-    extra_cross_margin = data.max_cross_margin.trailing();
-  }
-
-  if (!collapse_margins_spacing_) {
-    extra_cross_margin += data.interior_margin.cross_size();
-  }
-
-  return std::max<SizeBound>(width + extra_cross_margin,
-                             minimum_cross_axis_size_);
+void BoxLayout::AdjustMainAxisForMargin(gfx::Rect* rect) const {
+  rect->Inset(MainAxisOuterMargin());
 }
 
-void BoxLayout::CalculatePreferredSize(const SizeBounds& bounds,
-                                       BoxLayoutData& data) const {
-  if (orientation_ == Orientation::kVertical) {
-    for (size_t i = 0; i < data.num_children(); ++i) {
-      BoxChildData& box_child = data.child_data[i];
-      ChildLayout& child_layout = data.layout.child_layouts[i];
-      SizeBound available_width = std::max<SizeBound>(
-          0, bounds.width() - box_child.margins.cross_size());
+void BoxLayout::AdjustCrossAxisForInsets(gfx::Rect* rect) const {
+  rect->Inset(orientation_ == Orientation::kVertical
+                  ? gfx::Insets::TLBR(0, inside_border_insets_.left(), 0,
+                                      inside_border_insets_.right())
+                  : gfx::Insets::TLBR(inside_border_insets_.top(), 0,
+                                      inside_border_insets_.bottom(), 0));
+}
 
+int BoxLayout::CrossAxisSizeForView(const ViewWrapper& view) const {
+  // TODO(bruthig): For horizontal case use the available width and not the
+  // preferred width. See https://crbug.com/682266.
+  return orientation_ == Orientation::kVertical
+             ? view.GetPreferredSize().width()
+             : view.GetHeightForWidth(view.GetPreferredSize().width());
+}
+
+int BoxLayout::CrossAxisMarginSizeForView(const ViewWrapper& view) const {
+  return collapse_margins_spacing_ ? 0
+                                   : (orientation_ == Orientation::kVertical
+                                          ? view.margins().width()
+                                          : view.margins().height());
+}
+
+int BoxLayout::CrossAxisLeadingMarginForView(const ViewWrapper& view) const {
+  return collapse_margins_spacing_ ? 0 : CrossAxisLeadingInset(view.margins());
+}
+
+void BoxLayout::InsetCrossAxis(gfx::Rect* rect,
+                               int leading,
+                               int trailing) const {
+  if (orientation_ == Orientation::kVertical)
+    rect->Inset(gfx::Insets::TLBR(0, leading, 0, trailing));
+  else
+    rect->Inset(gfx::Insets::TLBR(leading, 0, trailing, 0));
+}
+
+gfx::Size BoxLayout::GetPreferredSizeForChildWidth(const View* host,
+                                                   int child_area_width) const {
+  DCHECK_EQ(host, host_);
+  gfx::Rect child_area_bounds;
+
+  if (orientation_ == Orientation::kHorizontal) {
+    // Horizontal layouts ignore |child_area_width|, meaning they mimic the
+    // default behavior of GridLayout::GetPreferredHeightForWidth().
+    // TODO(estade|bruthig): Fix this See // https://crbug.com/682266.
+    int position = 0;
+    gfx::Insets max_margins = CrossAxisMaxViewMargin();
+    for (auto i = host->children().cbegin(); i != host->children().cend();
+         ++i) {
+      const ViewWrapper child(this, *i);
+      if (!child.VisibleToLayout())
+        continue;
+
+      gfx::Size size(child.view()->GetPreferredSize());
+      if (size.IsEmpty())
+        continue;
+
+      gfx::Rect child_bounds = gfx::Rect(
+          position, 0,
+          size.width() +
+              (!collapse_margins_spacing_ ? child.margins().width() : 0),
+          size.height());
+      gfx::Insets child_margins;
+      if (collapse_margins_spacing_)
+        child_margins = MaxAxisInsets(Axis::kVertical, child.margins(),
+                                      inside_border_insets_, child.margins(),
+                                      inside_border_insets_);
+      else
+        child_margins = child.margins();
+
+      if (cross_axis_alignment_ == CrossAxisAlignment::kStart) {
+        child_bounds.Inset(
+            gfx::Insets::TLBR(-CrossAxisLeadingInset(max_margins), 0,
+                              -child_margins.bottom(), 0));
+        child_bounds.set_origin(gfx::Point(position, 0));
+      } else if (cross_axis_alignment_ == CrossAxisAlignment::kEnd) {
+        child_bounds.Inset(gfx::Insets::TLBR(
+            -child_margins.top(), 0, -CrossAxisTrailingInset(max_margins), 0));
+        child_bounds.set_origin(gfx::Point(position, 0));
+      } else {
+        child_bounds.set_origin(
+            gfx::Point(position, -(child_bounds.height() / 2)));
+        child_bounds.Inset(gfx::Insets::TLBR(-child_margins.top(), 0,
+                                             -child_margins.bottom(), 0));
+      }
+
+      child_area_bounds.Union(child_bounds);
+      position += child_bounds.width() +
+                  MainAxisMarginBetweenViews(
+                      child, ViewWrapper(this, NextVisibleView(std::next(i))));
+    }
+    child_area_bounds.set_height(
+        std::max(child_area_bounds.height(), minimum_cross_axis_size_));
+  } else {
+    int height = 0;
+    for (auto i = host->children().cbegin(); i != host->children().cend();
+         ++i) {
+      const ViewWrapper child(this, *i);
+      if (!child.VisibleToLayout())
+        continue;
+
+      const ViewWrapper next(this, NextVisibleView(std::next(i)));
       // Use the child area width for getting the height if the child is
       // supposed to stretch. Use its preferred size otherwise.
-      int actual_width =
-          cross_axis_alignment_ == CrossAxisAlignment::kStretch
-              ? available_width.value()
-              : std::min(
-                    available_width.value(),
-                    child_layout.child_view->GetPreferredSize({/* Unbounded */})
-                        .width());
-
-      if (collapse_margins_spacing_) {
-        int height = child_layout.child_view->GetHeightForWidth(actual_width);
-        box_child.preferred_size = NormalizedSize(height, actual_width);
-      } else {
-        actual_width = std::max(0, actual_width);
-        int height = child_layout.child_view->GetHeightForWidth(actual_width);
-        box_child.preferred_size = NormalizedSize(height, actual_width);
-      }
+      int extra_height = MainAxisSizeForView(child, child_area_width);
+      // Only add |between_child_spacing_| if this is not the only child.
+      if (next.view() && extra_height > 0)
+        height += MainAxisMarginBetweenViews(child, next);
+      height += extra_height;
     }
-  } else {
-    for (size_t i = 0; i < data.num_children(); ++i) {
-      BoxChildData& box_child = data.child_data[i];
-      ChildLayout& child_layout = data.layout.child_layouts[i];
 
-      box_child.preferred_size = Normalize(
-          orientation_, child_layout.child_view->GetPreferredSize(bounds));
-    }
+    child_area_bounds.set_width(child_area_width);
+    child_area_bounds.set_height(height);
   }
+
+  gfx::Size non_child_size = NonChildSize(host_);
+  return gfx::Size(child_area_bounds.width() + non_child_size.width(),
+                   child_area_bounds.height() + non_child_size.height());
 }
 
-NormalizedInsets BoxLayout::GetChildMargins(BoxLayoutData& data,
-                                            size_t index) const {
-  BoxChildData& box_child = data.child_data[index];
-
-  if (collapse_margins_spacing_) {
-    const size_t num_child = data.num_children();
-    NormalizedInsets leading =
-        index == 0 ? data.interior_margin : data.child_data[index - 1].margins;
-    NormalizedInsets trailing = index == num_child - 1
-                                    ? data.interior_margin
-                                    : data.child_data[index + 1].margins;
-
-    return NormalizedInsets(
-        std::max(box_child.margins.main_leading(), leading.main_trailing()),
-        std::max(box_child.margins.cross_leading(),
-                 data.interior_margin.cross_leading()),
-        std::max(box_child.margins.main_trailing(), trailing.main_leading()),
-        std::max(box_child.margins.cross_trailing(),
-                 data.interior_margin.cross_trailing()));
-  } else {
-    return box_child.margins;
-  }
+gfx::Size BoxLayout::NonChildSize(const View* host) const {
+  gfx::Insets insets(host->GetInsets());
+  if (!collapse_margins_spacing_)
+    return gfx::Size(insets.width() + inside_border_insets_.width(),
+                     insets.height() + inside_border_insets_.height());
+  gfx::Insets main_axis = MainAxisOuterMargin();
+  gfx::Insets cross_axis = inside_border_insets_;
+  return gfx::Size(insets.width() + main_axis.width() + cross_axis.width(),
+                   insets.height() + main_axis.height() + cross_axis.height());
 }
 
-void BoxLayout::CalculatePreferredTotalSize(BoxLayoutData& data) const {
-  for (size_t i = 0; i < data.num_children(); ++i) {
-    BoxChildData& box_child = data.child_data[i];
-
-    int main_size = box_child.preferred_size.main();
-    if (!collapse_margins_spacing_) {
-      main_size += box_child.margins.main_size();
-    }
-
-    if (main_size == 0 && box_child.flex == 0) {
-      continue;
-    }
-
-    NormalizedInsets child_margins = GetChildMargins(data, i);
-
-    if (i < data.num_children() - 1) {
-      if (collapse_margins_spacing_) {
-        main_size +=
-            std::max(between_child_spacing_, child_margins.main_trailing());
-      } else {
-        main_size += between_child_spacing_;
-      }
-    }
-
-    int cross_size = box_child.preferred_size.cross();
-    if (cross_axis_alignment_ == CrossAxisAlignment::kStart) {
-      cross_size +=
-          data.max_cross_margin.leading() + child_margins.cross_trailing();
-    } else if (cross_axis_alignment_ == CrossAxisAlignment::kEnd) {
-      cross_size +=
-          data.max_cross_margin.trailing() + child_margins.cross_leading();
-    } else {
-      // We implement center alignment by moving the central axis.
-      int view_center = box_child.preferred_size.cross() / 2;
-      int old_cross_center_pos = data.cross_center_pos;
-      data.cross_center_pos = std::max(
-          data.cross_center_pos, child_margins.cross_leading() + view_center);
-      cross_size = data.cross_center_pos + box_child.preferred_size.cross() -
-                   view_center + child_margins.cross_trailing();
-      // If the new center point has moved to the right relative to the original
-      // center point, then we need to move all the views to the right, so the
-      // original total size increases by |data.cross_center_pos -
-      // old_cross_center_pos|.
-      data.total_size.Enlarge(
-          0, std::max(0, data.cross_center_pos - old_cross_center_pos));
-    }
-    data.total_size.SetSize(data.total_size.main() + main_size,
-                            std::max(data.total_size.cross(), cross_size));
-  }
-
-  EnsureCrossSize(data);
+View* BoxLayout::NextVisibleView(View::Views::const_iterator pos) const {
+  const auto i = std::find_if(pos, host_->children().cend(), [this](View* v) {
+    return ViewWrapper(this, v).VisibleToLayout();
+  });
+  return (i == host_->children().cend()) ? nullptr : *i;
 }
 
-void BoxLayout::UpdateFlexLayout(const NormalizedSizeBounds& bounds,
-                                 BoxLayoutData& data) const {
-  if (bounds.main() == 0 && bounds.cross() == 0) {
-    return;
-  }
-
-  int total_main_axis_size = data.total_size.main();
-  int flex_sum = std::accumulate(
-      data.child_data.cbegin(), data.child_data.cend(), 0,
-      [](int total, const BoxChildData& data) { return total + data.flex; });
-
-  // Free space can be negative indicating that the views want to overflow.
-  SizeBound main_free_space = bounds.main() - total_main_axis_size;
-  int total_padding = 0;
-  int current_flex = 0;
-  const size_t num_child = data.num_children();
-  const int preferred_cross = data.total_size.cross();
-  data.total_size = NormalizedSize();
-  data.cross_center_pos = 0;
-
-  for (size_t i = 0; i < num_child; ++i) {
-    BoxChildData& box_child = data.child_data[i];
-    ChildLayout& child_layout = data.layout.child_layouts[i];
-
-    NormalizedInsets child_margins = GetChildMargins(data, i);
-
-    if (!collapse_margins_spacing_) {
-      data.total_size.Enlarge(box_child.margins.main_leading(), 0);
-    }
-
-    box_child.actual_bounds.set_origin_main(data.total_size.main());
-    SizeBound cross_axis_size =
-        bounds.cross().is_bounded() && bounds.cross().value() > 0
-            ? bounds.cross()
-            : preferred_cross;
-    if (cross_axis_alignment_ == CrossAxisAlignment::kStretch ||
-        cross_axis_alignment_ == CrossAxisAlignment::kCenter) {
-      cross_axis_size -= child_margins.cross_size();
-    }
-
-    // Calculate flex padding.
-    int current_padding = 0;
-    int child_flex = box_child.flex;
-    if (main_free_space.is_bounded() && child_flex > 0) {
-      current_flex += child_flex;
-      int quot = (main_free_space.value() * current_flex) / flex_sum;
-      int rem = (main_free_space.value() * current_flex) % flex_sum;
-      current_padding = quot - total_padding;
-      // Use the current remainder to round to the nearest pixel.
-      if (std::abs(rem) * 2 >= flex_sum) {
-        current_padding += main_free_space > 0 ? 1 : -1;
-      }
-      total_padding += current_padding;
-    }
-
-    // Set main axis size.
-    box_child.preferred_size = Normalize(
-        orientation_,
-        GetPreferredSizeForView(
-            child_layout.child_view,
-            NormalizedSizeBounds(
-                std::max<SizeBound>(0, bounds.main() - data.total_size.main()),
-                cross_axis_size)));
-    int child_main_axis_size = box_child.preferred_size.main();
-
-    int child_min_size = GetMinimumSizeForView(child_layout.child_view);
-    if (child_min_size > 0 && !collapse_margins_spacing_) {
-      child_min_size += box_child.margins.main_leading();
-    }
-
-    box_child.actual_bounds.set_size_main(
-        std::max(child_min_size, child_main_axis_size + current_padding));
-    if (box_child.actual_bounds.size_main() > 0 || box_child.flex > 0) {
-      data.total_size.set_main(box_child.actual_bounds.max_main());
-      if (i < num_child - 1) {
-        if (collapse_margins_spacing_) {
-          data.total_size.Enlarge(
-              std::max(between_child_spacing_, child_margins.main_trailing()),
-              0);
-        } else {
-          data.total_size.Enlarge(between_child_spacing_, 0);
-        }
-      }
-
-      if (!collapse_margins_spacing_) {
-        data.total_size.Enlarge(child_margins.main_trailing(), 0);
-      }
-    } else if (!collapse_margins_spacing_) {
-      // TODO(weidongliu): see crbugs.com/1514004#c4. If a view with a 0
-      // preferred size has a margin, it will be considered for main_leading but
-      // not for main_trailing.
-      data.total_size.set_main(data.total_size.main() +
-                               child_margins.main_leading());
-    }
-
-    int cross_size = box_child.preferred_size.cross();
-    if (cross_axis_alignment_ == CrossAxisAlignment::kStart) {
-      cross_size +=
-          data.max_cross_margin.leading() + child_margins.cross_trailing();
-    } else if (cross_axis_alignment_ == CrossAxisAlignment::kEnd) {
-      cross_size +=
-          data.max_cross_margin.trailing() + child_margins.cross_leading();
-    } else {
-      int view_center = box_child.preferred_size.cross() / 2;
-      // When center aligning, if the size is an odd number, we want the view to
-      // be to the left instead of to the right.
-      if (cross_axis_alignment_ == CrossAxisAlignment::kCenter) {
-        view_center += box_child.preferred_size.cross() & 1;
-      }
-
-      int old_cross_center_pos = data.cross_center_pos;
-      data.cross_center_pos = std::max(
-          data.cross_center_pos, child_margins.cross_leading() + view_center);
-      cross_size = data.cross_center_pos + box_child.preferred_size.cross() -
-                   view_center + child_margins.cross_trailing();
-
-      // If the new center point has moved to the right relative to the original
-      // center point, then we need to move all the views to the right, so the
-      // original total size increases by |data.cross_center_pos -
-      // old_cross_center_pos|.
-      data.total_size.Enlarge(
-          0, std::max(0, data.cross_center_pos - old_cross_center_pos));
-    }
-    data.total_size.set_cross(std::max(data.total_size.cross(), cross_size));
-  }
-
-  EnsureCrossSize(data);
-
-  for (size_t i = 0; i < num_child; ++i) {
-    BoxChildData& box_child = data.child_data[i];
-    if (box_child.preferred_size.cross() == 0) {
-      continue;
-    }
-
-    NormalizedInsets child_margins = GetChildMargins(data, i);
-    SizeBound cross_axis_size =
-        bounds.cross().is_bounded() && bounds.cross().value() > 0
-            ? bounds.cross()
-            : data.total_size.cross();
-    cross_axis_size -= child_margins.cross_size();
-
-    if (cross_axis_alignment_ == CrossAxisAlignment::kCenter) {
-      box_child.actual_bounds.set_size_cross(
-          std::min(cross_axis_size.value(), box_child.preferred_size.cross()));
-      int view_center = box_child.actual_bounds.size_cross() / 2 +
-                        (box_child.actual_bounds.size_cross() & 1);
-      int available_cross = cross_axis_size.value() +
-                            child_margins.cross_size() -
-                            data.total_size.cross();
-      box_child.actual_bounds.set_origin_cross(
-          data.cross_center_pos - view_center + available_cross / 2);
-    } else {
-      Span container(child_margins.cross_leading(), cross_axis_size.value());
-      Span new_cross(0, std::min(cross_axis_size.value(),
-                                 box_child.preferred_size.cross()));
-      new_cross.Align(container, cross_axis_alignment_);
-      box_child.actual_bounds.set_origin_cross(new_cross.start());
-      box_child.actual_bounds.set_size_cross(new_cross.length());
-    }
-  }
-
-  // Flex views should have grown/shrunk to consume all free space.
-  if (flex_sum && main_free_space.is_bounded()) {
-    DCHECK_EQ(total_padding, main_free_space);
-  }
+View* BoxLayout::FirstVisibleView() const {
+  return NextVisibleView(host_->children().begin());
 }
 
-void BoxLayout::CalculateChildBounds(const SizeBounds& size_bounds,
-                                     BoxLayoutData& data) const {
-  // Apply main axis alignment (we've already done cross-axis alignment above).
-  const NormalizedSizeBounds normalized_bounds =
-      Normalize(orientation_, size_bounds);
-  const NormalizedSize normalized_host_size =
-      Normalize(orientation_, data.layout.host_size);
-  int available_main = normalized_bounds.main().is_bounded()
-                           ? normalized_bounds.main().value()
-                           : normalized_host_size.main();
-  available_main = std::max(0, available_main - data.host_insets.main_size());
-  const int excess_main = available_main - data.total_size.main();
-  NormalizedPoint start(data.host_insets.main_leading(),
-                        data.host_insets.cross_leading());
-
-  int flex_sum = std::accumulate(
-      data.child_data.cbegin(), data.child_data.cend(), 0,
-      [](int total, const BoxChildData& data) { return total + data.flex; });
-  // BoxLayoutTest.FlexShrinkHorizontal relies on this behavior, why is
-  // alignment needed only when there is no flex?
-  if (!flex_sum) {
-    switch (main_axis_alignment()) {
-      case LayoutAlignment::kStart:
-        break;
-      case LayoutAlignment::kCenter:
-        start.set_main(start.main() + excess_main / 2);
-        break;
-      case LayoutAlignment::kEnd:
-        start.set_main(start.main() + excess_main);
-        break;
-      case LayoutAlignment::kStretch:
-      case LayoutAlignment::kBaseline:
-        NOTIMPLEMENTED();
-        break;
-    }
-  }
-
-  // Calculate the actual child bounds.
-  for (size_t i = 0; i < data.num_children(); ++i) {
-    ChildLayout& child_layout = data.layout.child_layouts[i];
-    BoxChildData& box_child = data.child_data[i];
-    NormalizedRect actual = box_child.actual_bounds;
-    actual.Offset(start.main(), start.cross());
-    // If the view exceeds the space, truncate the view.
-    if (actual.origin_main() < data.host_insets.main_leading()) {
-      actual.SetByBounds(data.host_insets.main_leading(), actual.origin_cross(),
-                         actual.max_main(), actual.max_cross());
-    }
-
-    if (actual.max_main() > data.host_insets.main_leading() + available_main) {
-      actual.SetByBounds(actual.origin_main(), actual.origin_cross(),
-                         data.host_insets.main_leading() + available_main,
-                         actual.max_cross());
-    }
-    child_layout.bounds = Denormalize(orientation_, actual);
-  }
+View* BoxLayout::LastVisibleView() const {
+  const auto& children = host_->children();
+  const auto i = base::ranges::find_if(
+      base::Reversed(children),
+      [this](View* v) { return ViewWrapper(this, v).VisibleToLayout(); });
+  return (i == children.crend()) ? nullptr : *i;
 }
 
 }  // namespace views
diff --git a/ui/views/layout/box_layout.h b/ui/views/layout/box_layout.h
index 1213cf21..4e0026b 100644
--- a/ui/views/layout/box_layout.h
+++ b/ui/views/layout/box_layout.h
@@ -8,11 +8,14 @@
 #include "base/memory/raw_ptr.h"
 #include "base/memory/raw_ptr_exclusion.h"
 #include "ui/gfx/geometry/insets.h"
-#include "ui/views/layout/layout_manager_base.h"
-#include "ui/views/layout/layout_types.h"
-#include "ui/views/layout/normalized_geometry.h"
+#include "ui/views/layout/layout_manager.h"
 #include "ui/views/view.h"
 
+namespace gfx {
+class Rect;
+class Size;
+}  // namespace gfx
+
 namespace views {
 
 class VIEWS_EXPORT BoxLayoutFlexSpecification {
@@ -39,20 +42,35 @@
 // child views are always sized according to their preferred size. If the
 // host's bounds provide insufficient space, child views will be clamped.
 // Excess space will not be distributed.
-class VIEWS_EXPORT BoxLayout : public LayoutManagerBase {
+class VIEWS_EXPORT BoxLayout : public LayoutManager {
  public:
-  using Orientation = LayoutOrientation;
+  enum class Orientation {
+    kHorizontal,
+    kVertical,
+  };
 
   // This specifies that the start/center/end of the collective child views is
   // aligned with the start/center/end of the host view. e.g. a horizontal
   // layout of MainAxisAlignment::kEnd will result in the child views being
   // right-aligned.
-  using MainAxisAlignment = LayoutAlignment;
+  enum class MainAxisAlignment {
+    kStart,
+    kCenter,
+    kEnd,
+    // TODO(calamity): Add MAIN_AXIS_ALIGNMENT_JUSTIFY which spreads blank space
+    // in-between the child views.
+  };
 
   // This specifies where along the cross axis the children should be laid out.
   // e.g. a horizontal layout of kEnd will result in the child views being
   // bottom-aligned.
-  using CrossAxisAlignment = LayoutAlignment;
+  enum class CrossAxisAlignment {
+    // This causes the child view to stretch to fit the host in the cross axis.
+    kStretch,
+    kStart,
+    kCenter,
+    kEnd,
+  };
 
   // Use |inside_border_insets| to add additional space between the child
   // view area and the host view border. |between_child_spacing| controls the
@@ -116,15 +134,21 @@
 
   // TODO(tluk): These class member setters should likely be calling
   // LayoutManager::InvalidateLayout() .
-  void set_main_axis_alignment(MainAxisAlignment main_axis_alignment);
+  void set_main_axis_alignment(MainAxisAlignment main_axis_alignment) {
+    main_axis_alignment_ = main_axis_alignment;
+  }
   MainAxisAlignment main_axis_alignment() const { return main_axis_alignment_; }
 
-  void set_cross_axis_alignment(CrossAxisAlignment cross_axis_alignment);
+  void set_cross_axis_alignment(CrossAxisAlignment cross_axis_alignment) {
+    cross_axis_alignment_ = cross_axis_alignment;
+  }
   CrossAxisAlignment cross_axis_alignment() const {
     return cross_axis_alignment_;
   }
 
-  void set_inside_border_insets(const gfx::Insets& insets);
+  void set_inside_border_insets(const gfx::Insets& insets) {
+    inside_border_insets_ = insets;
+  }
   const gfx::Insets& inside_border_insets() const {
     return inside_border_insets_;
   }
@@ -164,13 +188,52 @@
   void SetDefaultFlex(int default_flex);
   int GetDefaultFlex() const;
 
- protected:
   // Overridden from views::LayoutManager:
-  ProposedLayout CalculateProposedLayout(
-      const SizeBounds& size_bounds) const override;
+  void Installed(View* host) override;
+  void ViewRemoved(View* host, View* view) override;
+  void Layout(View* host) override;
+  gfx::Size GetPreferredSize(const View* host) const override;
+  int GetPreferredHeightForWidth(const View* host, int width) const override;
 
  private:
-  struct BoxLayoutData;
+  // This struct is used internally to "wrap" a child view in order to obviate
+  // the need for the main layout logic to be fully aware of the per-view
+  // margins when |collapse_margin_spacing_| is false. Since each view is a
+  // rectangle of a certain size, this wrapper, coupled with any margins set
+  // will increase the apparent size of the view along the main axis. All
+  // necessary view size/position methods required for the layout logic add or
+  // subtract the margins where appropriate to ensure the actual visible size of
+  // the view doesn't include the margins. For the cross axis, the margins are
+  // NOT included in the size/position calculations. BoxLayout will adjust the
+  // bounding rectangle of the space used for layout using the maximum margin
+  // for all views along the appropriate edge.
+  // When |collapse_margin_spacing_| is true, this wrapper provides quick access
+  // to the view's margins for use by the layout to collapse adjacent spacing
+  // to the largest of the several values.
+  class ViewWrapper {
+   public:
+    ViewWrapper();
+    ViewWrapper(const BoxLayout* layout, View* view);
+
+    ViewWrapper(const ViewWrapper&) = delete;
+    ViewWrapper& operator=(const ViewWrapper&) = delete;
+
+    ~ViewWrapper();
+
+    int GetHeightForWidth(int width) const;
+    const gfx::Insets& margins() const { return margins_; }
+    gfx::Size GetPreferredSize() const;
+    void SetBoundsRect(const gfx::Rect& bounds);
+    View* view() const { return view_; }
+    bool VisibleToLayout() const;
+
+   private:
+    // RAW_PTR_EXCLUSION: Performance reasons: based on this sampling profiler
+    // result on ChromeOS. go/brp-cros-prof-diff-20230403
+    RAW_PTR_EXCLUSION View* view_ = nullptr;
+    RAW_PTR_EXCLUSION const BoxLayout* layout_ = nullptr;
+    gfx::Insets margins_;
+  };
 
   // Returns the flex for the specified |view|.
   int GetFlexForView(const View* view) const;
@@ -178,44 +241,100 @@
   // Returns the minimum size for the specified |view|.
   int GetMinimumSizeForView(const View* view) const;
 
-  // Get the margin of the subview
-  NormalizedInsets GetChildMargins(BoxLayoutData& data, size_t index) const;
+  // Returns the size and position along the main axis of |rect|.
+  int MainAxisSize(const gfx::Rect& rect) const;
+  int MainAxisPosition(const gfx::Rect& rect) const;
 
-  // Returns the preferred size of the current view under `size_bounds`.
-  gfx::Size GetPreferredSizeForView(
-      const View* view,
-      const NormalizedSizeBounds& size_bounds) const;
+  // Sets the size and position along the main axis of |rect|.
+  void SetMainAxisSize(int size, gfx::Rect* rect) const;
+  void SetMainAxisPosition(int position, gfx::Rect* rect) const;
 
-  // Ensure that the vertical axis size of the view is no less than
-  // minimum_cross_axis_size_.
-  void EnsureCrossSize(BoxLayoutData& data) const;
+  // Returns the size and position along the cross axis of |rect|.
+  int CrossAxisSize(const gfx::Rect& rect) const;
+  int CrossAxisPosition(const gfx::Rect& rect) const;
 
-  // Data required to initialize the layout, including filtering views that do
-  // not participate in the layout and calculating the maximum leading and
-  // trailing margin.
-  void InitializeChildData(BoxLayoutData& data) const;
+  // Sets the size and position along the cross axis of |rect|.
+  void SetCrossAxisSize(int size, gfx::Rect* rect) const;
+  void SetCrossAxisPosition(int size, gfx::Rect* rect) const;
 
-  // Calculate the maximum child width, only used in vertical layout when no
-  // main view bounds are provided.
-  SizeBound CalculateMaxChildWidth(BoxLayoutData& data) const;
+  // Returns the main axis size for the given view. |child_area_width| is needed
+  // to calculate the height of the view when the orientation is vertical.
+  int MainAxisSizeForView(const ViewWrapper& view, int child_area_width) const;
 
-  // Calculate the preferred size of the largest subview.
-  void CalculatePreferredSize(const SizeBounds& size_bounds,
-                              BoxLayoutData& data) const;
+  // Returns the |left| or |top| edge of the given inset based on the value of
+  // |orientation_|.
+  int MainAxisLeadingInset(const gfx::Insets& insets) const;
 
-  // Calculate the total size of host_view. If no main view bounds are provided,
-  // this will be the total size of the content
-  void CalculatePreferredTotalSize(BoxLayoutData& data) const;
+  // Returns the |right| or |bottom| edge of the given inset based on the value
+  // of |orientation_|.
+  int MainAxisTrailingInset(const gfx::Insets& insets) const;
 
-  // Update and calculate the actual positions of all subviews based on flex
-  // rules.
-  void UpdateFlexLayout(const NormalizedSizeBounds& bounds,
-                        BoxLayoutData& data) const;
+  // Returns the left (|x|) or top (|y|) edge of the given rect based on the
+  // value of |orientation_|.
+  int CrossAxisLeadingEdge(const gfx::Rect& rect) const;
 
-  // Apply alignment rules to the subview, this will crop the subview when it
-  // exceeds the bounds.
-  void CalculateChildBounds(const SizeBounds& size_bounds,
-                            BoxLayoutData& data) const;
+  // Returns the |left| or |top| edge of the given inset based on the value of
+  // |orientation_|.
+  int CrossAxisLeadingInset(const gfx::Insets& insets) const;
+
+  // Returns the |right| or |bottom| edge of the given inset based on the value
+  // of |orientation_|.
+  int CrossAxisTrailingInset(const gfx::Insets& insets) const;
+
+  // Returns the main axis margin spacing between the two views which is the max
+  // of the right margin from the |left| view, the left margin of the |right|
+  // view and |between_child_spacing_|.
+  int MainAxisMarginBetweenViews(const ViewWrapper& left,
+                                 const ViewWrapper& right) const;
+
+  // Returns the outer margin along the main axis as insets.
+  gfx::Insets MainAxisOuterMargin() const;
+
+  // Returns the maximum margin along the cross axis from all views as insets.
+  gfx::Insets CrossAxisMaxViewMargin() const;
+
+  // Adjusts the main axis for |rect| by collapsing the left or top margin of
+  // the first view with corresponding side of |inside_border_insets_| and the
+  // right or bottom margin of the last view with the corresponding side of
+  // |inside_border_insets_|.
+  void AdjustMainAxisForMargin(gfx::Rect* rect) const;
+
+  // Adjust the cross axis for |rect| using the appropriate sides of
+  // |inside_border_insets_|.
+  void AdjustCrossAxisForInsets(gfx::Rect* rect) const;
+
+  // Returns the cross axis size for the given view.
+  int CrossAxisSizeForView(const ViewWrapper& view) const;
+
+  // Returns the total margin width for the given view or 0 when
+  // collapse_margins_spacing_ is true.
+  int CrossAxisMarginSizeForView(const ViewWrapper& view) const;
+
+  // Returns the Top or Left size of the margin for the given view or 0 when
+  // collapse_margins_spacing_ is true.
+  int CrossAxisLeadingMarginForView(const ViewWrapper& view) const;
+
+  // Adjust the cross axis for |rect| using the given leading and trailing
+  // values.
+  void InsetCrossAxis(gfx::Rect* rect, int leading, int trailing) const;
+
+  // The preferred size for the dialog given the width of the child area.
+  gfx::Size GetPreferredSizeForChildWidth(const View* host,
+                                          int child_area_width) const;
+
+  // The amount of space the layout requires in addition to any space for the
+  // child views.
+  gfx::Size NonChildSize(const View* host) const;
+
+  // The next visible view at or after pos. If no other views are visible,
+  // returns null.
+  View* NextVisibleView(View::Views::const_iterator pos) const;
+
+  // Return the first visible view in the host or nullptr if none are visible.
+  View* FirstVisibleView() const;
+
+  // Return the last visible view in the host or nullptr if none are visible.
+  View* LastVisibleView() const;
 
   Orientation orientation_;
 
@@ -241,6 +360,9 @@
 
   // Adjacent view margins and spacing should be collapsed.
   bool collapse_margins_spacing_;
+
+  // The view that this BoxLayout is managing the layout for.
+  raw_ptr<views::View> host_ = nullptr;
 };
 
 }  // namespace views
diff --git a/ui/views/layout/box_layout_unittest.cc b/ui/views/layout/box_layout_unittest.cc
index 1efdddbe..6c97d70 100644
--- a/ui/views/layout/box_layout_unittest.cc
+++ b/ui/views/layout/box_layout_unittest.cc
@@ -125,16 +125,14 @@
   // doesn't fit.
   test::RunScheduledLayout(host_.get());
   EXPECT_EQ(gfx::Rect(0, 0, 15, 10), v1->bounds());
-  // BoxLayout will arrange the views in order, but because it exceeds the main
-  // view space. So the width is clipped to 0.
-  EXPECT_EQ(gfx::Rect(20, 0, 0, 10), v2->bounds());
+  EXPECT_EQ(gfx::Rect(0, 0, 0, 0), v2->bounds());
 
   // Clipping of children should occur at the opposite end(s) to the main axis
   // alignment position.
   layout->set_main_axis_alignment(BoxLayout::MainAxisAlignment::kStart);
   HandleHostLayoutManagerChanges();
   EXPECT_EQ(gfx::Rect(0, 0, 15, 10), v1->bounds());
-  EXPECT_EQ(gfx::Rect(20, 0, 0, 10), v2->bounds());
+  EXPECT_EQ(gfx::Rect(0, 0, 0, 0), v2->bounds());
 
   layout->set_main_axis_alignment(BoxLayout::MainAxisAlignment::kCenter);
   HandleHostLayoutManagerChanges();
@@ -154,7 +152,7 @@
   host_->AddChildView(childView);
   host_->SetBounds(0, 0, 10, 10);
   test::RunScheduledLayout(host_.get());
-  EXPECT_EQ(gfx::Rect(10, 10, 0, 0), childView->bounds());
+  EXPECT_EQ(gfx::Rect(0, 0, 0, 0), childView->bounds());
 }
 
 TEST_F(BoxLayoutTest, InvisibleChild) {
@@ -234,11 +232,7 @@
 
     EXPECT_EQ(v2->GetPreferredSize().width(), host_->bounds().width()) << i;
     EXPECT_EQ(v2->GetPreferredSize().height(), host_->bounds().height()) << i;
-    // During vertical layout, due to stretching caused by vertical axis
-    // alignment, the width of v1 is 10 instead of 0.
-    if (orientation == BoxLayout::Orientation::kHorizontal) {
-      EXPECT_EQ(v1->GetPreferredSize().width(), v1->bounds().width()) << i;
-    }
+    EXPECT_EQ(v1->GetPreferredSize().width(), v1->bounds().width()) << i;
     EXPECT_EQ(v1->GetPreferredSize().height(), v1->bounds().height()) << i;
     EXPECT_EQ(v2->GetPreferredSize().width(), v2->bounds().width()) << i;
     EXPECT_EQ(v2->GetPreferredSize().height(), v2->bounds().height()) << i;
@@ -659,7 +653,8 @@
     layout->ClearFlexForView(v3);
     HandleHostLayoutManagerChanges();
     EXPECT_EQ(gfx::Rect(10, 10, 20, 30).ToString(), v1->bounds().ToString());
-    EXPECT_EQ(gfx::Rect(40, 10, 0, 30).ToString(), v2->bounds().ToString());
+    // Conceptually this view is at 10, 40, 0, 0.
+    EXPECT_EQ(gfx::Rect(0, 0, 0, 0).ToString(), v2->bounds().ToString());
     EXPECT_EQ(gfx::Rect(50, 10, 25, 30).ToString(), v3->bounds().ToString());
   }
 }
@@ -853,10 +848,7 @@
   EXPECT_EQ(gfx::Size(59, 20), layout->GetPreferredSize(host_.get()));
   host_->SizeToPreferredSize();
   layout->Layout(host_.get());
-  // The margin of v1 in the vertical direction is [5, 4], so the view center of
-  // v1 is at 10, the margin of v2 is [6, 3], and v2 is at 11. In order to
-  // ensure alignment. The center line of the entire view is 11.
-  EXPECT_EQ(gfx::Rect(5, 6, 20, 10), v1->bounds());
+  EXPECT_EQ(gfx::Rect(5, 5, 20, 10), v1->bounds());
   EXPECT_EQ(gfx::Rect(33, 6, 20, 10), v2->bounds());
 }
 
@@ -876,7 +868,7 @@
   EXPECT_EQ(gfx::Size(55, 20), layout->GetPreferredSize(host_.get()));
   host_->SizeToPreferredSize();
   layout->Layout(host_.get());
-  EXPECT_EQ(gfx::Rect(5, 6, 20, 10), v1->bounds());
+  EXPECT_EQ(gfx::Rect(5, 5, 20, 10), v1->bounds());
   EXPECT_EQ(gfx::Rect(29, 6, 20, 10), v2->bounds());
 }
 
diff --git a/ui/views/layout/box_layout_view.cc b/ui/views/layout/box_layout_view.cc
index 4c8af25..ccb3ba5 100644
--- a/ui/views/layout/box_layout_view.cc
+++ b/ui/views/layout/box_layout_view.cc
@@ -145,3 +145,21 @@
 END_METADATA
 
 }  // namespace views
+
+DEFINE_ENUM_CONVERTERS(views::BoxLayout::Orientation,
+                       {views::BoxLayout::Orientation::kHorizontal,
+                        u"kHorizontal"},
+                       {views::BoxLayout::Orientation::kVertical, u"kVertical"})
+
+DEFINE_ENUM_CONVERTERS(views::BoxLayout::MainAxisAlignment,
+                       {views::BoxLayout::MainAxisAlignment::kStart, u"kStart"},
+                       {views::BoxLayout::MainAxisAlignment::kCenter,
+                        u"kCenter"},
+                       {views::BoxLayout::MainAxisAlignment::kEnd, u"kEnd"})
+
+DEFINE_ENUM_CONVERTERS(
+    views::BoxLayout::CrossAxisAlignment,
+    {views::BoxLayout::CrossAxisAlignment::kStretch, u"kStretch"},
+    {views::BoxLayout::CrossAxisAlignment::kStart, u"kStart"},
+    {views::BoxLayout::CrossAxisAlignment::kCenter, u"kCenter"},
+    {views::BoxLayout::CrossAxisAlignment::kEnd, u"kEnd"})
diff --git a/ui/views/layout/fill_layout.cc b/ui/views/layout/fill_layout.cc
index a5b8f1f..6e39023 100644
--- a/ui/views/layout/fill_layout.cc
+++ b/ui/views/layout/fill_layout.cc
@@ -6,6 +6,8 @@
 
 #include <algorithm>
 
+#include "ui/views/view_class_properties.h"
+
 namespace views {
 
 FillLayout::FillLayout() = default;
@@ -32,7 +34,7 @@
 
   const gfx::Rect contents_bounds = host_view()->GetContentsBounds();
   for (View* child : host_view()->children()) {
-    if (!IsChildViewIgnoredByLayout(child)) {
+    if (!child->GetProperty(kViewIgnoredByLayoutKey)) {
       layout.child_layouts.push_back(
           ChildLayout{child, child->GetVisible(), contents_bounds,
                       SizeBounds(contents_bounds.size())});
@@ -49,7 +51,7 @@
 
   bool has_child = false;
   for (const View* child : host->children()) {
-    if (!IsChildViewIgnoredByLayout(child)) {
+    if (!child->GetProperty(kViewIgnoredByLayoutKey)) {
       has_child = true;
       result.SetToMax(child->GetPreferredSize(GetContentsSizeBounds(host)));
     }
@@ -76,7 +78,7 @@
 
   bool has_child = false;
   for (const View* child : host->children()) {
-    if (!IsChildViewIgnoredByLayout(child)) {
+    if (!child->GetProperty(kViewIgnoredByLayoutKey)) {
       has_child = true;
       result.SetToMax(child->GetMinimumSize());
     }
@@ -99,7 +101,7 @@
   width -= insets.width();
   int height = 0;
   for (const View* child : host->children()) {
-    if (!IsChildViewIgnoredByLayout(child)) {
+    if (!child->GetProperty(kViewIgnoredByLayoutKey)) {
       height =
           std::max(height, insets.height() + child->GetHeightForWidth(width));
     }
diff --git a/ui/views/layout/fill_layout_unittest.cc b/ui/views/layout/fill_layout_unittest.cc
index 3dcf582..26ded69 100644
--- a/ui/views/layout/fill_layout_unittest.cc
+++ b/ui/views/layout/fill_layout_unittest.cc
@@ -12,6 +12,7 @@
 #include "ui/views/test/test_views.h"
 #include "ui/views/test/views_test_utils.h"
 #include "ui/views/view.h"
+#include "ui/views/view_class_properties.h"
 
 namespace views {
 
@@ -200,7 +201,7 @@
   View* const child_2 = AddChildView(5, 5);
   View* const child_3 = AddChildView(25, 10);
 
-  layout()->SetChildViewIgnoredByLayout(child_3, true);
+  child_3->SetProperty(kViewIgnoredByLayoutKey, true);
   EXPECT_EQ(gfx::Size(10, 50), GetPreferredSize());
   test::RunScheduledLayout(host_.get());
 
@@ -254,7 +255,7 @@
   child_1->set_minimum_size({1, 3});
   child_2->set_minimum_size({3, 1});
   child_3->set_minimum_size({2, 2});
-  layout()->SetChildViewIgnoredByLayout(child_2, true);
+  child_2->SetProperty(kViewIgnoredByLayoutKey, true);
   EXPECT_EQ(gfx::Size(2, 3), host_->GetMinimumSize());
 }
 
diff --git a/ui/views/layout/flex_layout_unittest.cc b/ui/views/layout/flex_layout_unittest.cc
index b23ec4c7..22dc496 100644
--- a/ui/views/layout/flex_layout_unittest.cc
+++ b/ui/views/layout/flex_layout_unittest.cc
@@ -496,7 +496,7 @@
   View* child2 = AddChild(kChild2Size);
   const View* child3 = AddChild(kChild3Size);
 
-  layout_->SetChildViewIgnoredByLayout(child2, true);
+  child2->SetProperty(kViewIgnoredByLayoutKey, true);
   child2->SetBounds(3, 3, 3, 3);
   test::RunScheduledLayout(host_.get());
   EXPECT_EQ(Rect(3, 3, 3, 3), child2->bounds());
@@ -504,7 +504,7 @@
   EXPECT_EQ(Rect(18, 5, 17, 13), child3->bounds());
   EXPECT_EQ(Size(44, 25), host_->GetPreferredSize());
 
-  layout_->SetChildViewIgnoredByLayout(child2, false);
+  child2->SetProperty(kViewIgnoredByLayoutKey, false);
   test::RunScheduledLayout(host_.get());
   std::vector<Rect> expected = {Rect(6, 5, 12, 10), Rect(18, 5, 13, 11),
                                 Rect(31, 5, 17, 13)};
diff --git a/ui/views/layout/layout_manager_base.cc b/ui/views/layout/layout_manager_base.cc
index e7004561..f74d2b5 100644
--- a/ui/views/layout/layout_manager_base.cc
+++ b/ui/views/layout/layout_manager_base.cc
@@ -141,18 +141,6 @@
   return cached_layout_;
 }
 
-void LayoutManagerBase::SetChildViewIgnoredByLayout(View* child_view,
-                                                    bool ignored) {
-  // TODO(crbug.com/1267319): Call this directly in callers and remove.
-  child_view->SetProperty(kViewIgnoredByLayoutKey, ignored);
-}
-
-bool LayoutManagerBase::IsChildViewIgnoredByLayout(
-    const View* child_view) const {
-  // TODO(crbug.com/1267319): Call this directly in callers and remove.
-  return child_view->GetProperty(kViewIgnoredByLayoutKey);
-}
-
 LayoutManagerBase::LayoutManagerBase() = default;
 
 SizeBounds LayoutManagerBase::GetAvailableHostSize() const {
@@ -174,7 +162,7 @@
     return false;
   }
 
-  return !IsChildViewIgnoredByLayout(child) &&
+  return !child->GetProperty(kViewIgnoredByLayoutKey) &&
          (include_hidden || it->second.can_be_visible);
 }
 
@@ -332,7 +320,7 @@
   auto it = child_infos_.find(view);
   DCHECK(it != child_infos_.end());
   const bool removed_visible =
-      it->second.can_be_visible && !IsChildViewIgnoredByLayout(view);
+      it->second.can_be_visible && !view->GetProperty(kViewIgnoredByLayoutKey);
 
   view_observations_.RemoveObservation(view);
 
@@ -349,7 +337,7 @@
   DCHECK_EQ(host_view_, host);
   auto it = child_infos_.find(view);
   DCHECK(it != child_infos_.end());
-  const bool was_ignored = IsChildViewIgnoredByLayout(view);
+  const bool was_ignored = view->GetProperty(kViewIgnoredByLayoutKey);
   if (it->second.can_be_visible == new_visibility)
     return;
 
@@ -384,7 +372,7 @@
     for (View* child_view : host_view_->children()) {
       const ChildInfo& child_info = child_infos_.find(child_view)->second;
       owned_layout->PropagateChildViewIgnoredByLayout(
-          child_view, IsChildViewIgnoredByLayout(child_view));
+          child_view, child_view->GetProperty(kViewIgnoredByLayoutKey));
       owned_layout->PropagateViewVisibilitySet(host_view_, child_view,
                                                child_info.can_be_visible);
     }
diff --git a/ui/views/layout/layout_manager_base.h b/ui/views/layout/layout_manager_base.h
index cf8f5637..20581bde1 100644
--- a/ui/views/layout/layout_manager_base.h
+++ b/ui/views/layout/layout_manager_base.h
@@ -46,13 +46,6 @@
   // result had already been calculated, a cached value may be returned.
   ProposedLayout GetProposedLayout(const gfx::Size& host_size) const;
 
-  // Excludes a specific view from the layout when doing layout calculations.
-  // Useful when a child view is meant to be displayed but has its size and
-  // position managed elsewhere in code. By default, all child views are
-  // included in the layout unless they are hidden.
-  void SetChildViewIgnoredByLayout(View* child_view, bool ignored);
-  bool IsChildViewIgnoredByLayout(const View* child_view) const;
-
   // LayoutManager:
   gfx::Size GetPreferredSize(const View* host) const override;
   gfx::Size GetPreferredSize(const View* host,
diff --git a/ui/views/layout/layout_manager_base_unittest.cc b/ui/views/layout/layout_manager_base_unittest.cc
index b9dbdf1..f570ecf1 100644
--- a/ui/views/layout/layout_manager_base_unittest.cc
+++ b/ui/views/layout/layout_manager_base_unittest.cc
@@ -14,6 +14,7 @@
 #include "ui/views/test/test_views.h"
 #include "ui/views/test/views_test_utils.h"
 #include "ui/views/view.h"
+#include "ui/views/view_class_properties.h"
 
 namespace views {
 
@@ -181,27 +182,27 @@
   ExpectSameViews({child1, child2, child3}, layout->GetIncludedChildViews());
 
   // Remove one.
-  layout->SetChildViewIgnoredByLayout(child2, true);
+  child2->SetProperty(kViewIgnoredByLayoutKey, true);
   ExpectSameViews({child1, child3}, layout->GetIncludedChildViews());
 
   // Remove another.
-  layout->SetChildViewIgnoredByLayout(child1, true);
+  child1->SetProperty(kViewIgnoredByLayoutKey, true);
   ExpectSameViews({child3}, layout->GetIncludedChildViews());
 
   // Removing it again should have no effect.
-  layout->SetChildViewIgnoredByLayout(child1, true);
+  child1->SetProperty(kViewIgnoredByLayoutKey, true);
   ExpectSameViews({child3}, layout->GetIncludedChildViews());
 
   // Add one back.
-  layout->SetChildViewIgnoredByLayout(child1, false);
+  child1->SetProperty(kViewIgnoredByLayoutKey, false);
   ExpectSameViews({child1, child3}, layout->GetIncludedChildViews());
 
   // Adding it back again should have no effect.
-  layout->SetChildViewIgnoredByLayout(child1, false);
+  child1->SetProperty(kViewIgnoredByLayoutKey, false);
   ExpectSameViews({child1, child3}, layout->GetIncludedChildViews());
 
   // Add the other view back.
-  layout->SetChildViewIgnoredByLayout(child2, false);
+  child2->SetProperty(kViewIgnoredByLayoutKey, false);
   ExpectSameViews({child1, child2, child3}, layout->GetIncludedChildViews());
 }
 
@@ -506,30 +507,12 @@
   EXPECT_FALSE(child(2)->GetVisible());
 }
 
-TEST_F(LayoutManagerBaseManagerTest, ChildViewIgnoredByLayout) {
+TEST_F(LayoutManagerBaseManagerTest, IgnoresChildWithViewIgnoredByLayoutKey) {
   AddChildView(kSquarishSize);
   AddChildView(kLongSize);
   AddChildView(kTallSize);
 
-  EXPECT_FALSE(layout_manager()->IsChildViewIgnoredByLayout(child(0)));
-  EXPECT_FALSE(layout_manager()->IsChildViewIgnoredByLayout(child(1)));
-  EXPECT_FALSE(layout_manager()->IsChildViewIgnoredByLayout(child(2)));
-
-  layout_manager()->SetChildViewIgnoredByLayout(child(1), true);
-
-  EXPECT_FALSE(layout_manager()->IsChildViewIgnoredByLayout(child(0)));
-  EXPECT_TRUE(layout_manager()->IsChildViewIgnoredByLayout(child(1)));
-  EXPECT_FALSE(layout_manager()->IsChildViewIgnoredByLayout(child(2)));
-}
-
-TEST_F(LayoutManagerBaseManagerTest,
-       ChildViewIgnoredByLayout_IgnoresChildView) {
-  AddChildView(kSquarishSize);
-  AddChildView(kLongSize);
-  AddChildView(kTallSize);
-
-  layout_manager()->SetChildViewIgnoredByLayout(child(1), true);
-
+  child(1)->SetProperty(kViewIgnoredByLayoutKey, true);
   child(1)->SetSize(kLargeSize);
 
   // Makes enough room for all views, and triggers layout.
diff --git a/ui/views/layout/proposed_layout.cc b/ui/views/layout/proposed_layout.cc
index c9c4a84..a2feb0bf 100644
--- a/ui/views/layout/proposed_layout.cc
+++ b/ui/views/layout/proposed_layout.cc
@@ -9,9 +9,7 @@
 #include <string>
 
 #include "base/ranges/algorithm.h"
-#include "base/strings/strcat.h"
 #include "ui/gfx/animation/tween.h"
-#include "ui/views/view.h"
 
 namespace views {
 
@@ -56,10 +54,10 @@
 }
 
 std::string ChildLayout::ToString() const {
-  return base::StrCat({"{", child_view->GetClassName(),
-                       (visible ? " visible " : " not visible "),
-                       bounds.ToString(), " / ", available_size.ToString(),
-                       "}"});
+  std::ostringstream oss;
+  oss << "{" << child_view << (visible ? " visible " : " not visible ")
+      << bounds.ToString() << " / " << available_size.ToString() << "}";
+  return oss.str();
 }
 
 ProposedLayout::ProposedLayout() = default;
diff --git a/ui/views/layout/table_layout.cc b/ui/views/layout/table_layout.cc
index c2e492f..12fff52 100644
--- a/ui/views/layout/table_layout.cc
+++ b/ui/views/layout/table_layout.cc
@@ -382,8 +382,9 @@
   layout.host_size.SetToMax(minimum_size_);
 
   for (View* child : GetChildViewsInPaintOrder(host_view())) {
-    if (!IsChildViewIgnoredByLayout(child))
+    if (!child->GetProperty(kViewIgnoredByLayoutKey)) {
       layout.child_layouts.push_back({child, true, {}, {}});
+    }
   }
 
   // Size each view.
diff --git a/ui/web_dialogs/web_dialog_delegate.h b/ui/web_dialogs/web_dialog_delegate.h
index 79d011e..c0c3e32 100644
--- a/ui/web_dialogs/web_dialog_delegate.h
+++ b/ui/web_dialogs/web_dialog_delegate.h
@@ -131,6 +131,10 @@
   // certain that the window is about to be closed.
   virtual void OnDialogWillClose() {}
 
+  // A callback to notify the delegate that the dialog is about to close due to
+  // the user pressing the ESC key.
+  virtual void OnDialogClosingFromKeyEvent() {}
+
   // A callback to notify the delegate that the dialog closed.
   // IMPORTANT: Implementations should delete |this| here (unless they've
   // arranged for the delegate to be deleted in some other way, e.g. by
diff --git a/v8 b/v8
index 4bd7c5b7..deda839 160000
--- a/v8
+++ b/v8
@@ -1 +1 @@
-Subproject commit 4bd7c5b7d0c7fbfcf9cf9e54a92518150a7e544c
+Subproject commit deda839adfed890bdcfbbd0a73fd6b1daa94eb23