diff --git a/AUTHORS b/AUTHORS
index e21a78a..7b29b16b 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -343,6 +343,7 @@
 Fabien Tassin <fta@sofaraway.org>
 Felipe Erias Morandeira <felipeerias@gmail.com>
 Felix H. Dahlke <fhd@ubercode.de>
+Felix Weilbach <feweilbach@gmail.com>
 Fengrong Fang <fr.fang@samsung.com>
 Fernando Jiménez Moreno <ferjmoreno@gmail.com>
 Finbar Crago <finbar.crago@gmail.com>
diff --git a/DEPS b/DEPS
index ac19aba4..789f0d8 100644
--- a/DEPS
+++ b/DEPS
@@ -219,7 +219,7 @@
   'dawn_standalone': False,
 
   # reclient CIPD package version
-  'reclient_version': 're_client_version:0.44.0.17d680c-gomaip',
+  'reclient_version': 're_client_version:0.46.0.cd68397-gomaip',
 
   'android_git': 'https://android.googlesource.com',
   'aomedia_git': 'https://aomedia.googlesource.com',
@@ -234,11 +234,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': '3868636fb8aa2c312b0139d2d9e775ad334669e7',
+  'skia_revision': 'f1660bf1baecb787f5d541c1973f2c7f2ded13dd',
   # 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': 'e5295095036eefaadf795df8fad10d9250c4ba7a',
+  'v8_revision': '1a4ea6de5ad3d3303d5fad4d3b390d3a618795d6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
@@ -301,7 +301,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': 'ccdf6bde5f204ff8d1c4acb5dfddc501669a1d47',
+  'catapult_revision': 'f61cb71c3cd559d3ccce35544ea626f32b9de3c6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -309,7 +309,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': '8f3c4fdc1bf993b4124e18d94d087fcb14efc598',
+  'devtools_frontend_revision': '93c0859f32546392eb58e3463d349499dbcec8b8',
   # 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.
@@ -1416,7 +1416,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '406e7c3674a735f3fc4b751a90d21fd3d74f9511',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'f32b6ac45880108dee6eaa9e585aa8c6217607cb',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1577,7 +1577,7 @@
   },
 
   'src/third_party/tflite/src':
-    Var('chromium_git') + '/external/github.com/tensorflow/tensorflow.git' + '@' + '1500fdacf05723138b55cc7a81149cdb0e780d46',
+    Var('chromium_git') + '/external/github.com/tensorflow/tensorflow.git' + '@' + '95c0ea4aaefbad28d5b7d976250bca06c006943a',
 
   'src/third_party/turbine': {
       'packages': [
@@ -1637,7 +1637,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '5e8ac4959f931fcffb7d0d97b82b22ba0db6258e',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '96ef2bd2932f7ba4aa1730045f855e9185ae23d6',
+    Var('webrtc_git') + '/src.git' + '@' + '9456501f198f77cdd7da7760d4d264616cf87de1',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1695,7 +1695,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@1d0b5a74e019fdff36e15492229f3199c1ee40a0',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@513581ec6efd484d480f32d702d12234168c2338',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/webview_repack_locales.gni b/android_webview/webview_repack_locales.gni
index 6e5bb78..a2634eb 100644
--- a/android_webview/webview_repack_locales.gni
+++ b/android_webview/webview_repack_locales.gni
@@ -22,11 +22,13 @@
       "${root_gen_dir}/android_webview/components_strings_",
       "${root_gen_dir}/third_party/blink/public/strings/blink_strings_",
       "${root_gen_dir}/ui/strings/app_locale_settings_",
+      "${root_gen_dir}/ui/strings/ax_strings_",
     ]
     deps += [
       "//android_webview:generate_components_strings",
       "//third_party/blink/public/strings",
       "//ui/strings:app_locale_settings",
+      "//ui/strings:ax_strings",
     ]
     source_patterns += webview_repack_locales_source_patterns
     deps += webview_repack_locales_deps
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 6bf8776c..b6526a3 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -905,6 +905,8 @@
     "style/element_style.h",
     "style/highlight_border.cc",
     "style/highlight_border.h",
+    "style/icon_button.cc",
+    "style/icon_button.h",
     "style/pill_button.cc",
     "style/pill_button.h",
     "style/scoped_light_mode_as_default.cc",
@@ -1518,8 +1520,6 @@
     "system/unified/page_indicator_view.h",
     "system/unified/quiet_mode_feature_pod_controller.cc",
     "system/unified/quiet_mode_feature_pod_controller.h",
-    "system/unified/top_shortcut_button.cc",
-    "system/unified/top_shortcut_button.h",
     "system/unified/top_shortcuts_view.cc",
     "system/unified/top_shortcuts_view.h",
     "system/unified/unified_notifier_settings_controller.cc",
diff --git a/ash/app_list/views/productivity_launcher_search_view.cc b/ash/app_list/views/productivity_launcher_search_view.cc
index dadfa5d..a2dedeac 100644
--- a/ash/app_list/views/productivity_launcher_search_view.cc
+++ b/ash/app_list/views/productivity_launcher_search_view.cc
@@ -248,7 +248,7 @@
 
 bool ProductivityLauncherSearchView::CanSelectSearchResults() {
   DCHECK(!result_container_views_.empty());
-  return result_container_views_.front()->num_results() > 0;
+  return last_search_result_count_ > 0;
 }
 
 BEGIN_METADATA(ProductivityLauncherSearchView, views::View)
diff --git a/ash/app_list/views/productivity_launcher_search_view.h b/ash/app_list/views/productivity_launcher_search_view.h
index 8254b69..1ea26f27 100644
--- a/ash/app_list/views/productivity_launcher_search_view.h
+++ b/ash/app_list/views/productivity_launcher_search_view.h
@@ -57,6 +57,10 @@
     return result_container_views_;
   }
 
+  ResultSelectionController* result_selection_controller_for_test() {
+    return result_selection_controller_.get();
+  }
+
  private:
   // Passed to |result_selection_controller_| as a callback that gets called
   // when the currently selected result changes.
diff --git a/ash/app_list/views/productivity_launcher_search_view_unittest.cc b/ash/app_list/views/productivity_launcher_search_view_unittest.cc
index 5e87f63e..18a15496 100644
--- a/ash/app_list/views/productivity_launcher_search_view_unittest.cc
+++ b/ash/app_list/views/productivity_launcher_search_view_unittest.cc
@@ -12,6 +12,7 @@
 #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/result_selection_controller.h"
 #include "ash/app_list/views/search_box_view.h"
 #include "ash/app_list/views/search_result_list_view.h"
 #include "ash/constants/ash_features.h"
@@ -94,6 +95,65 @@
   EXPECT_TRUE(result_containers[0]->GetVisible());
 }
 
+// Tests that result selection controller can change between  within and between
+// result containers.
+TEST_F(ProductivityLauncherSearchViewTest, ResultSelection) {
+  auto* test_helper = GetAppListTestHelper();
+  test_helper->ShowAppList();
+  EXPECT_FALSE(test_helper->GetProductivityLauncherSearchView()
+                   ->CanSelectSearchResults());
+
+  // Press a key to start a search.
+  PressAndReleaseKey(ui::VKEY_A);
+  SearchModel::SearchResults* results = test_helper->GetSearchResults();
+
+  // Create categorized results and order categories as {kApps, kWeb}.
+  std::vector<AppListSearchResultCategory>* ordered_categories =
+      test_helper->GetOrderedResultCategories();
+  AppListModelProvider::Get()->search_model()->DeleteAllResults();
+  ordered_categories->push_back(AppListSearchResultCategory::kApps);
+  ordered_categories->push_back(AppListSearchResultCategory::kWeb);
+  SetUpSearchResults(results, 1, kDefaultSearchItems, 100, false,
+                     SearchResult::Category::kApps);
+  SetUpSearchResults(results, 1 + kDefaultSearchItems, kDefaultSearchItems, 1,
+                     false, SearchResult::Category::kWeb);
+  test_helper->GetProductivityLauncherSearchView()
+      ->OnSearchResultContainerResultsChanged();
+
+  // Press VKEY_DOWN and check if the first result view is selected.
+  EXPECT_TRUE(test_helper->GetProductivityLauncherSearchView()
+                  ->CanSelectSearchResults());
+  ResultSelectionController* controller =
+      test_helper->GetProductivityLauncherSearchView()
+          ->result_selection_controller_for_test();
+  // Tests that VKEY_DOWN selects the next result in container 1.
+  PressAndReleaseKey(ui::VKEY_DOWN);
+
+  EXPECT_EQ(controller->selected_location_details()->container_index, 1);
+  EXPECT_EQ(controller->selected_location_details()->result_index, 1);
+  PressAndReleaseKey(ui::VKEY_DOWN);
+  EXPECT_EQ(controller->selected_location_details()->container_index, 1);
+  EXPECT_EQ(controller->selected_location_details()->result_index, 2);
+  // Tests that VKEY_DOWN while selecting the last result of the current
+  // container causes the selection controller to select the next container.
+  PressAndReleaseKey(ui::VKEY_DOWN);
+  EXPECT_EQ(controller->selected_location_details()->container_index, 2);
+  EXPECT_EQ(controller->selected_location_details()->result_index, 0);
+  PressAndReleaseKey(ui::VKEY_DOWN);
+  EXPECT_EQ(controller->selected_location_details()->container_index, 2);
+  EXPECT_EQ(controller->selected_location_details()->result_index, 1);
+  PressAndReleaseKey(ui::VKEY_DOWN);
+  EXPECT_EQ(controller->selected_location_details()->container_index, 2);
+  EXPECT_EQ(controller->selected_location_details()->result_index, 2);
+  // Tests that VKEY_UP while selecting the first result of the current
+  // container causes the selection controller to select the previous container.
+  PressAndReleaseKey(ui::VKEY_UP);
+  PressAndReleaseKey(ui::VKEY_UP);
+  PressAndReleaseKey(ui::VKEY_UP);
+  EXPECT_EQ(controller->selected_location_details()->container_index, 1);
+  EXPECT_EQ(controller->selected_location_details()->result_index, 2);
+}
+
 // Verifies that search result categories are sorted properly.
 TEST_F(ProductivityLauncherSearchViewTest, SearchResultCategoricalSort) {
   auto* test_helper = GetAppListTestHelper();
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index b8be117..3664b0fc 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -2233,27 +2233,12 @@
       <message name="IDS_ASH_STATUS_TRAY_DISPLAY_RESOLUTION_CHANGED_TO_UNSUPPORTED" desc="The label used in the tray to notify that the display resolution settings has changed to an unsupported resolution and the system falls back to another resolution.">
         <ph name="DISPLAY_NAME">$1<ex>Google Monitor X</ex></ph> doesn't support <ph name="SPECIFIED_RESOLUTION">$2<ex>2560x1600</ex></ph>. The resolution was changed to <ph name="FALLBACK_RESOLUTION">$3<ex>1920x1200</ex></ph>.
       </message>
-      <message name="IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED" desc="The label used in the tray to notify that the display rotation settings has changed.">
-        <ph name="DISPLAY_NAME">$1</ph> was rotated to <ph name="ROTATION">$2</ph>
-      </message>
       <message name="IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATED_NAME" desc="Label used to show a display name with annotation (like screen resolution or overscan info).">
         <ph name="DISPLAY_NAME">$1</ph> (<ph name="ANNOTATION">$2</ph>)
       </message>
       <message name="IDS_ASH_STATUS_TRAY_DISPLAY_ANNOTATION_OVERSCAN" desc="Label used to describe that the system notice that this display device may have overscan area.">
         overscan
       </message>
-      <message name="IDS_ASH_STATUS_TRAY_DISPLAY_STANDARD_ORIENTATION" desc="The default value of display orientation option item.">
-        0&#x00B0;
-      </message>
-      <message name="IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90" desc="The value of display orientation option item: 90-degree rotated">
-        90&#x00B0;
-      </message>
-      <message name="IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_180" desc="The value of display orientation option item: 180-degree rotated">
-        180&#x00B0;
-      </message>
-      <message name="IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_270" desc="The value of display orientation option item: 270-degree rotated">
-        270&#x00B0;
-      </message>
       <message name="IDS_ASH_STATUS_TRAY_DISPLAY_UNIFIED" desc="The title of the notification indicating that the system is in unified desktop mode.">
         Unified desktop mode
       </message>
diff --git a/ash/components/arc/BUILD.gn b/ash/components/arc/BUILD.gn
index ea812ea..bc84d60 100644
--- a/ash/components/arc/BUILD.gn
+++ b/ash/components/arc/BUILD.gn
@@ -43,6 +43,20 @@
     "dark_theme/arc_dark_theme_bridge.h",
     "disk_quota/arc_disk_quota_bridge.cc",
     "disk_quota/arc_disk_quota_bridge.h",
+    "keyboard_shortcut/arc_keyboard_shortcut_bridge.cc",
+    "keyboard_shortcut/arc_keyboard_shortcut_bridge.h",
+    "lock_screen/arc_lock_screen_bridge.cc",
+    "lock_screen/arc_lock_screen_bridge.h",
+    "memory/arc_memory_bridge.cc",
+    "memory/arc_memory_bridge.h",
+    "memory_pressure/arc_memory_pressure_bridge.cc",
+    "memory_pressure/arc_memory_pressure_bridge.h",
+    "metrics/arc_metrics_service.cc",
+    "metrics/arc_metrics_service.h",
+    "metrics/stability_metrics_manager.cc",
+    "metrics/stability_metrics_manager.h",
+    "midis/arc_midis_bridge.cc",
+    "midis/arc_midis_bridge.h",
   ]
 
   public_deps = [
@@ -396,7 +410,6 @@
     "//base",
     "//base/test:test_support",
     "//chromeos",
-    "//chromeos/policy",
     "//chromeos/cryptohome:cryptohome",
     "//chromeos/dbus:test_support",
     "//chromeos/dbus/dlcservice",
@@ -410,6 +423,7 @@
     "//chromeos/dbus/upstart",
     "//chromeos/disks:test_support",
     "//chromeos/network:test_support",
+    "//chromeos/policy",
     "//chromeos/ui/frame",
     "//components/account_id",
     "//components/arc/enterprise",
diff --git a/components/arc/keyboard_shortcut/arc_keyboard_shortcut_bridge.cc b/ash/components/arc/keyboard_shortcut/arc_keyboard_shortcut_bridge.cc
similarity index 96%
rename from components/arc/keyboard_shortcut/arc_keyboard_shortcut_bridge.cc
rename to ash/components/arc/keyboard_shortcut/arc_keyboard_shortcut_bridge.cc
index 66767db..9065765 100644
--- a/components/arc/keyboard_shortcut/arc_keyboard_shortcut_bridge.cc
+++ b/ash/components/arc/keyboard_shortcut/arc_keyboard_shortcut_bridge.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/arc/keyboard_shortcut/arc_keyboard_shortcut_bridge.h"
+#include "ash/components/arc/keyboard_shortcut/arc_keyboard_shortcut_bridge.h"
 
 #include "ash/components/arc/arc_browser_context_keyed_service_factory_base.h"
 #include "ash/components/arc/arc_features.h"
diff --git a/components/arc/keyboard_shortcut/arc_keyboard_shortcut_bridge.h b/ash/components/arc/keyboard_shortcut/arc_keyboard_shortcut_bridge.h
similarity index 83%
rename from components/arc/keyboard_shortcut/arc_keyboard_shortcut_bridge.h
rename to ash/components/arc/keyboard_shortcut/arc_keyboard_shortcut_bridge.h
index 3486a50..b6c4db9 100644
--- a/components/arc/keyboard_shortcut/arc_keyboard_shortcut_bridge.h
+++ b/ash/components/arc/keyboard_shortcut/arc_keyboard_shortcut_bridge.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_ARC_KEYBOARD_SHORTCUT_ARC_KEYBOARD_SHORTCUT_BRIDGE_H_
-#define COMPONENTS_ARC_KEYBOARD_SHORTCUT_ARC_KEYBOARD_SHORTCUT_BRIDGE_H_
+#ifndef ASH_COMPONENTS_ARC_KEYBOARD_SHORTCUT_ARC_KEYBOARD_SHORTCUT_BRIDGE_H_
+#define ASH_COMPONENTS_ARC_KEYBOARD_SHORTCUT_ARC_KEYBOARD_SHORTCUT_BRIDGE_H_
 
 #include "components/arc/mojom/keyboard_shortcut.mojom.h"
 #include "components/keyed_service/core/keyed_service.h"
@@ -43,4 +43,4 @@
 
 }  // namespace arc
 
-#endif  // COMPONENTS_ARC_KEYBOARD_SHORTCUT_ARC_KEYBOARD_SHORTCUT_BRIDGE_H_
+#endif  // ASH_COMPONENTS_ARC_KEYBOARD_SHORTCUT_ARC_KEYBOARD_SHORTCUT_BRIDGE_H_
diff --git a/components/arc/lock_screen/arc_lock_screen_bridge.cc b/ash/components/arc/lock_screen/arc_lock_screen_bridge.cc
similarity index 97%
rename from components/arc/lock_screen/arc_lock_screen_bridge.cc
rename to ash/components/arc/lock_screen/arc_lock_screen_bridge.cc
index c710d96..b2e7575b3 100644
--- a/components/arc/lock_screen/arc_lock_screen_bridge.cc
+++ b/ash/components/arc/lock_screen/arc_lock_screen_bridge.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/arc/lock_screen/arc_lock_screen_bridge.h"
+#include "ash/components/arc/lock_screen/arc_lock_screen_bridge.h"
 
 #include "ash/components/arc/arc_browser_context_keyed_service_factory_base.h"
 #include "base/bind.h"
diff --git a/components/arc/lock_screen/arc_lock_screen_bridge.h b/ash/components/arc/lock_screen/arc_lock_screen_bridge.h
similarity index 90%
rename from components/arc/lock_screen/arc_lock_screen_bridge.h
rename to ash/components/arc/lock_screen/arc_lock_screen_bridge.h
index f7a64e6..270a26d 100644
--- a/components/arc/lock_screen/arc_lock_screen_bridge.h
+++ b/ash/components/arc/lock_screen/arc_lock_screen_bridge.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_ARC_LOCK_SCREEN_ARC_LOCK_SCREEN_BRIDGE_H_
-#define COMPONENTS_ARC_LOCK_SCREEN_ARC_LOCK_SCREEN_BRIDGE_H_
+#ifndef ASH_COMPONENTS_ARC_LOCK_SCREEN_ARC_LOCK_SCREEN_BRIDGE_H_
+#define ASH_COMPONENTS_ARC_LOCK_SCREEN_ARC_LOCK_SCREEN_BRIDGE_H_
 
 #include "base/threading/thread_checker.h"
 #include "components/arc/mojom/lock_screen.mojom.h"
@@ -57,4 +57,4 @@
 
 }  // namespace arc
 
-#endif  // COMPONENTS_ARC_LOCK_SCREEN_ARC_LOCK_SCREEN_BRIDGE_H_
+#endif  // ASH_COMPONENTS_ARC_LOCK_SCREEN_ARC_LOCK_SCREEN_BRIDGE_H_
diff --git a/ash/components/arc/lock_screen/arc_lock_screen_bridge_unittest.cc b/ash/components/arc/lock_screen/arc_lock_screen_bridge_unittest.cc
index fa427ec2..a783dc6 100644
--- a/ash/components/arc/lock_screen/arc_lock_screen_bridge_unittest.cc
+++ b/ash/components/arc/lock_screen/arc_lock_screen_bridge_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/arc/lock_screen/arc_lock_screen_bridge.h"
+#include "ash/components/arc/lock_screen/arc_lock_screen_bridge.h"
 
 #include "ash/components/arc/test/connection_holder_util.h"
 #include "ash/components/arc/test/fake_lock_screen_instance.h"
diff --git a/components/arc/memory/arc_memory_bridge.cc b/ash/components/arc/memory/arc_memory_bridge.cc
similarity index 97%
rename from components/arc/memory/arc_memory_bridge.cc
rename to ash/components/arc/memory/arc_memory_bridge.cc
index 913206e3..79a553a 100644
--- a/components/arc/memory/arc_memory_bridge.cc
+++ b/ash/components/arc/memory/arc_memory_bridge.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/arc/memory/arc_memory_bridge.h"
+#include "ash/components/arc/memory/arc_memory_bridge.h"
 
 #include <utility>
 
diff --git a/components/arc/memory/arc_memory_bridge.h b/ash/components/arc/memory/arc_memory_bridge.h
similarity index 89%
rename from components/arc/memory/arc_memory_bridge.h
rename to ash/components/arc/memory/arc_memory_bridge.h
index 1506a87..df7ad8a 100644
--- a/components/arc/memory/arc_memory_bridge.h
+++ b/ash/components/arc/memory/arc_memory_bridge.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_ARC_MEMORY_ARC_MEMORY_BRIDGE_H_
-#define COMPONENTS_ARC_MEMORY_ARC_MEMORY_BRIDGE_H_
+#ifndef ASH_COMPONENTS_ARC_MEMORY_ARC_MEMORY_BRIDGE_H_
+#define ASH_COMPONENTS_ARC_MEMORY_ARC_MEMORY_BRIDGE_H_
 
 #include "base/callback_forward.h"
 #include "base/threading/thread_checker.h"
@@ -45,4 +45,4 @@
 
 }  // namespace arc
 
-#endif  // COMPONENTS_ARC_MEMORY_ARC_MEMORY_BRIDGE_H_
+#endif  // ASH_COMPONENTS_ARC_MEMORY_ARC_MEMORY_BRIDGE_H_
diff --git a/ash/components/arc/memory/arc_memory_bridge_unittest.cc b/ash/components/arc/memory/arc_memory_bridge_unittest.cc
index e2c512d..3398fa9 100644
--- a/ash/components/arc/memory/arc_memory_bridge_unittest.cc
+++ b/ash/components/arc/memory/arc_memory_bridge_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/arc/memory/arc_memory_bridge.h"
+#include "ash/components/arc/memory/arc_memory_bridge.h"
 
 #include "ash/components/arc/test/connection_holder_util.h"
 #include "ash/components/arc/test/fake_memory_instance.h"
diff --git a/components/arc/memory_pressure/arc_memory_pressure_bridge.cc b/ash/components/arc/memory_pressure/arc_memory_pressure_bridge.cc
similarity index 97%
rename from components/arc/memory_pressure/arc_memory_pressure_bridge.cc
rename to ash/components/arc/memory_pressure/arc_memory_pressure_bridge.cc
index a2d99101..916c696 100644
--- a/components/arc/memory_pressure/arc_memory_pressure_bridge.cc
+++ b/ash/components/arc/memory_pressure/arc_memory_pressure_bridge.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/arc/memory_pressure/arc_memory_pressure_bridge.h"
+#include "ash/components/arc/memory_pressure/arc_memory_pressure_bridge.h"
 
 #include "ash/components/arc/arc_browser_context_keyed_service_factory_base.h"
 #include "base/logging.h"
diff --git a/components/arc/memory_pressure/arc_memory_pressure_bridge.h b/ash/components/arc/memory_pressure/arc_memory_pressure_bridge.h
similarity index 88%
rename from components/arc/memory_pressure/arc_memory_pressure_bridge.h
rename to ash/components/arc/memory_pressure/arc_memory_pressure_bridge.h
index d4ff116..f66e11c 100644
--- a/components/arc/memory_pressure/arc_memory_pressure_bridge.h
+++ b/ash/components/arc/memory_pressure/arc_memory_pressure_bridge.h
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_ARC_MEMORY_PRESSURE_ARC_MEMORY_PRESSURE_BRIDGE_H_
-#define COMPONENTS_ARC_MEMORY_PRESSURE_ARC_MEMORY_PRESSURE_BRIDGE_H_
+#ifndef ASH_COMPONENTS_ARC_MEMORY_PRESSURE_ARC_MEMORY_PRESSURE_BRIDGE_H_
+#define ASH_COMPONENTS_ARC_MEMORY_PRESSURE_ARC_MEMORY_PRESSURE_BRIDGE_H_
 
+#include "ash/components/arc/metrics/arc_metrics_service.h"
 #include "base/memory/weak_ptr.h"
 #include "chromeos/dbus/resourced/resourced_client.h"
-#include "components/arc/metrics/arc_metrics_service.h"
 #include "components/keyed_service/core/keyed_service.h"
 
 namespace content {
@@ -61,4 +61,4 @@
 
 }  // namespace arc
 
-#endif  // COMPONENTS_ARC_MEMORY_PRESSURE_ARC_MEMORY_PRESSURE_BRIDGE_H_
+#endif  // ASH_COMPONENTS_ARC_MEMORY_PRESSURE_ARC_MEMORY_PRESSURE_BRIDGE_H_
diff --git a/ash/components/arc/memory_pressure/arc_memory_pressure_bridge_unittest.cc b/ash/components/arc/memory_pressure/arc_memory_pressure_bridge_unittest.cc
index b2e133c..2228910 100644
--- a/ash/components/arc/memory_pressure/arc_memory_pressure_bridge_unittest.cc
+++ b/ash/components/arc/memory_pressure/arc_memory_pressure_bridge_unittest.cc
@@ -2,24 +2,24 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/arc/memory_pressure/arc_memory_pressure_bridge.h"
+#include "ash/components/arc/memory_pressure/arc_memory_pressure_bridge.h"
 
 #include <stdint.h>
 #include <unistd.h>
 
 #include "ash/components/arc/arc_prefs.h"
+#include "ash/components/arc/metrics/stability_metrics_manager.h"
 #include "ash/components/arc/test/fake_process_instance.h"
 #include "ash/components/arc/test/test_browser_context.h"
 #include "chromeos/dbus/resourced/fake_resourced_client.h"
-#include "components/arc/metrics/stability_metrics_manager.h"
 #include "components/arc/session/arc_bridge_service.h"
 #include "components/arc/session/arc_service_manager.h"
 #include "content/public/test/browser_task_environment.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+#include "ash/components/arc/metrics/arc_metrics_service.h"
 #include "chromeos/dbus/session_manager/fake_session_manager_client.h"
-#include "components/arc/metrics/arc_metrics_service.h"
 #include "components/session_manager/core/session_manager.h"
 
 namespace arc {
diff --git a/components/arc/metrics/DEPS b/ash/components/arc/metrics/DEPS
similarity index 100%
rename from components/arc/metrics/DEPS
rename to ash/components/arc/metrics/DEPS
diff --git a/components/arc/metrics/OWNERS b/ash/components/arc/metrics/OWNERS
similarity index 100%
rename from components/arc/metrics/OWNERS
rename to ash/components/arc/metrics/OWNERS
diff --git a/components/arc/metrics/arc_metrics_service.cc b/ash/components/arc/metrics/arc_metrics_service.cc
similarity index 99%
rename from components/arc/metrics/arc_metrics_service.cc
rename to ash/components/arc/metrics/arc_metrics_service.cc
index 565e9bf..fdd3cf4 100644
--- a/components/arc/metrics/arc_metrics_service.cc
+++ b/ash/components/arc/metrics/arc_metrics_service.cc
@@ -2,13 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/arc/metrics/arc_metrics_service.h"
+#include "ash/components/arc/metrics/arc_metrics_service.h"
 
 #include <string>
 #include <utility>
 
 #include "ash/components/arc/arc_prefs.h"
 #include "ash/components/arc/arc_util.h"
+#include "ash/components/arc/metrics/stability_metrics_manager.h"
 #include "ash/public/cpp/app_types_util.h"
 #include "base/bind.h"
 #include "base/logging.h"
@@ -19,7 +20,6 @@
 #include "base/strings/string_util.h"
 #include "chromeos/dbus/power_manager/idle.pb.h"
 #include "chromeos/dbus/session_manager/session_manager_client.h"
-#include "components/arc/metrics/stability_metrics_manager.h"
 #include "components/arc/session/arc_bridge_service.h"
 #include "components/exo/wm_helper.h"
 #include "components/prefs/pref_service.h"
diff --git a/components/arc/metrics/arc_metrics_service.h b/ash/components/arc/metrics/arc_metrics_service.h
similarity index 98%
rename from components/arc/metrics/arc_metrics_service.h
rename to ash/components/arc/metrics/arc_metrics_service.h
index 2168fdcb..2515246 100644
--- a/components/arc/metrics/arc_metrics_service.h
+++ b/ash/components/arc/metrics/arc_metrics_service.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_ARC_METRICS_ARC_METRICS_SERVICE_H_
-#define COMPONENTS_ARC_METRICS_ARC_METRICS_SERVICE_H_
+#ifndef ASH_COMPONENTS_ARC_METRICS_ARC_METRICS_SERVICE_H_
+#define ASH_COMPONENTS_ARC_METRICS_ARC_METRICS_SERVICE_H_
 
 #include <memory>
 #include <string>
@@ -313,4 +313,4 @@
 
 }  // namespace arc
 
-#endif  // COMPONENTS_ARC_METRICS_ARC_METRICS_SERVICE_H_
+#endif  // ASH_COMPONENTS_ARC_METRICS_ARC_METRICS_SERVICE_H_
diff --git a/ash/components/arc/metrics/arc_metrics_service_unittest.cc b/ash/components/arc/metrics/arc_metrics_service_unittest.cc
index 3d8baae..bd0fba8 100644
--- a/ash/components/arc/metrics/arc_metrics_service_unittest.cc
+++ b/ash/components/arc/metrics/arc_metrics_service_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/arc/metrics/arc_metrics_service.h"
+#include "ash/components/arc/metrics/arc_metrics_service.h"
 
 #include <algorithm>
 #include <array>
@@ -12,6 +12,7 @@
 
 #include "ash/components/arc/arc_prefs.h"
 #include "ash/components/arc/metrics/arc_metrics_constants.h"
+#include "ash/components/arc/metrics/stability_metrics_manager.h"
 #include "ash/components/arc/test/test_browser_context.h"
 #include "ash/constants/app_types.h"
 #include "base/metrics/histogram_samples.h"
@@ -19,7 +20,6 @@
 #include "base/strings/stringprintf.h"
 #include "base/test/metrics/histogram_tester.h"
 #include "chromeos/dbus/session_manager/fake_session_manager_client.h"
-#include "components/arc/metrics/stability_metrics_manager.h"
 #include "components/arc/session/arc_service_manager.h"
 #include "components/prefs/testing_pref_service.h"
 #include "components/session_manager/core/session_manager.h"
diff --git a/components/arc/metrics/stability_metrics_manager.cc b/ash/components/arc/metrics/stability_metrics_manager.cc
similarity index 97%
rename from components/arc/metrics/stability_metrics_manager.cc
rename to ash/components/arc/metrics/stability_metrics_manager.cc
index 6fa078d..5e7d9167 100644
--- a/components/arc/metrics/stability_metrics_manager.cc
+++ b/ash/components/arc/metrics/stability_metrics_manager.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/arc/metrics/stability_metrics_manager.h"
+#include "ash/components/arc/metrics/stability_metrics_manager.h"
 
 #include "ash/components/arc/arc_prefs.h"
 #include "base/metrics/histogram_macros.h"
diff --git a/components/arc/metrics/stability_metrics_manager.h b/ash/components/arc/metrics/stability_metrics_manager.h
similarity index 91%
rename from components/arc/metrics/stability_metrics_manager.h
rename to ash/components/arc/metrics/stability_metrics_manager.h
index b53d2aa..a3e97d1c 100644
--- a/components/arc/metrics/stability_metrics_manager.h
+++ b/ash/components/arc/metrics/stability_metrics_manager.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_ARC_METRICS_STABILITY_METRICS_MANAGER_H_
-#define COMPONENTS_ARC_METRICS_STABILITY_METRICS_MANAGER_H_
+#ifndef ASH_COMPONENTS_ARC_METRICS_STABILITY_METRICS_MANAGER_H_
+#define ASH_COMPONENTS_ARC_METRICS_STABILITY_METRICS_MANAGER_H_
 
 #include "ash/components/arc/metrics/arc_metrics_constants.h"
 #include "base/sequence_checker.h"
@@ -61,4 +61,4 @@
 
 }  // namespace arc
 
-#endif  // COMPONENTS_ARC_METRICS_STABILITY_METRICS_MANAGER_H_
+#endif  // ASH_COMPONENTS_ARC_METRICS_STABILITY_METRICS_MANAGER_H_
diff --git a/ash/components/arc/metrics/stability_metrics_manager_unittest.cc b/ash/components/arc/metrics/stability_metrics_manager_unittest.cc
index 285148b..5541a2b9 100644
--- a/ash/components/arc/metrics/stability_metrics_manager_unittest.cc
+++ b/ash/components/arc/metrics/stability_metrics_manager_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/arc/metrics/stability_metrics_manager.h"
+#include "ash/components/arc/metrics/stability_metrics_manager.h"
 
 #include "ash/components/arc/arc_prefs.h"
 #include "base/test/metrics/histogram_tester.h"
diff --git a/components/arc/midis/arc_midis_bridge.cc b/ash/components/arc/midis/arc_midis_bridge.cc
similarity index 98%
rename from components/arc/midis/arc_midis_bridge.cc
rename to ash/components/arc/midis/arc_midis_bridge.cc
index 1edbdd9..e7eceb9 100644
--- a/components/arc/midis/arc_midis_bridge.cc
+++ b/ash/components/arc/midis/arc_midis_bridge.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/arc/midis/arc_midis_bridge.h"
+#include "ash/components/arc/midis/arc_midis_bridge.h"
 
 #include <utility>
 
diff --git a/components/arc/midis/arc_midis_bridge.h b/ash/components/arc/midis/arc_midis_bridge.h
similarity index 87%
rename from components/arc/midis/arc_midis_bridge.h
rename to ash/components/arc/midis/arc_midis_bridge.h
index 7e3bf997..b44b4c1 100644
--- a/components/arc/midis/arc_midis_bridge.h
+++ b/ash/components/arc/midis/arc_midis_bridge.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef COMPONENTS_ARC_MIDIS_ARC_MIDIS_BRIDGE_H_
-#define COMPONENTS_ARC_MIDIS_ARC_MIDIS_BRIDGE_H_
+#ifndef ASH_COMPONENTS_ARC_MIDIS_ARC_MIDIS_BRIDGE_H_
+#define ASH_COMPONENTS_ARC_MIDIS_ARC_MIDIS_BRIDGE_H_
 
 #include <stdint.h>
 
@@ -21,8 +21,7 @@
 
 class ArcBridgeService;
 
-class ArcMidisBridge : public KeyedService,
-                       public mojom::MidisHost {
+class ArcMidisBridge : public KeyedService, public mojom::MidisHost {
  public:
   // Returns singleton instance for the given BrowserContext,
   // or nullptr if the browser |context| is not allowed to use ARC.
@@ -58,4 +57,4 @@
 
 }  // namespace arc
 
-#endif  // COMPONENTS_ARC_MIDIS_ARC_MIDIS_BRIDGE_H_
+#endif  // ASH_COMPONENTS_ARC_MIDIS_ARC_MIDIS_BRIDGE_H_
diff --git a/ash/components/arc/midis/arc_midis_bridge_unittest.cc b/ash/components/arc/midis/arc_midis_bridge_unittest.cc
index f309d13..c7ac4292 100644
--- a/ash/components/arc/midis/arc_midis_bridge_unittest.cc
+++ b/ash/components/arc/midis/arc_midis_bridge_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/arc/midis/arc_midis_bridge.h"
+#include "ash/components/arc/midis/arc_midis_bridge.h"
 
 #include "ash/components/arc/test/test_browser_context.h"
 #include "components/arc/session/arc_service_manager.h"
diff --git a/ash/components/fwupd/firmware_update_manager.cc b/ash/components/fwupd/firmware_update_manager.cc
index 6d18b3e..4d0aefc 100644
--- a/ash/components/fwupd/firmware_update_manager.cc
+++ b/ash/components/fwupd/firmware_update_manager.cc
@@ -52,6 +52,7 @@
 }
 
 void FirmwareUpdateManager::OnUpdateListResponse(
+    const std::string& device_id,
     chromeos::FwupdUpdateList* updates) {
   DCHECK(updates);
   // TODO(swifton): This is a stub implementation.
diff --git a/ash/components/fwupd/firmware_update_manager.h b/ash/components/fwupd/firmware_update_manager.h
index 78d2a84b..082f0a3 100644
--- a/ash/components/fwupd/firmware_update_manager.h
+++ b/ash/components/fwupd/firmware_update_manager.h
@@ -5,6 +5,8 @@
 #ifndef ASH_COMPONENTS_FWUPD_FIRMWARE_UPDATE_MANAGER_H_
 #define ASH_COMPONENTS_FWUPD_FIRMWARE_UPDATE_MANAGER_H_
 
+#include <string>
+
 #include "base/component_export.h"
 #include "chromeos/dbus/fwupd/fwupd_client.h"
 #include "chromeos/dbus/fwupd/fwupd_device.h"
@@ -15,11 +17,13 @@
 class COMPONENT_EXPORT(ASH_FIRMWARE_UPDATE_MANAGER) FirmwareUpdateManager
     : public chromeos::FwupdClient::Observer {
  public:
-  // Query the fwupd DBus client for currently connected devices.
-  void RequestDevices();
+  FirmwareUpdateManager();
+  FirmwareUpdateManager(const FirmwareUpdateManager&) = delete;
+  FirmwareUpdateManager& operator=(const FirmwareUpdateManager&) = delete;
+  ~FirmwareUpdateManager() override;
 
-  // Query the fwupd DBus client for updates for a certain device.
-  void RequestUpdates(const std::string& device_id);
+  // Gets the global instance pointer.
+  static FirmwareUpdateManager* Get();
 
   // FwupdClient::Observer:
   // When the fwupd DBus client gets a response with devices from fwupd,
@@ -28,15 +32,14 @@
 
   // When the fwupd DBus client gets a response with updates from fwupd,
   // it calls this function and passes the response.
-  void OnUpdateListResponse(chromeos::FwupdUpdateList* updates) override;
+  void OnUpdateListResponse(const std::string& device_id,
+                            chromeos::FwupdUpdateList* updates) override;
 
-  FirmwareUpdateManager();
-  FirmwareUpdateManager(const FirmwareUpdateManager&) = delete;
-  FirmwareUpdateManager& operator=(const FirmwareUpdateManager&) = delete;
-  ~FirmwareUpdateManager() override;
+  // Query the fwupd DBus client for currently connected devices.
+  void RequestDevices();
 
-  // Gets the global instance pointer.
-  static FirmwareUpdateManager* Get();
+  // Query the fwupd DBus client for updates for a certain device.
+  void RequestUpdates(const std::string& device_id);
 
  protected:
   friend class FirmwareUpdateManagerTest;
diff --git a/ash/display/screen_orientation_controller_unittest.cc b/ash/display/screen_orientation_controller_unittest.cc
index 57da1aec..ad941068 100644
--- a/ash/display/screen_orientation_controller_unittest.cc
+++ b/ash/display/screen_orientation_controller_unittest.cc
@@ -10,7 +10,6 @@
 #include "ash/accelerometer/accelerometer_reader.h"
 #include "ash/accelerometer/accelerometer_types.h"
 #include "ash/constants/app_types.h"
-#include "ash/constants/ash_features.h"
 #include "ash/constants/ash_switches.h"
 #include "ash/display/screen_orientation_controller.h"
 #include "ash/display/screen_orientation_controller_test_api.h"
@@ -26,7 +25,6 @@
 #include "ash/wm/window_util.h"
 #include "base/command_line.h"
 #include "base/numerics/math_constants.h"
-#include "base/test/scoped_feature_list.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
 #include "ui/compositor/layer_type.h"
@@ -36,7 +34,6 @@
 #include "ui/display/manager/managed_display_info.h"
 #include "ui/display/test/display_manager_test_api.h"
 #include "ui/events/event_constants.h"
-#include "ui/message_center/message_center.h"
 #include "ui/wm/core/window_util.h"
 #include "ui/wm/public/activation_client.h"
 
@@ -456,68 +453,6 @@
   EXPECT_EQ(display::Display::ROTATE_90, GetCurrentInternalDisplayRotation());
 }
 
-// The ScreenLayoutObserver class that is responsible for adding/updating
-// MessageCenter notifications is only added to the SystemTray on ChromeOS.
-// Tests that the screen rotation notifications are suppressed when
-// triggered by the accelerometer.
-TEST_F(ScreenOrientationControllerTest, BlockRotationNotifications) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(
-      features::kReduceDisplayNotifications);
-
-  EnableTabletMode(true);
-  Shell::Get()->screen_layout_observer()->set_show_notifications_for_testing(
-      true);
-  display::test::DisplayManagerTestApi(display_manager())
-      .SetFirstDisplayAsInternalDisplay();
-
-  message_center::MessageCenter* message_center =
-      message_center::MessageCenter::Get();
-
-  EXPECT_EQ(0u, message_center->NotificationCount());
-  EXPECT_FALSE(message_center->HasPopupNotifications());
-
-  // Make sure notifications are still displayed when
-  // adjusting the screen rotation directly when in tablet mode
-  ASSERT_NE(display::Display::ROTATE_270, GetCurrentInternalDisplayRotation());
-  SetInternalDisplayRotation(display::Display::ROTATE_270);
-  SetSystemRotationLocked(false);
-  EXPECT_EQ(display::Display::ROTATE_270, GetCurrentInternalDisplayRotation());
-  EXPECT_EQ(1u, message_center->NotificationCount());
-  EXPECT_TRUE(message_center->HasPopupNotifications());
-
-  // Clear all notifications
-  message_center->RemoveAllNotifications(
-      false /* by_user */, message_center::MessageCenter::RemoveType::ALL);
-  EXPECT_EQ(0u, message_center->NotificationCount());
-  EXPECT_FALSE(message_center->HasPopupNotifications());
-
-  // Make sure notifications are blocked when adjusting the screen rotation
-  // via the accelerometer while in tablet mode
-  // Rotate the screen 90 degrees
-  ASSERT_EQ(display::Display::ROTATE_270, GetCurrentInternalDisplayRotation());
-  TriggerLidUpdate(gfx::Vector3dF(kMeanGravityFloat, 0.0f, 0.0f));
-  ASSERT_EQ(display::Display::ROTATE_90, GetCurrentInternalDisplayRotation());
-  EXPECT_EQ(0u, message_center->NotificationCount());
-  EXPECT_FALSE(message_center->HasPopupNotifications());
-
-  // Make sure notifications are still displayed when
-  // adjusting the screen rotation directly when not in tablet mode
-  EnableTabletMode(false);
-  // Reset the screen rotation.
-  SetInternalDisplayRotation(display::Display::ROTATE_0);
-  // Clear all notifications
-  message_center->RemoveAllNotifications(
-      false /* by_user */, message_center::MessageCenter::RemoveType::ALL);
-  ASSERT_NE(display::Display::ROTATE_180, GetCurrentInternalDisplayRotation());
-  ASSERT_EQ(0u, message_center->NotificationCount());
-  ASSERT_FALSE(message_center->HasPopupNotifications());
-  SetInternalDisplayRotation(display::Display::ROTATE_180);
-  EXPECT_EQ(display::Display::ROTATE_180, GetCurrentInternalDisplayRotation());
-  EXPECT_EQ(1u, message_center->NotificationCount());
-  EXPECT_TRUE(message_center->HasPopupNotifications());
-}
-
 // Tests that if a user has set a display rotation that it is restored upon
 // exiting tablet mode.
 TEST_F(ScreenOrientationControllerTest, ResetUserRotationUponExit) {
diff --git a/ash/quick_pair/keyed_service/BUILD.gn b/ash/quick_pair/keyed_service/BUILD.gn
index 58c9417..2bf156e 100644
--- a/ash/quick_pair/keyed_service/BUILD.gn
+++ b/ash/quick_pair/keyed_service/BUILD.gn
@@ -9,6 +9,8 @@
 
 static_library("keyed_service") {
   sources = [
+    "battery_update_message_handler.cc",
+    "battery_update_message_handler.h",
     "fast_pair_bluetooth_config_delegate.cc",
     "fast_pair_bluetooth_config_delegate.h",
     "quick_pair_keyed_service.cc",
@@ -33,6 +35,7 @@
     "//components/keyed_service/core",
     "//components/prefs",
     "//components/user_manager",
+    "//device/bluetooth",
   ]
 }
 
@@ -40,6 +43,7 @@
   testonly = true
 
   sources = [
+    "battery_update_message_handler_unittest.cc",
     "quick_pair_mediator_unittest.cc",
     "quick_pair_metrics_logger_unittest.cc",
   ]
@@ -55,8 +59,13 @@
     "//ash/quick_pair/scanning:test_support",
     "//ash/quick_pair/ui:test_support",
     "//ash/services/quick_pair",
+    "//ash/services/quick_pair:test_support",
+    "//base",
     "//base/test:test_support",
     "//components/user_manager:test_support",
+    "//device/bluetooth",
+    "//device/bluetooth:mocks",
+    "//mojo/public/cpp/bindings:bindings",
     "//testing/gtest",
   ]
 }
diff --git a/ash/quick_pair/keyed_service/battery_update_message_handler.cc b/ash/quick_pair/keyed_service/battery_update_message_handler.cc
new file mode 100644
index 0000000..ac0a2df
--- /dev/null
+++ b/ash/quick_pair/keyed_service/battery_update_message_handler.cc
@@ -0,0 +1,141 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/quick_pair/keyed_service/battery_update_message_handler.h"
+
+#include "ash/quick_pair/common/logging.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/containers/contains.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/bluetooth_device.h"
+
+namespace {
+
+device::BluetoothDevice::BatteryInfo GetBatteryInfo(
+    const ash::quick_pair::mojom::BatteryInfoPtr& battery_info,
+    const device::BluetoothDevice::BatteryType& battery_type) {
+  if (battery_info->percentage == -1) {
+    return device::BluetoothDevice::BatteryInfo(
+        battery_type,
+        /*percentage=*/absl::nullopt,
+        battery_info->is_charging
+            ? device::BluetoothDevice::BatteryInfo::ChargeState::kCharging
+            : device::BluetoothDevice::BatteryInfo::ChargeState::kDischarging);
+  }
+
+  return device::BluetoothDevice::BatteryInfo(
+      battery_type, battery_info->percentage,
+      battery_info->is_charging
+          ? device::BluetoothDevice::BatteryInfo::ChargeState::kCharging
+          : device::BluetoothDevice::BatteryInfo::ChargeState::kDischarging);
+}
+
+}  // namespace
+
+namespace ash {
+namespace quick_pair {
+
+BatteryUpdateMessageHandler::BatteryUpdateMessageHandler(
+    MessageStreamLookup* message_stream_lookup) {
+  device::BluetoothAdapterFactory::Get()->GetAdapter(
+      base::BindOnce(&BatteryUpdateMessageHandler::OnGetAdapter,
+                     weak_ptr_factory_.GetWeakPtr()));
+  message_stream_lookup_observation_.Observe(message_stream_lookup);
+}
+
+void BatteryUpdateMessageHandler::OnGetAdapter(
+    scoped_refptr<device::BluetoothAdapter> adapter) {
+  adapter_ = adapter;
+}
+
+BatteryUpdateMessageHandler::~BatteryUpdateMessageHandler() {
+  // Remove any observation of remaining MessageStreams.
+  for (auto it = message_streams_.begin(); it != message_streams_.end(); it++) {
+    it->second->RemoveObserver(this);
+  }
+}
+
+void BatteryUpdateMessageHandler::OnMessageStreamConnected(
+    const std::string& device_address,
+    MessageStream* message_stream) {
+  if (!message_stream)
+    return;
+
+  message_stream->AddObserver(this);
+  message_streams_[device_address] = message_stream;
+  GetBatteryUpdateFromMessageStream(device_address, message_stream);
+}
+
+void BatteryUpdateMessageHandler::GetBatteryUpdateFromMessageStream(
+    const std::string& device_address,
+    MessageStream* message_stream) {
+  DCHECK(message_stream);
+
+  // Iterate over messages for battery update if it already exists.
+  for (auto it = message_stream->messages().rbegin();
+       it != message_stream->messages().rend(); ++it) {
+    if ((*it)->is_battery_update()) {
+      SetBatteryInfo(device_address, (*it)->get_battery_update());
+      return;
+    }
+  }
+}
+
+void BatteryUpdateMessageHandler::OnBatteryUpdateMessage(
+    const std::string& device_address,
+    const mojom::BatteryUpdatePtr& battery_update) {
+  SetBatteryInfo(device_address, battery_update);
+}
+
+void BatteryUpdateMessageHandler::OnDisconnected(
+    const std::string& device_address) {
+  CleanUpMessageStream(device_address);
+}
+
+void BatteryUpdateMessageHandler::OnMessageStreamDestroyed(
+    const std::string& device_address) {
+  CleanUpMessageStream(device_address);
+}
+
+void BatteryUpdateMessageHandler::SetBatteryInfo(
+    const std::string& device_address,
+    const mojom::BatteryUpdatePtr& battery_update) {
+  device::BluetoothDevice* device = adapter_->GetDevice(device_address);
+  if (!device) {
+    QP_LOG(INFO) << "Device lost from adapter before battery info was set.";
+    CleanUpMessageStream(device_address);
+    return;
+  }
+
+  device::BluetoothDevice::BatteryInfo left_bud_info =
+      GetBatteryInfo(/*battery_info=*/battery_update->left_bud_info,
+                     /*battery_type=*/device::BluetoothDevice::BatteryType::
+                         kLeftBudTrueWireless);
+  device->SetBatteryInfo(left_bud_info);
+
+  device::BluetoothDevice::BatteryInfo right_bud_info =
+      GetBatteryInfo(/*battery_info=*/battery_update->right_bud_info,
+                     /*battery_type=*/device::BluetoothDevice::BatteryType::
+                         kRightBudTrueWireless);
+  device->SetBatteryInfo(right_bud_info);
+
+  device::BluetoothDevice::BatteryInfo case_info = GetBatteryInfo(
+      /*battery_info=*/battery_update->case_info,
+      /*battery_type=*/device::BluetoothDevice::BatteryType::kCaseTrueWireless);
+  device->SetBatteryInfo(case_info);
+}
+
+void BatteryUpdateMessageHandler::CleanUpMessageStream(
+    const std::string& device_address) {
+  if (message_streams_.find(device_address) == message_streams_.end())
+    return;
+
+  message_streams_[device_address]->RemoveObserver(this);
+  message_streams_.erase(device_address);
+}
+
+}  // namespace quick_pair
+}  // namespace ash
diff --git a/ash/quick_pair/keyed_service/battery_update_message_handler.h b/ash/quick_pair/keyed_service/battery_update_message_handler.h
new file mode 100644
index 0000000..1377bc7
--- /dev/null
+++ b/ash/quick_pair/keyed_service/battery_update_message_handler.h
@@ -0,0 +1,79 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_QUICK_PAIR_KEYED_SERVICE_BATTERY_UPDATE_MESSAGE_HANDLER_H_
+#define ASH_QUICK_PAIR_KEYED_SERVICE_BATTERY_UPDATE_MESSAGE_HANDLER_H_
+
+#include <string>
+
+#include "ash/quick_pair/message_stream/message_stream.h"
+#include "ash/quick_pair/message_stream/message_stream_lookup.h"
+#include "base/containers/flat_map.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/scoped_observation.h"
+
+namespace device {
+class BluetoothAdapter;
+}  // namespace device
+
+namespace ash {
+namespace quick_pair {
+
+// Observes MessageStreams instances for devices when they are created, and
+// on battery update messages, adds the battery information to the bluetooth
+// device.
+class BatteryUpdateMessageHandler : public MessageStreamLookup::Observer,
+                                    public MessageStream::Observer {
+ public:
+  explicit BatteryUpdateMessageHandler(
+      MessageStreamLookup* message_stream_lookup);
+  BatteryUpdateMessageHandler(const BatteryUpdateMessageHandler&) = delete;
+  BatteryUpdateMessageHandler& operator=(const BatteryUpdateMessageHandler&) =
+      delete;
+  ~BatteryUpdateMessageHandler() override;
+
+ private:
+  // MessageStreamLookup::Observer
+  void OnMessageStreamConnected(const std::string& device_address,
+                                MessageStream* message_stream) override;
+
+  // MessageStream::Observer
+  void OnBatteryUpdateMessage(
+      const std::string& device_address,
+      const mojom::BatteryUpdatePtr& battery_update) override;
+  void OnDisconnected(const std::string& device_address) override;
+  void OnMessageStreamDestroyed(const std::string& device_address) override;
+
+  // Internal method called by BluetoothAdapterFactory to provide the adapter
+  // object.
+  void OnGetAdapter(scoped_refptr<device::BluetoothAdapter> adapter);
+
+  // Parses MessageStream messages for battery update, and notifies observers
+  // if it exists.
+  void GetBatteryUpdateFromMessageStream(const std::string& device_address,
+                                         MessageStream* message_stream);
+
+  // Sets the battery information on the bluetooth device at |device_address|.
+  void SetBatteryInfo(const std::string& device_address,
+                      const mojom::BatteryUpdatePtr& battery_update);
+
+  // Cleans up memory associated with a MessageStream corresponding to
+  // |device_address| if it exists.
+  void CleanUpMessageStream(const std::string& device_address);
+
+  // Map of the classic pairing address to their corresponding MessageStreams.
+  base::flat_map<std::string, MessageStream*> message_streams_;
+
+  scoped_refptr<device::BluetoothAdapter> adapter_;
+
+  base::ScopedObservation<MessageStreamLookup, MessageStreamLookup::Observer>
+      message_stream_lookup_observation_{this};
+  base::WeakPtrFactory<BatteryUpdateMessageHandler> weak_ptr_factory_{this};
+};
+
+}  // namespace quick_pair
+}  // namespace ash
+
+#endif  // ASH_QUICK_PAIR_KEYED_SERVICE_BATTERY_UPDATE_MESSAGE_HANDLER_H_
diff --git a/ash/quick_pair/keyed_service/battery_update_message_handler_unittest.cc b/ash/quick_pair/keyed_service/battery_update_message_handler_unittest.cc
new file mode 100644
index 0000000..fb8f468
--- /dev/null
+++ b/ash/quick_pair/keyed_service/battery_update_message_handler_unittest.cc
@@ -0,0 +1,504 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/quick_pair/keyed_service/battery_update_message_handler.h"
+
+#include <memory>
+
+#include "ash/quick_pair/common/constants.h"
+#include "ash/quick_pair/common/device.h"
+#include "ash/quick_pair/common/logging.h"
+#include "ash/quick_pair/common/protocol.h"
+#include "ash/quick_pair/message_stream/fake_bluetooth_socket.h"
+#include "ash/quick_pair/message_stream/fake_message_stream_lookup.h"
+#include "ash/quick_pair/message_stream/message_stream.h"
+#include "ash/quick_pair/message_stream/message_stream_lookup.h"
+#include "ash/quick_pair/pairing/mock_pairer_broker.h"
+#include "ash/quick_pair/pairing/pairer_broker.h"
+#include "ash/services/quick_pair/fast_pair_data_parser.h"
+#include "ash/services/quick_pair/mock_quick_pair_process_manager.h"
+#include "ash/services/quick_pair/quick_pair_process.h"
+#include "ash/services/quick_pair/quick_pair_process_manager.h"
+#include "ash/services/quick_pair/quick_pair_process_manager_impl.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
+#include "device/bluetooth/test/mock_bluetooth_device.h"
+#include "mojo/public/cpp/bindings/shared_remote.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace {
+
+constexpr char kTestDeviceAddress[] = "11:12:13:14:15:16";
+constexpr char kTestBleDeviceName[] = "Test Device Name";
+
+std::vector<uint8_t> kBatteryUpdateBytes1 = {/*mesage_group=*/0x03,
+                                             /*mesage_code=*/0x03,
+                                             /*additional_data_length=*/0x00,
+                                             0x03,
+                                             /*additional_data=*/0x57,
+                                             0x41,
+                                             0x7F};
+std::vector<uint8_t> kBatteryUpdateBytes2 = {/*mesage_group=*/0x03,
+                                             /*mesage_code=*/0x03,
+                                             /*additional_data_length=*/0x00,
+                                             0x03,
+                                             /*additional_data=*/0x51,
+                                             0x38,
+                                             0x38};
+
+const std::vector<uint8_t> kModelIdBytes = {
+    /*message_group=*/0x03,
+    /*message_code=*/0x01,
+    /*additional_data_length=*/0x00, 0x03,
+    /*additional_data=*/0xAA,        0xBB, 0xCC};
+
+std::unique_ptr<testing::NiceMock<device::MockBluetoothDevice>>
+CreateTestBluetoothDevice(std::string address,
+                          device::MockBluetoothAdapter* adapter) {
+  return std::make_unique<testing::NiceMock<device::MockBluetoothDevice>>(
+      /*adapter=*/adapter, /*bluetooth_class=*/0, kTestBleDeviceName, address,
+      /*paired=*/true, /*connected=*/false);
+}
+
+}  // namespace
+
+namespace ash {
+namespace quick_pair {
+
+class BatteryUpdateMessageFakeBluetoothAdapter
+    : public testing::NiceMock<device::MockBluetoothAdapter> {
+ public:
+  device::BluetoothDevice* GetDevice(const std::string& address) override {
+    for (const auto& it : mock_devices_) {
+      if (it->GetAddress() == address)
+        return it.get();
+    }
+
+    return nullptr;
+  }
+
+ private:
+  ~BatteryUpdateMessageFakeBluetoothAdapter() = default;
+};
+
+class BatteryUpdateMessageHandlerTest : public testing::Test {
+ public:
+  void SetUp() override {
+    adapter_ = base::MakeRefCounted<BatteryUpdateMessageFakeBluetoothAdapter>();
+    std::unique_ptr<testing::NiceMock<device::MockBluetoothDevice>>
+        bluetooth_device =
+            CreateTestBluetoothDevice(kTestDeviceAddress, adapter_.get());
+    device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_);
+    bluetooth_device_ = bluetooth_device.get();
+    adapter_->AddMockDevice(std::move(bluetooth_device));
+
+    message_stream_lookup_ = std::make_unique<FakeMessageStreamLookup>();
+    fake_message_stream_lookup_ =
+        static_cast<FakeMessageStreamLookup*>(message_stream_lookup_.get());
+    message_stream_ =
+        std::make_unique<MessageStream>(kTestDeviceAddress, fake_socket_.get());
+
+    process_manager_ = std::make_unique<MockQuickPairProcessManager>();
+    quick_pair_process::SetProcessManager(process_manager_.get());
+    data_parser_ = std::make_unique<FastPairDataParser>(
+        fast_pair_data_parser_.InitWithNewPipeAndPassReceiver());
+    data_parser_remote_.Bind(std::move(fast_pair_data_parser_),
+                             task_environment_.GetMainThreadTaskRunner());
+    EXPECT_CALL(*mock_process_manager(), GetProcessReference)
+        .WillRepeatedly([&](QuickPairProcessManager::ProcessStoppedCallback) {
+          return std::make_unique<
+              QuickPairProcessManagerImpl::ProcessReferenceImpl>(
+              data_parser_remote_, base::DoNothing());
+        });
+
+    battery_update_message_handler_ =
+        std::make_unique<BatteryUpdateMessageHandler>(
+            message_stream_lookup_.get());
+  }
+
+  MockQuickPairProcessManager* mock_process_manager() {
+    return static_cast<MockQuickPairProcessManager*>(process_manager_.get());
+  }
+
+  void SetMessageStream(const std::vector<uint8_t>& message_bytes) {
+    fake_socket_->SetIOBufferFromBytes(message_bytes);
+    message_stream_ =
+        std::make_unique<MessageStream>(kTestDeviceAddress, fake_socket_.get());
+  }
+
+  void AddMessageStream(const std::vector<uint8_t>& message_bytes) {
+    fake_socket_->SetIOBufferFromBytes(message_bytes);
+    message_stream_ =
+        std::make_unique<MessageStream>(kTestDeviceAddress, fake_socket_.get());
+    fake_message_stream_lookup_->AddMessageStream(kTestDeviceAddress,
+                                                  message_stream_.get());
+  }
+
+  void NotifyMessageStreamConnected(std::string device_address) {
+    fake_message_stream_lookup_->NotifyMessageStreamConnected(
+        device_address, message_stream_.get());
+  }
+
+ protected:
+  base::test::TaskEnvironment task_environment_{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+
+  scoped_refptr<BatteryUpdateMessageFakeBluetoothAdapter> adapter_;
+
+  scoped_refptr<FakeBluetoothSocket> fake_socket_ =
+      base::MakeRefCounted<FakeBluetoothSocket>();
+  std::unique_ptr<MessageStream> message_stream_;
+  std::unique_ptr<MessageStreamLookup> message_stream_lookup_;
+  FakeMessageStreamLookup* fake_message_stream_lookup_ = nullptr;
+
+  mojo::SharedRemote<mojom::FastPairDataParser> data_parser_remote_;
+  mojo::PendingRemote<mojom::FastPairDataParser> fast_pair_data_parser_;
+  std::unique_ptr<FastPairDataParser> data_parser_;
+  std::unique_ptr<QuickPairProcessManager> process_manager_;
+
+  device::BluetoothDevice* bluetooth_device_ = nullptr;
+  std::unique_ptr<BatteryUpdateMessageHandler> battery_update_message_handler_;
+};
+
+TEST_F(BatteryUpdateMessageHandlerTest, BatteryUpdate_GetMessages) {
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+
+  SetMessageStream(kBatteryUpdateBytes1);
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_NE(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_NE(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_NE(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+}
+
+TEST_F(BatteryUpdateMessageHandlerTest, BatteryUpdate_Observation) {
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+
+  fake_socket_->SetIOBufferFromBytes(kBatteryUpdateBytes1);
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_NE(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_NE(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_NE(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+}
+
+TEST_F(BatteryUpdateMessageHandlerTest, BatteryUpdate_MultipleMessages) {
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+
+  SetMessageStream(kBatteryUpdateBytes1);
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(
+      bluetooth_device_
+          ->GetBatteryInfo(
+              device::BluetoothDevice::BatteryType::kLeftBudTrueWireless)
+          ->percentage);
+  EXPECT_EQ(87,
+            bluetooth_device_
+                ->GetBatteryInfo(
+                    device::BluetoothDevice::BatteryType::kLeftBudTrueWireless)
+                ->percentage.value());
+  EXPECT_TRUE(
+      bluetooth_device_
+          ->GetBatteryInfo(
+              device::BluetoothDevice::BatteryType::kRightBudTrueWireless)
+          ->percentage);
+  EXPECT_EQ(65,
+            bluetooth_device_
+                ->GetBatteryInfo(
+                    device::BluetoothDevice::BatteryType::kRightBudTrueWireless)
+                ->percentage.value());
+
+  EXPECT_FALSE(bluetooth_device_
+                   ->GetBatteryInfo(
+                       device::BluetoothDevice::BatteryType::kCaseTrueWireless)
+                   ->percentage);
+
+  fake_socket_->SetIOBufferFromBytes(kBatteryUpdateBytes2);
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(
+      bluetooth_device_
+          ->GetBatteryInfo(
+              device::BluetoothDevice::BatteryType::kLeftBudTrueWireless)
+          ->percentage);
+  EXPECT_EQ(81,
+            bluetooth_device_
+                ->GetBatteryInfo(
+                    device::BluetoothDevice::BatteryType::kLeftBudTrueWireless)
+                ->percentage.value());
+  EXPECT_TRUE(
+      bluetooth_device_
+          ->GetBatteryInfo(
+              device::BluetoothDevice::BatteryType::kRightBudTrueWireless)
+          ->percentage);
+  EXPECT_EQ(56,
+            bluetooth_device_
+                ->GetBatteryInfo(
+                    device::BluetoothDevice::BatteryType::kRightBudTrueWireless)
+                ->percentage.value());
+
+  EXPECT_TRUE(bluetooth_device_
+                  ->GetBatteryInfo(
+                      device::BluetoothDevice::BatteryType::kCaseTrueWireless)
+                  ->percentage);
+  EXPECT_EQ(56, bluetooth_device_
+                    ->GetBatteryInfo(
+                        device::BluetoothDevice::BatteryType::kCaseTrueWireless)
+                    ->percentage.value());
+}
+
+TEST_F(BatteryUpdateMessageHandlerTest, NoBatteryUpdate_GetMessages) {
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+
+  SetMessageStream(kModelIdBytes);
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+}
+
+TEST_F(BatteryUpdateMessageHandlerTest, NoBatteryUpdate_Observation) {
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+
+  fake_socket_->SetIOBufferFromBytes(kModelIdBytes);
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+}
+
+TEST_F(BatteryUpdateMessageHandlerTest, MessageStreamRemovedOnDestroyed) {
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+
+  SetMessageStream(kBatteryUpdateBytes1);
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  message_stream_.reset();
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+}
+
+TEST_F(BatteryUpdateMessageHandlerTest, MessageStreamRemovedOnDisconnect) {
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+
+  fake_socket_->SetErrorReason(
+      device::BluetoothSocket::ErrorReason::kDisconnected);
+  message_stream_ =
+      std::make_unique<MessageStream>(kTestDeviceAddress, fake_socket_.get());
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+}
+
+TEST_F(BatteryUpdateMessageHandlerTest,
+       MessageStreamRemovedOnDisconnect_MessageStreamDestroted) {
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+
+  fake_socket_->SetErrorReason(
+      device::BluetoothSocket::ErrorReason::kDisconnected);
+  message_stream_ =
+      std::make_unique<MessageStream>(kTestDeviceAddress, fake_socket_.get());
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+
+  SetMessageStream(kBatteryUpdateBytes1);
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  message_stream_.reset();
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+}
+
+TEST_F(BatteryUpdateMessageHandlerTest, DeviceLost) {
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device_->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+
+  SetMessageStream(kBatteryUpdateBytes1);
+  fake_socket_->TriggerReceiveCallback();
+  base::RunLoop().RunUntilIdle();
+  auto bluetooth_device = adapter_->RemoveMockDevice(kTestDeviceAddress);
+
+  NotifyMessageStreamConnected(kTestDeviceAddress);
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kLeftBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kRightBudTrueWireless));
+  EXPECT_EQ(absl::nullopt,
+            bluetooth_device->GetBatteryInfo(
+                device::BluetoothDevice::BatteryType::kCaseTrueWireless));
+}
+
+}  // namespace quick_pair
+}  // namespace ash
diff --git a/ash/quick_pair/keyed_service/quick_pair_mediator.cc b/ash/quick_pair/keyed_service/quick_pair_mediator.cc
index 08dfcab5..8461a9a 100644
--- a/ash/quick_pair/keyed_service/quick_pair_mediator.cc
+++ b/ash/quick_pair/keyed_service/quick_pair_mediator.cc
@@ -12,6 +12,7 @@
 #include "ash/quick_pair/feature_status_tracker/fast_pair_pref_enabled_provider.h"
 #include "ash/quick_pair/feature_status_tracker/quick_pair_feature_status_tracker.h"
 #include "ash/quick_pair/feature_status_tracker/quick_pair_feature_status_tracker_impl.h"
+#include "ash/quick_pair/keyed_service/battery_update_message_handler.h"
 #include "ash/quick_pair/keyed_service/fast_pair_bluetooth_config_delegate.h"
 #include "ash/quick_pair/keyed_service/quick_pair_metrics_logger.h"
 #include "ash/quick_pair/message_stream/message_stream_lookup.h"
@@ -74,7 +75,7 @@
     : feature_status_tracker_(std::move(feature_status_tracker)),
       scanner_broker_(std::move(scanner_broker)),
       retroactive_pairing_detector_(std::move(retroactive_pairing_detector)),
-      message_stream_lookup(std::move(message_stream_lookup)),
+      message_stream_lookup_(std::move(message_stream_lookup)),
       pairer_broker_(std::move(pairer_broker)),
       ui_broker_(std::move(ui_broker)),
       fast_pair_repository_(std::move(fast_pair_repository)),
@@ -83,7 +84,9 @@
           std::make_unique<FastPairBluetoothConfigDelegate>()) {
   metrics_logger_ = std::make_unique<QuickPairMetricsLogger>(
       scanner_broker_.get(), pairer_broker_.get(), ui_broker_.get());
-
+  battery_update_message_handler_ =
+      std::make_unique<BatteryUpdateMessageHandler>(
+          message_stream_lookup_.get());
   feature_status_tracker_observation_.Observe(feature_status_tracker_.get());
   scanner_broker_observation_.Observe(scanner_broker_.get());
   retroactive_pairing_detector_observation_.Observe(
diff --git a/ash/quick_pair/keyed_service/quick_pair_mediator.h b/ash/quick_pair/keyed_service/quick_pair_mediator.h
index ade3774..5cc2cac9 100644
--- a/ash/quick_pair/keyed_service/quick_pair_mediator.h
+++ b/ash/quick_pair/keyed_service/quick_pair_mediator.h
@@ -32,6 +32,7 @@
 class QuickPairProcessManager;
 class QuickPairMetricsLogger;
 class MessageStreamLookup;
+class BatteryUpdateMessageHandler;
 
 // Implements the Mediator design pattern for the components in the Quick Pair
 // system, e.g. the UI Broker, Scanning Broker and Pairing Broker.
@@ -101,7 +102,7 @@
   std::unique_ptr<FeatureStatusTracker> feature_status_tracker_;
   std::unique_ptr<ScannerBroker> scanner_broker_;
   std::unique_ptr<RetroactivePairingDetector> retroactive_pairing_detector_;
-  std::unique_ptr<MessageStreamLookup> message_stream_lookup;
+  std::unique_ptr<MessageStreamLookup> message_stream_lookup_;
   std::unique_ptr<PairerBroker> pairer_broker_;
   std::unique_ptr<UIBroker> ui_broker_;
   std::unique_ptr<FastPairRepository> fast_pair_repository_;
@@ -109,6 +110,7 @@
   std::unique_ptr<QuickPairMetricsLogger> metrics_logger_;
   std::unique_ptr<FastPairBluetoothConfigDelegate>
       fast_pair_bluetooth_config_delegate_;
+  std::unique_ptr<BatteryUpdateMessageHandler> battery_update_message_handler_;
 
   base::ScopedObservation<FeatureStatusTracker, FeatureStatusTracker::Observer>
       feature_status_tracker_observation_{this};
diff --git a/ash/quick_pair/keyed_service/quick_pair_mediator_unittest.cc b/ash/quick_pair/keyed_service/quick_pair_mediator_unittest.cc
index 40dce53..b06b977 100644
--- a/ash/quick_pair/keyed_service/quick_pair_mediator_unittest.cc
+++ b/ash/quick_pair/keyed_service/quick_pair_mediator_unittest.cc
@@ -26,6 +26,9 @@
 #include "ash/services/quick_pair/quick_pair_process_manager_impl.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/test/task_environment.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/test/mock_bluetooth_adapter.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -42,6 +45,10 @@
 class MediatorTest : public testing::Test {
  public:
   void SetUp() override {
+    adapter_ =
+        base::MakeRefCounted<testing::NiceMock<device::MockBluetoothAdapter>>();
+    device::BluetoothAdapterFactory::SetAdapterForTesting(adapter_);
+
     std::unique_ptr<FeatureStatusTracker> tracker =
         std::make_unique<FakeFeatureStatusTracker>();
     feature_status_tracker_ =
@@ -78,6 +85,7 @@
 
  protected:
   scoped_refptr<Device> device_;
+  scoped_refptr<testing::NiceMock<device::MockBluetoothAdapter>> adapter_;
   FakeFeatureStatusTracker* feature_status_tracker_;
   MockScannerBroker* mock_scanner_broker_;
   FakeRetroactivePairingDetector* fake_retroactive_pairing_detector_;
diff --git a/ash/services/recording/video_capture_params.cc b/ash/services/recording/video_capture_params.cc
index 1832e81..20675dd 100644
--- a/ash/services/recording/video_capture_params.cc
+++ b/ash/services/recording/video_capture_params.cc
@@ -121,12 +121,8 @@
     DCHECK_NE(frame_sink_id_, new_frame_sink_id);
 
     frame_sink_id_ = new_frame_sink_id;
-
-    auto sub_target =
-        subtree_capture_id_.is_valid()
-            ? viz::mojom::SubTarget::NewSubtreeCaptureId(subtree_capture_id_)
-            : nullptr;
-    capturer->ChangeTarget(frame_sink_id_, std::move(sub_target));
+    capturer->ChangeTarget(
+        viz::VideoCaptureTarget(frame_sink_id_, subtree_capture_id_));
 
     // If the movement to another display results in changes in the frame sink
     // size or DSF, OnVideoSizeMayHaveChanged() will be called by the below
@@ -293,12 +289,8 @@
   // TODO(afakhry): Discuss with //media/ team the implications of color space
   // conversions.
   capturer->SetFormat(media::PIXEL_FORMAT_I420, kColorSpace);
-
-  auto sub_target =
-      subtree_capture_id_.is_valid()
-          ? viz::mojom::SubTarget::NewSubtreeCaptureId(subtree_capture_id_)
-          : nullptr;
-  capturer->ChangeTarget(frame_sink_id_, std::move(sub_target));
+  capturer->ChangeTarget(
+      viz::VideoCaptureTarget(frame_sink_id_, subtree_capture_id_));
 }
 
 gfx::Rect VideoCaptureParams::GetVideoFrameVisibleRect(
diff --git a/ash/strings/BUILD.gn b/ash/strings/BUILD.gn
index 12462fc..faece9b 100644
--- a/ash/strings/BUILD.gn
+++ b/ash/strings/BUILD.gn
@@ -31,6 +31,7 @@
       "$root_gen_dir/device/bluetooth/strings/bluetooth_strings_${locale}.pak",
       "$root_gen_dir/ui/chromeos/strings/ui_chromeos_strings_${locale}.pak",
       "$root_gen_dir/ui/strings/app_locale_settings_${locale}.pak",
+      "$root_gen_dir/ui/strings/ax_strings_${locale}.pak",
       "$root_gen_dir/ui/strings/ui_strings_${locale}.pak",
     ]
 
@@ -42,6 +43,7 @@
       "//device/bluetooth/strings",
       "//ui/chromeos/strings",
       "//ui/strings:app_locale_settings",
+      "//ui/strings:ax_strings",
       "//ui/strings:ui_strings",
     ]
   }
diff --git a/ash/style/icon_button.cc b/ash/style/icon_button.cc
new file mode 100644
index 0000000..6fe7f3a
--- /dev/null
+++ b/ash/style/icon_button.cc
@@ -0,0 +1,118 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/style/icon_button.h"
+
+#include "ash/style/ash_color_provider.h"
+#include "ash/style/style_util.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/views/controls/highlight_path_generator.h"
+
+namespace ash {
+namespace {
+
+constexpr int kSmallButtonSize = 32;
+constexpr int kMediumButtonSize = 36;
+constexpr int kLargeButtonSize = 48;
+
+// Icon size of the IconButton. Though the button has different sizes, the icon
+// inside will be kept the same size.
+constexpr int kIconSize = 20;
+
+int GetButtonSizeOnType(IconButton::Type type) {
+  switch (type) {
+    case IconButton::Type::kSmall:
+    case IconButton::Type::kSmallFloating:
+      return kSmallButtonSize;
+    case IconButton::Type::kMedium:
+    case IconButton::Type::kMediumFloating:
+      return kMediumButtonSize;
+    case IconButton::Type::kLarge:
+    case IconButton::Type::kLargeFloating:
+      return kLargeButtonSize;
+  }
+}
+
+bool IsFloatingIconButton(IconButton::Type type) {
+  return type == IconButton::Type::kSmallFloating ||
+         type == IconButton::Type::kMediumFloating ||
+         type == IconButton::Type::kLargeFloating;
+}
+
+}  // namespace
+
+IconButton::IconButton(PressedCallback callback,
+                       IconButton::Type type,
+                       const gfx::VectorIcon& icon,
+                       int accessible_name_id)
+    : views::ImageButton(std::move(callback)), type_(type), icon_(icon) {
+  const int button_size = GetButtonSizeOnType(type);
+  SetPreferredSize(gfx::Size(button_size, button_size));
+
+  SetImageHorizontalAlignment(ALIGN_CENTER);
+  SetImageVerticalAlignment(ALIGN_MIDDLE);
+  if (accessible_name_id)
+    SetTooltipText(l10n_util::GetStringUTF16(accessible_name_id));
+  StyleUtil::SetUpInkDropForButton(this, gfx::Insets(),
+                                   /*highlight_on_hover=*/false,
+                                   /*highlight_on_focus=*/false);
+  views::InstallCircleHighlightPathGenerator(this);
+}
+
+IconButton::~IconButton() = default;
+
+void IconButton::PaintButtonContents(gfx::Canvas* canvas) {
+  if (!IsFloatingIconButton(type_)) {
+    const gfx::Rect rect(GetContentsBounds());
+    cc::PaintFlags flags;
+    flags.setAntiAlias(true);
+    flags.setColor(AshColorProvider::Get()->GetControlsLayerColor(
+        AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive));
+    flags.setStyle(cc::PaintFlags::kFill_Style);
+    canvas->DrawCircle(gfx::PointF(rect.CenterPoint()), rect.width() / 2,
+                       flags);
+  }
+
+  views::ImageButton::PaintButtonContents(canvas);
+}
+
+void IconButton::OnThemeChanged() {
+  views::ImageButton::OnThemeChanged();
+
+  auto* color_provider = AshColorProvider::Get();
+  const SkColor icon_color = color_provider->GetContentLayerColor(
+      AshColorProvider::ContentLayerType::kButtonIconColor);
+
+  // Skip repainting if the incoming icon is the same as the current icon. If
+  // the icon has been painted before, |gfx::CreateVectorIcon()| will simply
+  // grab the ImageSkia from a cache, so it will be cheap. Note that this
+  // assumes that toggled/disabled images changes at the same time as the normal
+  // image, which it currently does.
+  const gfx::ImageSkia new_normal_image =
+      gfx::CreateVectorIcon(icon_, kIconSize, icon_color);
+  const gfx::ImageSkia& old_normal_image =
+      GetImage(views::Button::STATE_NORMAL);
+  if (!new_normal_image.isNull() && !old_normal_image.isNull() &&
+      new_normal_image.BackedBySameObjectAs(old_normal_image)) {
+    return;
+  }
+
+  SetImage(views::Button::STATE_NORMAL, new_normal_image);
+  SetImage(
+      views::Button::STATE_DISABLED,
+      gfx::CreateVectorIcon(icon_, kIconSize,
+                            AshColorProvider::GetDisabledColor(icon_color)));
+
+  views::FocusRing::Get(this)->SetColor(
+      AshColorProvider::Get()->GetControlsLayerColor(
+          AshColorProvider::ControlsLayerType::kFocusRingColor));
+}
+
+BEGIN_METADATA(IconButton, views::ImageButton)
+END_METADATA
+
+}  // namespace ash
diff --git a/ash/style/icon_button.h b/ash/style/icon_button.h
new file mode 100644
index 0000000..642b4921
--- /dev/null
+++ b/ash/style/icon_button.h
@@ -0,0 +1,48 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_STYLE_ICON_BUTTON_H_
+#define ASH_STYLE_ICON_BUTTON_H_
+
+#include "ui/base/metadata/metadata_header_macros.h"
+#include "ui/gfx/vector_icon_types.h"
+#include "ui/views/controls/button/image_button.h"
+
+namespace ash {
+
+// A circular ImageButton that can have small/medium/large different sizes. Each
+// of them has the floating version, which do not have the background.
+class IconButton : public views::ImageButton {
+ public:
+  METADATA_HEADER(IconButton);
+
+  enum class Type {
+    kSmall,
+    kMedium,
+    kLarge,
+    kSmallFloating,
+    kMediumFloating,
+    kLargeFloating
+  };
+
+  IconButton(PressedCallback callback,
+             Type type,
+             const gfx::VectorIcon& icon,
+             int accessible_name_id);
+  IconButton(const IconButton&) = delete;
+  IconButton& operator=(const IconButton&) = delete;
+  ~IconButton() override;
+
+  // views::ImageButton:
+  void PaintButtonContents(gfx::Canvas* canvas) override;
+  void OnThemeChanged() override;
+
+ private:
+  const Type type_;
+  const gfx::VectorIcon& icon_;
+};
+
+}  // namespace ash
+
+#endif  // ASH_STYLE_ICON_BUTTON_H_
diff --git a/ash/system/accessibility/autoclick_menu_view.cc b/ash/system/accessibility/autoclick_menu_view.cc
index 98f6a6d..d24260a 100644
--- a/ash/system/accessibility/autoclick_menu_view.cc
+++ b/ash/system/accessibility/autoclick_menu_view.cc
@@ -11,7 +11,6 @@
 #include "ash/style/ash_color_provider.h"
 #include "ash/system/accessibility/floating_menu_button.h"
 #include "ash/system/tray/tray_constants.h"
-#include "ash/system/unified/top_shortcut_button.h"
 #include "base/bind.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
diff --git a/ash/system/bluetooth/bluetooth_detailed_view_impl.cc b/ash/system/bluetooth/bluetooth_detailed_view_impl.cc
index d2f4292..23c5fa4 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view_impl.cc
+++ b/ash/system/bluetooth/bluetooth_detailed_view_impl.cc
@@ -12,6 +12,7 @@
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
+#include "ash/style/icon_button.h"
 #include "ash/system/bluetooth/bluetooth_device_list_item_view.h"
 #include "ash/system/bluetooth/bluetooth_disabled_detailed_view.h"
 #include "ash/system/model/system_tray_model.h"
@@ -20,7 +21,6 @@
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/system/tray/tray_toggle_button.h"
 #include "ash/system/tray/tri_view.h"
-#include "ash/system/unified/top_shortcut_button.h"
 #include "base/check.h"
 #include "base/memory/ptr_util.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -151,10 +151,10 @@
   hover_highlight_view->SetID(static_cast<int>(
       BluetoothDetailedViewChildId::kPairNewDeviceClickableView));
 
-  std::unique_ptr<ash::TopShortcutButton> button =
-      std::make_unique<ash::TopShortcutButton>(
-          views::Button::PressedCallback(), kSystemMenuBluetoothPlusIcon,
-          IDS_ASH_STATUS_TRAY_BLUETOOTH_PAIR_NEW_DEVICE);
+  std::unique_ptr<ash::IconButton> button = std::make_unique<ash::IconButton>(
+      views::Button::PressedCallback(), IconButton::Type::kSmall,
+      kSystemMenuBluetoothPlusIcon,
+      IDS_ASH_STATUS_TRAY_BLUETOOTH_PAIR_NEW_DEVICE);
   button->SetCanProcessEventsWithinSubtree(/*can_process=*/false);
   button->SetFocusBehavior(
       /*focus_behavior=*/views::View::FocusBehavior::NEVER);
diff --git a/ash/system/bluetooth/bluetooth_detailed_view_unittest.cc b/ash/system/bluetooth/bluetooth_detailed_view_unittest.cc
index 1e4a2f9d..c489baf 100644
--- a/ash/system/bluetooth/bluetooth_detailed_view_unittest.cc
+++ b/ash/system/bluetooth/bluetooth_detailed_view_unittest.cc
@@ -9,11 +9,11 @@
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/test/test_system_tray_client.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/icon_button.h"
 #include "ash/system/bluetooth/bluetooth_detailed_view_impl.h"
 #include "ash/system/bluetooth/bluetooth_device_list_item_view.h"
 #include "ash/system/bluetooth/bluetooth_disabled_detailed_view.h"
 #include "ash/system/tray/detailed_view_delegate.h"
-#include "ash/system/unified/top_shortcut_button.h"
 #include "ash/test/ash_test_base.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
@@ -127,8 +127,8 @@
     AshTestBase::TearDown();
   }
 
-  ash::TopShortcutButton* FindPairNewDeviceClickableView() {
-    return FindViewById<ash::TopShortcutButton*>(
+  ash::IconButton* FindPairNewDeviceClickableView() {
+    return FindViewById<ash::IconButton*>(
         BluetoothDetailedViewImpl::BluetoothDetailedViewChildId::
             kPairNewDeviceClickableView);
   }
@@ -251,8 +251,7 @@
 }
 
 TEST_F(BluetoothDetailedViewTest, PressingPairNewDeviceNotifiesDelegate) {
-  ash::TopShortcutButton* pair_new_device_button =
-      FindPairNewDeviceClickableView();
+  IconButton* pair_new_device_button = FindPairNewDeviceClickableView();
   views::View* pair_new_device_view = FindPairNewDeviceView();
 
   EXPECT_FALSE(pair_new_device_view->GetVisible());
@@ -266,8 +265,7 @@
 }
 
 TEST_F(BluetoothDetailedViewTest, PairNewDeviceButtonIsCentered) {
-  ash::TopShortcutButton* pair_new_device_button =
-      FindPairNewDeviceClickableView();
+  IconButton* pair_new_device_button = FindPairNewDeviceClickableView();
   views::View* pair_new_device_view = FindPairNewDeviceView();
 
   bluetooth_detailed_view()->UpdateBluetoothEnabledState(true);
diff --git a/ash/system/ime_menu/ime_menu_tray.cc b/ash/system/ime_menu/ime_menu_tray.cc
index 44300131..867f523 100644
--- a/ash/system/ime_menu/ime_menu_tray.cc
+++ b/ash/system/ime_menu/ime_menu_tray.cc
@@ -17,6 +17,7 @@
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
+#include "ash/style/icon_button.h"
 #include "ash/system/ime_menu/ime_list_view.h"
 #include "ash/system/model/system_tray_model.h"
 #include "ash/system/tray/detailed_view_delegate.h"
@@ -27,7 +28,6 @@
 #include "ash/system/tray/tray_container.h"
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/system/tray/tray_utils.h"
-#include "ash/system/unified/top_shortcut_button.h"
 #include "base/bind.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
@@ -149,13 +149,14 @@
                                      TrayPopupUtils::FontStyle::kPodMenuHeader);
     SetFlexForView(title_label, 1);
 
-    settings_button_ = AddChildView(std::make_unique<TopShortcutButton>(
+    settings_button_ = AddChildView(std::make_unique<IconButton>(
         base::BindRepeating([]() {
           base::RecordAction(
               base::UserMetricsAction("StatusArea_IME_Detailed"));
           Shell::Get()->system_tray_model()->client()->ShowIMESettings();
         }),
-        kSystemMenuSettingsIcon, IDS_ASH_STATUS_TRAY_IME_SETTINGS));
+        IconButton::Type::kSmall, kSystemMenuSettingsIcon,
+        IDS_ASH_STATUS_TRAY_IME_SETTINGS));
     settings_button_->SetEnabled(TrayPopupUtils::CanOpenWebUISettings());
   }
   ImeTitleView(const ImeTitleView&) = delete;
@@ -164,7 +165,7 @@
   ~ImeTitleView() override = default;
 
  private:
-  TopShortcutButton* settings_button_ = nullptr;
+  IconButton* settings_button_ = nullptr;
 };
 
 BEGIN_METADATA(ImeTitleView, views::BoxLayoutView)
diff --git a/ash/system/media/media_tray.cc b/ash/system/media/media_tray.cc
index 824e361..94695a4 100644
--- a/ash/system/media/media_tray.cc
+++ b/ash/system/media/media_tray.cc
@@ -172,9 +172,10 @@
 }
 
 MediaTray::PinButton::PinButton()
-    : TopShortcutButton(
+    : IconButton(
           base::BindRepeating(&PinButton::ButtonPressed,
                               base::Unretained(this)),
+          IconButton::Type::kSmall,
           MediaTray::IsPinnedToShelf() ? kPinnedIcon : kUnpinnedIcon,
           MediaTray::IsPinnedToShelf()
               ? IDS_ASH_GLOBAL_MEDIA_CONTROLS_PINNED_BUTTON_TOOLTIP_TEXT
diff --git a/ash/system/media/media_tray.h b/ash/system/media/media_tray.h
index b58b69ce..1e12e0bc 100644
--- a/ash/system/media/media_tray.h
+++ b/ash/system/media/media_tray.h
@@ -7,9 +7,9 @@
 
 #include "ash/ash_export.h"
 #include "ash/public/cpp/session/session_observer.h"
+#include "ash/style/icon_button.h"
 #include "ash/system/media/media_notification_provider_observer.h"
 #include "ash/system/tray/tray_background_view.h"
-#include "ash/system/unified/top_shortcut_button.h"
 
 class PrefChangeRegistrar;
 class PrefRegistrySimple;
@@ -39,7 +39,7 @@
 
   // Pin button showed in media tray bubble's title view and media controls
   // detailed view's title view.
-  class PinButton : public TopShortcutButton {
+  class PinButton : public IconButton {
    public:
     PinButton();
     ~PinButton() override = default;
diff --git a/ash/system/network/network_section_header_view.cc b/ash/system/network/network_section_header_view.cc
index 33e762b..aae428c 100644
--- a/ash/system/network/network_section_header_view.cc
+++ b/ash/system/network/network_section_header_view.cc
@@ -10,6 +10,7 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/icon_button.h"
 #include "ash/system/bluetooth/bluetooth_power_controller.h"
 #include "ash/system/model/system_tray_model.h"
 #include "ash/system/network/tray_network_state_model.h"
@@ -358,10 +359,10 @@
   can_add_esim_button_be_enabled_ = enabled;
   const gfx::VectorIcon& icon = base::i18n::IsRTL() ? kAddCellularNetworkRtlIcon
                                                     : kAddCellularNetworkIcon;
-  add_esim_button_ = new TopShortcutButton(
+  add_esim_button_ = new IconButton(
       base::BindRepeating(&MobileSectionHeaderView::AddCellularButtonPressed,
                           base::Unretained(this)),
-      icon, GetAddESimTooltipMessageId());
+      IconButton::Type::kSmall, icon, GetAddESimTooltipMessageId());
 
   add_esim_button_->SetEnabled(enabled && !IsCellularDeviceInhibited());
 
@@ -419,10 +420,11 @@
 }
 
 void WifiSectionHeaderView::AddExtraButtons(bool enabled) {
-  auto* join_button = new TopShortcutButton(
+  auto* join_button = new IconButton(
       base::BindRepeating(&WifiSectionHeaderView::JoinButtonPressed,
                           base::Unretained(this)),
-      vector_icons::kWifiAddIcon, IDS_ASH_STATUS_TRAY_OTHER_WIFI);
+      IconButton::Type::kSmall, vector_icons::kWifiAddIcon,
+      IDS_ASH_STATUS_TRAY_OTHER_WIFI);
   join_button->SetEnabled(enabled);
   container()->AddView(TriView::Container::END, join_button);
   join_button_ = join_button;
diff --git a/ash/system/network/network_section_header_view.h b/ash/system/network/network_section_header_view.h
index b00b78a..96ab31d7 100644
--- a/ash/system/network/network_section_header_view.h
+++ b/ash/system/network/network_section_header_view.h
@@ -9,7 +9,6 @@
 #include "ash/system/network/tray_network_state_observer.h"
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/system/tray/tri_view.h"
-#include "ash/system/unified/top_shortcut_button.h"
 #include "base/memory/weak_ptr.h"
 #include "base/timer/timer.h"
 #include "chromeos/services/network_config/public/mojom/cros_network_config.mojom-forward.h"
@@ -19,6 +18,7 @@
 
 namespace ash {
 
+class IconButton;
 class TrayNetworkStateModel;
 
 namespace tray {
@@ -132,7 +132,7 @@
 
   // Button that navigates to the Settings mobile data subpage with the eSIM
   // setup dialog open. This is null when the device is not eSIM-capable.
-  TopShortcutButton* add_esim_button_ = nullptr;
+  IconButton* add_esim_button_ = nullptr;
 
   // Indicates whether add_esim_button_ should be enabled when the device is
   // not inhibited.
@@ -164,7 +164,7 @@
   void JoinButtonPressed();
 
   // A button to invoke "Join Wi-Fi network" dialog.
-  views::Button* join_button_ = nullptr;
+  IconButton* join_button_ = nullptr;
 };
 
 }  // namespace tray
diff --git a/ash/system/palette/palette_tray.cc b/ash/system/palette/palette_tray.cc
index 7aa0433..674b3c0 100644
--- a/ash/system/palette/palette_tray.cc
+++ b/ash/system/palette/palette_tray.cc
@@ -18,6 +18,7 @@
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
+#include "ash/style/icon_button.h"
 #include "ash/system/model/system_tray_model.h"
 #include "ash/system/palette/palette_tool_manager.h"
 #include "ash/system/palette/palette_utils.h"
@@ -28,7 +29,6 @@
 #include "ash/system/tray/tray_container.h"
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/system/tray/tray_utils.h"
-#include "ash/system/unified/top_shortcut_button.h"
 #include "base/bind.h"
 #include "base/metrics/histogram_macros.h"
 #include "components/pref_registry/pref_registry_syncable.h"
@@ -168,22 +168,24 @@
           AshColorProvider::ContentLayerType::kSeparatorColor));
     }
 
-    help_button_ = AddChildView(std::make_unique<TopShortcutButton>(
+    help_button_ = AddChildView(std::make_unique<IconButton>(
         base::BindRepeating(
             &TitleView::ButtonPressed, base::Unretained(this),
             PaletteTrayOptions::PALETTE_HELP_BUTTON,
             base::BindRepeating(
                 &SystemTrayClient::ShowPaletteHelp,
                 base::Unretained(Shell::Get()->system_tray_model()->client()))),
-        kSystemMenuHelpIcon, IDS_ASH_STATUS_TRAY_HELP));
-    settings_button_ = AddChildView(std::make_unique<TopShortcutButton>(
+        IconButton::Type::kSmall, kSystemMenuHelpIcon,
+        IDS_ASH_STATUS_TRAY_HELP));
+    settings_button_ = AddChildView(std::make_unique<IconButton>(
         base::BindRepeating(
             &TitleView::ButtonPressed, base::Unretained(this),
             PaletteTrayOptions::PALETTE_SETTINGS_BUTTON,
             base::BindRepeating(
                 &SystemTrayClient::ShowPaletteSettings,
                 base::Unretained(Shell::Get()->system_tray_model()->client()))),
-        kSystemMenuSettingsIcon, IDS_ASH_PALETTE_SETTINGS));
+        IconButton::Type::kSmall, kSystemMenuSettingsIcon,
+        IDS_ASH_PALETTE_SETTINGS));
   }
 
   TitleView(const TitleView&) = delete;
diff --git a/ash/system/phonehub/phone_status_view.cc b/ash/system/phonehub/phone_status_view.cc
index 2beb275..fb181e01 100644
--- a/ash/system/phonehub/phone_status_view.cc
+++ b/ash/system/phonehub/phone_status_view.cc
@@ -12,6 +12,7 @@
 #include "ash/root_window_controller.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
+#include "ash/style/icon_button.h"
 #include "ash/system/phonehub/phone_hub_tray.h"
 #include "ash/system/phonehub/phone_hub_view_ids.h"
 #include "ash/system/status_area_widget.h"
@@ -134,10 +135,10 @@
   separator_->SetPreferredHeight(kSeparatorHeight);
   AddView(TriView::Container::CENTER, separator_);
 
-  settings_button_ = new TopShortcutButton(
+  settings_button_ = new IconButton(
       base::BindRepeating(&Delegate::OpenConnectedDevicesSettings,
                           base::Unretained(delegate)),
-      kSystemMenuSettingsIcon,
+      IconButton::Type::kSmall, kSystemMenuSettingsIcon,
       IDS_ASH_PHONE_HUB_CONNECTED_DEVICE_SETTINGS_LABEL);
   AddView(TriView::Container::END, settings_button_);
 
diff --git a/ash/system/phonehub/phone_status_view.h b/ash/system/phonehub/phone_status_view.h
index 736bcc3..0a7d7fd 100644
--- a/ash/system/phonehub/phone_status_view.h
+++ b/ash/system/phonehub/phone_status_view.h
@@ -9,7 +9,6 @@
 #include "ash/components/phonehub/phone_model.h"
 #include "ash/system/power/power_status.h"
 #include "ash/system/tray/tri_view.h"
-#include "ash/system/unified/top_shortcut_button.h"
 #include "base/gtest_prod_util.h"
 
 namespace views {
@@ -20,6 +19,8 @@
 
 namespace ash {
 
+class IconButton;
+
 // The header row at the top of the Phone Hub panel, showing phone title and
 // status (wifi, volime, etc.).
 class ASH_EXPORT PhoneStatusView : public TriView,
@@ -66,7 +67,7 @@
   views::ImageView* battery_icon_ = nullptr;
   views::Label* battery_label_ = nullptr;
   views::Separator* separator_ = nullptr;
-  TopShortcutButton* settings_button_ = nullptr;
+  IconButton* settings_button_ = nullptr;
 };
 
 }  // namespace ash
diff --git a/ash/system/phonehub/phone_status_view_unittest.cc b/ash/system/phonehub/phone_status_view_unittest.cc
index e7b4ab75..3b0d49ab9 100644
--- a/ash/system/phonehub/phone_status_view_unittest.cc
+++ b/ash/system/phonehub/phone_status_view_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "ash/components/phonehub/mutable_phone_model.h"
 #include "ash/constants/ash_features.h"
+#include "ash/style/icon_button.h"
 #include "ash/test/ash_test_base.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
diff --git a/ash/system/screen_layout_observer.cc b/ash/system/screen_layout_observer.cc
index 294d39c..e5bb4e1 100644
--- a/ash/system/screen_layout_observer.cc
+++ b/ash/system/screen_layout_observer.cc
@@ -20,7 +20,6 @@
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/system/model/system_tray_model.h"
 #include "ash/system/tray/tray_constants.h"
-#include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/bind.h"
 #include "base/containers/contains.h"
 #include "base/strings/string_util.h"
@@ -339,41 +338,6 @@
         return true;
       }
     }
-    // Don't show rotation change notification if
-    // a) no rotation change
-    if (iter.second.GetActiveRotation() == old_iter->second.GetActiveRotation())
-      continue;
-    // b) the source is accelerometer.
-    if (iter.second.active_rotation_source() ==
-        display::Display::RotationSource::ACCELEROMETER) {
-      continue;
-    }
-    // c) if the device is in tablet mode, and source is not user.
-    if (Shell::Get()->tablet_mode_controller()->InTabletMode() &&
-        iter.second.active_rotation_source() !=
-            display::Display::RotationSource::USER) {
-      continue;
-    }
-
-    int rotation_text_id = 0;
-    switch (iter.second.GetActiveRotation()) {
-      case display::Display::ROTATE_0:
-        rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_STANDARD_ORIENTATION;
-        break;
-      case display::Display::ROTATE_90:
-        rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90;
-        break;
-      case display::Display::ROTATE_180:
-        rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_180;
-        break;
-      case display::Display::ROTATE_270:
-        rotation_text_id = IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_270;
-        break;
-    }
-    *out_additional_message = l10n_util::GetStringFUTF16(
-        IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, GetDisplayName(iter.first),
-        l10n_util::GetStringUTF16(rotation_text_id));
-    return true;
   }
 
   // Found nothing special
@@ -391,8 +355,6 @@
   if (message.empty() && additional_message.empty())
     return;
 
-  // Don't display notifications for accelerometer triggered screen rotations.
-  // See http://crbug.com/364949
   if (Shell::Get()
           ->screen_orientation_controller()
           ->ignore_display_configuration_updates()) {
diff --git a/ash/system/screen_layout_observer_unittest.cc b/ash/system/screen_layout_observer_unittest.cc
index 3e68a02..abf8693 100644
--- a/ash/system/screen_layout_observer_unittest.cc
+++ b/ash/system/screen_layout_observer_unittest.cc
@@ -160,24 +160,6 @@
   display::Display::SetInternalDisplayId(display_manager()->first_display_id());
   EXPECT_TRUE(GetDisplayNotificationText().empty());
 
-  // rotation.
-  UpdateDisplay("500x400/r");
-  EXPECT_EQ(l10n_util::GetStringFUTF16(
-                IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, GetFirstDisplayName(),
-                l10n_util::GetStringUTF16(
-                    IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90)),
-            GetDisplayNotificationAdditionalText());
-  EXPECT_TRUE(GetDisplayNotificationText().empty());
-
-  CloseNotification();
-  UpdateDisplay("500x400");
-  EXPECT_EQ(l10n_util::GetStringFUTF16(
-                IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, GetFirstDisplayName(),
-                l10n_util::GetStringUTF16(
-                    IDS_ASH_STATUS_TRAY_DISPLAY_STANDARD_ORIENTATION)),
-            GetDisplayNotificationAdditionalText());
-  EXPECT_TRUE(GetDisplayNotificationText().empty());
-
   // No-update
   CloseNotification();
   UpdateDisplay("500x400");
@@ -277,15 +259,6 @@
             GetDisplayNotificationText());
   EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty());
 
-  // Rotate the second.
-  UpdateDisplay("500x400@1.5,300x200/r");
-  EXPECT_EQ(l10n_util::GetStringFUTF16(
-                IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, GetSecondDisplayName(),
-                l10n_util::GetStringUTF16(
-                    IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90)),
-            GetDisplayNotificationAdditionalText());
-  EXPECT_TRUE(GetDisplayNotificationText().empty());
-
   // Enters closed lid mode.
   UpdateDisplay("500x400@1.5,300x200");
   display::Display::SetInternalDisplayId(
@@ -305,10 +278,6 @@
   display::Display::SetInternalDisplayId(display_manager()->first_display_id());
   EXPECT_TRUE(GetDisplayNotificationText().empty());
 
-  // Rotation.
-  UpdateDisplay("500x400/r");
-  EXPECT_FALSE(IsNotificationShown());
-
   // Adding a display.
   UpdateDisplay("500x400,300x200");
   EXPECT_FALSE(IsNotificationShown());
@@ -430,20 +399,6 @@
       base::CompareCase::SENSITIVE));
 }
 
-// Verify the notification message content when one of the 2 displays that
-// connected to the device is rotated.
-TEST_F(ScreenLayoutObserverTest, UpdateAfterSuppressDisplayNotification) {
-  UpdateDisplay("500x400,300x200");
-
-  // Rotate the second.
-  UpdateDisplay("500x400,300x200/r");
-  EXPECT_EQ(l10n_util::GetStringFUTF16(
-                IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, GetSecondDisplayName(),
-                l10n_util::GetStringUTF16(
-                    IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_90)),
-            GetDisplayNotificationAdditionalText());
-}
-
 // Verify that no notification is shown when overscan of a screen is changed.
 TEST_F(ScreenLayoutObserverTest, OverscanDisplay) {
   UpdateDisplay("500x400, 400x300");
@@ -594,65 +549,6 @@
   EXPECT_FALSE(IsNotificationShown());
 }
 
-// Tests that rotation notifications are only shown when the rotation source is
-// a user action. The accelerometer source nevber produces any notifications.
-TEST_F(ScreenLayoutObserverTest, RotationNotification) {
-  UpdateDisplay("500x400");
-  const int64_t primary_id =
-      display_manager()->GetPrimaryDisplayCandidate().id();
-
-  // The accelerometer source.
-  display_manager()->SetDisplayRotation(
-      primary_id, display::Display::ROTATE_90,
-      display::Display::RotationSource::ACCELEROMETER);
-  EXPECT_FALSE(IsNotificationShown());
-
-  // The user source.
-  display_manager()->SetDisplayRotation(primary_id,
-                                        display::Display::ROTATE_180,
-                                        display::Display::RotationSource::USER);
-  EXPECT_EQ(l10n_util::GetStringFUTF16(
-                IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, GetFirstDisplayName(),
-                l10n_util::GetStringUTF16(
-                    IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_180)),
-            GetDisplayNotificationAdditionalText());
-
-  // The active source.
-  display_manager()->SetDisplayRotation(
-      primary_id, display::Display::ROTATE_270,
-      display::Display::RotationSource::ACTIVE);
-  EXPECT_EQ(l10n_util::GetStringFUTF16(
-                IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, GetFirstDisplayName(),
-                l10n_util::GetStringUTF16(
-                    IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_270)),
-            GetDisplayNotificationAdditionalText());
-
-  // Switch to Tablet
-  Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
-
-  // The accelerometer source.
-  display_manager()->SetDisplayRotation(
-      primary_id, display::Display::ROTATE_90,
-      display::Display::RotationSource::ACCELEROMETER);
-  EXPECT_TRUE(GetDisplayNotificationText().empty());
-
-  // The user source.
-  display_manager()->SetDisplayRotation(primary_id,
-                                        display::Display::ROTATE_180,
-                                        display::Display::RotationSource::USER);
-  EXPECT_EQ(l10n_util::GetStringFUTF16(
-                IDS_ASH_STATUS_TRAY_DISPLAY_ROTATED, GetFirstDisplayName(),
-                l10n_util::GetStringUTF16(
-                    IDS_ASH_STATUS_TRAY_DISPLAY_ORIENTATION_180)),
-            GetDisplayNotificationAdditionalText());
-
-  // The active source.
-  display_manager()->SetDisplayRotation(
-      primary_id, display::Display::ROTATE_270,
-      display::Display::RotationSource::ACTIVE);
-  EXPECT_TRUE(GetDisplayNotificationText().empty());
-}
-
 TEST_F(ScreenLayoutObserverTest, MirrorModeAddOrRemoveDisplayMessage) {
   const int64_t internal_display_id =
       display::test::DisplayManagerTestApi(display_manager())
@@ -721,12 +617,12 @@
 
 TEST_F(ScreenLayoutObserverTest, ClickNotification) {
   // Create notification.
-  UpdateDisplay("500x400/r");
-  EXPECT_FALSE(GetDisplayNotificationAdditionalText().empty());
+  UpdateDisplay("500x400,300x200");
+  EXPECT_FALSE(GetDisplayNotificationText().empty());
 
   // Click notification.
   ClickNotification();
-  EXPECT_TRUE(GetDisplayNotificationAdditionalText().empty());
+  EXPECT_TRUE(GetDisplayNotificationText().empty());
 }
 
 }  // namespace ash
diff --git a/ash/system/time/calendar_view.cc b/ash/system/time/calendar_view.cc
index d67a399..72d6a55b4 100644
--- a/ash/system/time/calendar_view.cc
+++ b/ash/system/time/calendar_view.cc
@@ -6,6 +6,7 @@
 
 #include "ash/public/cpp/ash_typography.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/icon_button.h"
 #include "ash/style/pill_button.h"
 #include "ash/system/time/calendar_month_view.h"
 #include "ash/system/time/calendar_utils.h"
@@ -70,24 +71,6 @@
     IDS_ASH_CALENDAR_SUN, IDS_ASH_CALENDAR_MON, IDS_ASH_CALENDAR_TUE,
     IDS_ASH_CALENDAR_WED, IDS_ASH_CALENDAR_THU, IDS_ASH_CALENDAR_FRI,
     IDS_ASH_CALENDAR_SAT};
-
-// The button on the header view.
-class HeaderButton : public TopShortcutButton {
- public:
-  HeaderButton(views::Button::PressedCallback callback,
-               const gfx::VectorIcon& icon,
-               int accessible_name_id)
-      : TopShortcutButton(std::move(callback), icon, accessible_name_id) {}
-  HeaderButton(const HeaderButton&) = delete;
-  HeaderButton& operator=(const HeaderButton&) = delete;
-  ~HeaderButton() override = default;
-
-  // Does not need the background color from `TopShortcutButton`.
-  void PaintButtonContents(gfx::Canvas* canvas) override {
-    views::ImageButton::PaintButtonContents(canvas);
-  }
-};
-
 // The overridden `Label` view used in `CalendarView`.
 class CalendarLabel : public views::Label {
  public:
@@ -284,15 +267,15 @@
                                                kContentHorizontalPadding));
   tri_view->AddView(TriView::Container::START, header_);
 
-  down_button_ = new HeaderButton(
+  down_button_ = new IconButton(
       base::BindRepeating(&CalendarView::ScrollOneMonthWithAnimation,
                           base::Unretained(this), /*is_scrolling_up=*/false),
-      vector_icons::kCaretDownIcon,
+      IconButton::Type::kSmallFloating, vector_icons::kCaretDownIcon,
       IDS_ASH_CALENDAR_DOWN_BUTTON_ACCESSIBLE_DESCRIPTION);
-  up_button_ = new HeaderButton(
+  up_button_ = new IconButton(
       base::BindRepeating(&CalendarView::ScrollOneMonthWithAnimation,
                           base::Unretained(this), /*is_scrolling_up=*/true),
-      vector_icons::kCaretUpIcon,
+      IconButton::Type::kSmallFloating, vector_icons::kCaretUpIcon,
       IDS_ASH_CALENDAR_UP_BUTTON_ACCESSIBLE_DESCRIPTION);
 
   tri_view->AddView(TriView::Container::END, down_button_);
diff --git a/ash/system/time/calendar_view.h b/ash/system/time/calendar_view.h
index afe9cb9..cf64138 100644
--- a/ash/system/time/calendar_view.h
+++ b/ash/system/time/calendar_view.h
@@ -28,6 +28,7 @@
 namespace ash {
 
 class CalendarMonthView;
+class IconButton;
 
 // The header of the calendar view, which shows the current month and year.
 class CalendarHeaderView : public views::View {
@@ -187,8 +188,8 @@
   CalendarHeaderView* header_ = nullptr;
   views::Button* reset_to_today_button_ = nullptr;
   views::Button* settings_button_ = nullptr;
-  TopShortcutButton* up_button_ = nullptr;
-  TopShortcutButton* down_button_ = nullptr;
+  IconButton* up_button_ = nullptr;
+  IconButton* down_button_ = nullptr;
 
   // If it `is_resetting_scroll_`, we don't calculate the scroll position and we
   // don't need to check if we need to update the month or not.
diff --git a/ash/system/time/calendar_view_unittest.cc b/ash/system/time/calendar_view_unittest.cc
index c8fb3f36..3cda9db 100644
--- a/ash/system/time/calendar_view_unittest.cc
+++ b/ash/system/time/calendar_view_unittest.cc
@@ -5,6 +5,7 @@
 #include "ash/system/time/calendar_view.h"
 
 #include "ash/shell.h"
+#include "ash/style/icon_button.h"
 #include "ash/system/time/calendar_month_view.h"
 #include "ash/system/time/calendar_view_controller.h"
 #include "ash/system/tray/detailed_view_delegate.h"
@@ -109,8 +110,8 @@
     return calendar_view_->reset_to_today_button_;
   }
   views::Button* settings_button() { return calendar_view_->settings_button_; }
-  TopShortcutButton* up_button() { return calendar_view_->up_button_; }
-  TopShortcutButton* down_button() { return calendar_view_->down_button_; }
+  IconButton* up_button() { return calendar_view_->up_button_; }
+  IconButton* down_button() { return calendar_view_->down_button_; }
 
   void ScrollUpOneMonth() { calendar_view_->ScrollUpOneMonthAndAutoScroll(); }
   void ScrollDownOneMonth() {
diff --git a/ash/system/tray/detailed_view_delegate.cc b/ash/system/tray/detailed_view_delegate.cc
index 8aff889..b17cb70 100644
--- a/ash/system/tray/detailed_view_delegate.cc
+++ b/ash/system/tray/detailed_view_delegate.cc
@@ -7,10 +7,10 @@
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
+#include "ash/style/icon_button.h"
 #include "ash/system/tray/hover_highlight_view.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_popup_utils.h"
-#include "ash/system/unified/top_shortcut_button.h"
 #include "ash/system/unified/unified_system_tray_controller.h"
 #include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -63,12 +63,13 @@
                        gfx::Size(0, kUnifiedDetailedViewTitleRowHeight));
 }
 
-class BackButton : public TopShortcutButton {
+class BackButton : public IconButton {
  public:
   BackButton(views::Button::PressedCallback callback)
-      : TopShortcutButton(std::move(callback),
-                          kUnifiedMenuExpandIcon,
-                          IDS_ASH_STATUS_TRAY_PREVIOUS_MENU) {}
+      : IconButton(std::move(callback),
+                   IconButton::Type::kSmallFloating,
+                   kUnifiedMenuExpandIcon,
+                   IDS_ASH_STATUS_TRAY_PREVIOUS_MENU) {}
   BackButton(const BackButton&) = delete;
   BackButton& operator=(const BackButton&) = delete;
   ~BackButton() override = default;
@@ -182,16 +183,16 @@
 views::Button* DetailedViewDelegate::CreateInfoButton(
     views::Button::PressedCallback callback,
     int info_accessible_name_id) {
-  return new TopShortcutButton(std::move(callback), kUnifiedMenuInfoIcon,
-                               info_accessible_name_id);
+  return new IconButton(std::move(callback), IconButton::Type::kSmall,
+                        kUnifiedMenuInfoIcon, info_accessible_name_id);
 }
 
 views::Button* DetailedViewDelegate::CreateSettingsButton(
     views::Button::PressedCallback callback,
     int setting_accessible_name_id) {
   auto* button =
-      new TopShortcutButton(std::move(callback), kUnifiedMenuSettingsIcon,
-                            setting_accessible_name_id);
+      new IconButton(std::move(callback), IconButton::Type::kSmall,
+                     kUnifiedMenuSettingsIcon, setting_accessible_name_id);
   if (!TrayPopupUtils::CanOpenWebUISettings())
     button->SetEnabled(false);
   return button;
@@ -200,8 +201,8 @@
 views::Button* DetailedViewDelegate::CreateHelpButton(
     views::Button::PressedCallback callback) {
   auto* button =
-      new TopShortcutButton(std::move(callback), vector_icons::kHelpOutlineIcon,
-                            IDS_ASH_STATUS_TRAY_HELP);
+      new IconButton(std::move(callback), IconButton::Type::kSmall,
+                     vector_icons::kHelpOutlineIcon, IDS_ASH_STATUS_TRAY_HELP);
   // Help opens a web page, so treat it like Web UI settings.
   if (!TrayPopupUtils::CanOpenWebUISettings())
     button->SetEnabled(false);
diff --git a/ash/system/unified/collapse_button.cc b/ash/system/unified/collapse_button.cc
index 4e729021..f729ee9 100644
--- a/ash/system/unified/collapse_button.cc
+++ b/ash/system/unified/collapse_button.cc
@@ -6,25 +6,18 @@
 
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/strings/grit/ash_strings.h"
-#include "ash/style/ash_color_provider.h"
-#include "ash/style/element_style.h"
-#include "ash/system/tray/tray_constants.h"
-#include "ash/system/tray/tray_popup_utils.h"
+#include "ash/style/icon_button.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/gfx/paint_vector_icon.h"
+#include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/gfx/scoped_canvas.h"
-#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
-#include "ui/views/animation/ink_drop_impl.h"
-#include "ui/views/controls/focus_ring.h"
-#include "ui/views/controls/highlight_path_generator.h"
 
 namespace ash {
 
 CollapseButton::CollapseButton(PressedCallback callback)
-    : ImageButton(std::move(callback)) {
-  views::InstallCircleHighlightPathGenerator(this);
-  TrayPopupUtils::ConfigureTrayPopupButton(this);
-}
+    : IconButton(std::move(callback),
+                 IconButton::Type::kSmallFloating,
+                 kUnifiedMenuExpandIcon,
+                 IDS_ASH_STATUS_TRAY_COLLAPSE) {}
 
 CollapseButton::~CollapseButton() = default;
 
@@ -38,10 +31,6 @@
   SchedulePaint();
 }
 
-gfx::Size CollapseButton::CalculatePreferredSize() const {
-  return gfx::Size(kTrayItemSize, kTrayItemSize);
-}
-
 void CollapseButton::PaintButtonContents(gfx::Canvas* canvas) {
   gfx::ScopedCanvas scoped(canvas);
   canvas->Translate(gfx::Vector2d(size().width() / 2, size().height() / 2));
@@ -50,16 +39,7 @@
   canvas->DrawImageInt(image, -image.width() / 2, -image.height() / 2);
 }
 
-const char* CollapseButton::GetClassName() const {
-  return "CollapseButton";
-}
-
-void CollapseButton::OnThemeChanged() {
-  views::ImageButton::OnThemeChanged();
-  element_style::DecorateFloatingIconButton(this, kUnifiedMenuExpandIcon);
-  views::FocusRing::Get(this)->SetColor(
-      AshColorProvider::Get()->GetControlsLayerColor(
-          AshColorProvider::ControlsLayerType::kFocusRingColor));
-}
+BEGIN_METADATA(CollapseButton, IconButton)
+END_METADATA
 
 }  // namespace ash
diff --git a/ash/system/unified/collapse_button.h b/ash/system/unified/collapse_button.h
index 239c048..a995aaa 100644
--- a/ash/system/unified/collapse_button.h
+++ b/ash/system/unified/collapse_button.h
@@ -5,15 +5,18 @@
 #ifndef ASH_SYSTEM_UNIFIED_COLLAPSE_BUTTON_H_
 #define ASH_SYSTEM_UNIFIED_COLLAPSE_BUTTON_H_
 
-#include "ui/views/controls/button/image_button.h"
+#include "ash/style/icon_button.h"
+#include "ui/base/metadata/metadata_header_macros.h"
 
 namespace ash {
 
 // The button with `kUnifiedMenuExpandIcon`. This button can be set as expanded
 // or collapsed through SetExpandedAmount and the icon will be rotated on the
 // `expanded_amount_`. Expanded is the default state.
-class CollapseButton : public views::ImageButton {
+class CollapseButton : public IconButton {
  public:
+  METADATA_HEADER(CollapseButton);
+
   explicit CollapseButton(PressedCallback callback);
 
   CollapseButton(const CollapseButton&) = delete;
@@ -24,11 +27,8 @@
   // Change the expanded state. The icon will change.
   void SetExpandedAmount(double expanded_amount);
 
-  // views::ImageButton:
-  gfx::Size CalculatePreferredSize() const override;
+  // IconButton:
   void PaintButtonContents(gfx::Canvas* canvas) override;
-  const char* GetClassName() const override;
-  void OnThemeChanged() override;
 
  private:
   double expanded_amount_ = 1.0;
diff --git a/ash/system/unified/top_shortcut_button.cc b/ash/system/unified/top_shortcut_button.cc
deleted file mode 100644
index aba140d..0000000
--- a/ash/system/unified/top_shortcut_button.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ash/system/unified/top_shortcut_button.h"
-
-#include "ash/style/ash_color_provider.h"
-#include "ash/style/element_style.h"
-#include "ash/system/tray/tray_popup_utils.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/base/metadata/metadata_impl_macros.h"
-#include "ui/gfx/canvas.h"
-#include "ui/gfx/paint_vector_icon.h"
-#include "ui/views/animation/flood_fill_ink_drop_ripple.h"
-#include "ui/views/animation/ink_drop_highlight.h"
-#include "ui/views/animation/ink_drop_impl.h"
-#include "ui/views/animation/ink_drop_mask.h"
-#include "ui/views/controls/focus_ring.h"
-#include "ui/views/controls/highlight_path_generator.h"
-#include "ui/views/view_class_properties.h"
-
-namespace ash {
-
-TopShortcutButton::TopShortcutButton(PressedCallback callback,
-                                     const gfx::VectorIcon& icon,
-                                     int accessible_name_id)
-    : views::ImageButton(std::move(callback)), icon_(icon) {
-  SetImageHorizontalAlignment(ALIGN_CENTER);
-  SetImageVerticalAlignment(ALIGN_MIDDLE);
-  if (accessible_name_id)
-    SetTooltipText(l10n_util::GetStringUTF16(accessible_name_id));
-  TrayPopupUtils::ConfigureTrayPopupButton(this);
-  views::InstallCircleHighlightPathGenerator(this);
-}
-
-TopShortcutButton::~TopShortcutButton() = default;
-
-void TopShortcutButton::PaintButtonContents(gfx::Canvas* canvas) {
-  cc::PaintFlags flags;
-  flags.setAntiAlias(true);
-  flags.setColor(AshColorProvider::Get()->GetControlsLayerColor(
-      AshColorProvider::ControlsLayerType::kControlBackgroundColorInactive));
-  flags.setStyle(cc::PaintFlags::kFill_Style);
-  canvas->DrawPath(views::GetHighlightPath(this), flags);
-
-  views::ImageButton::PaintButtonContents(canvas);
-}
-
-void TopShortcutButton::OnThemeChanged() {
-  views::ImageButton::OnThemeChanged();
-  element_style::DecorateSmallIconButton(this, icon_,
-                                         /*toggled_=*/false,
-                                         /*has_border=*/false);
-  views::FocusRing::Get(this)->SetColor(
-      AshColorProvider::Get()->GetControlsLayerColor(
-          AshColorProvider::ControlsLayerType::kFocusRingColor));
-  SchedulePaint();
-}
-
-BEGIN_METADATA(TopShortcutButton, views::ImageButton)
-END_METADATA
-
-}  // namespace ash
diff --git a/ash/system/unified/top_shortcut_button.h b/ash/system/unified/top_shortcut_button.h
deleted file mode 100644
index 5d51402..0000000
--- a/ash/system/unified/top_shortcut_button.h
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef ASH_SYSTEM_UNIFIED_TOP_SHORTCUT_BUTTON_H_
-#define ASH_SYSTEM_UNIFIED_TOP_SHORTCUT_BUTTON_H_
-
-#include "ui/base/metadata/metadata_header_macros.h"
-#include "ui/gfx/vector_icon_types.h"
-#include "ui/views/controls/button/image_button.h"
-
-namespace ash {
-
-// A button used in top shortcuts. Top shortcuts are small circular buttons
-// shown on top of the UnifiedSystemTrayView that allows quick access to
-// frequently used features e.g. lock screen, settings, and shutdown.
-class TopShortcutButton : public views::ImageButton {
- public:
-  METADATA_HEADER(TopShortcutButton);
-
-  TopShortcutButton(PressedCallback callback,
-                    const gfx::VectorIcon& icon,
-                    int accessible_name_id);
-
-  TopShortcutButton(const TopShortcutButton&) = delete;
-  TopShortcutButton& operator=(const TopShortcutButton&) = delete;
-
-  ~TopShortcutButton() override;
-
-  // views::ImageButton:
-  void PaintButtonContents(gfx::Canvas* canvas) override;
-  void OnThemeChanged() override;
-
- private:
-  const gfx::VectorIcon& icon_;
-};
-
-}  // namespace ash
-
-#endif  // ASH_SYSTEM_UNIFIED_TOP_SHORTCUT_BUTTON_H_
diff --git a/ash/system/unified/top_shortcuts_view.cc b/ash/system/unified/top_shortcuts_view.cc
index fb3609c..bdb44f2 100644
--- a/ash/system/unified/top_shortcuts_view.cc
+++ b/ash/system/unified/top_shortcuts_view.cc
@@ -15,11 +15,11 @@
 #include "ash/shutdown_controller_impl.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
+#include "ash/style/icon_button.h"
 #include "ash/style/pill_button.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/system/unified/collapse_button.h"
-#include "ash/system/unified/top_shortcut_button.h"
 #include "ash/system/unified/unified_system_tray_controller.h"
 #include "ash/system/unified/user_chooser_detailed_view_controller.h"
 #include "ash/system/unified/user_chooser_view.h"
@@ -202,27 +202,30 @@
   }
 
   bool reboot = shell->shutdown_controller()->reboot_on_shutdown();
-  power_button_ = new TopShortcutButton(
+
+  power_button_ = new IconButton(
       base::BindRepeating(&UnifiedSystemTrayController::HandlePowerAction,
                           base::Unretained(controller)),
-      kUnifiedMenuPowerIcon,
+      IconButton::Type::kSmall, kUnifiedMenuPowerIcon,
       reboot ? IDS_ASH_STATUS_TRAY_REBOOT : IDS_ASH_STATUS_TRAY_SHUTDOWN);
   power_button_->SetID(VIEW_ID_POWER_BUTTON);
   container_->AddChildView(power_button_);
 
   if (can_show_settings && can_lock_screen) {
-    lock_button_ = new TopShortcutButton(
+    lock_button_ = new IconButton(
         base::BindRepeating(&UnifiedSystemTrayController::HandleLockAction,
                             base::Unretained(controller)),
-        kUnifiedMenuLockIcon, IDS_ASH_STATUS_TRAY_LOCK);
+        IconButton::Type::kSmall, kUnifiedMenuLockIcon,
+        IDS_ASH_STATUS_TRAY_LOCK);
     container_->AddChildView(lock_button_);
   }
 
   if (can_show_settings) {
-    settings_button_ = new TopShortcutButton(
+    settings_button_ = new IconButton(
         base::BindRepeating(&UnifiedSystemTrayController::HandleSettingsAction,
                             base::Unretained(controller)),
-        kUnifiedMenuSettingsIcon, IDS_ASH_STATUS_TRAY_SETTINGS);
+        IconButton::Type::kSmall, kUnifiedMenuSettingsIcon,
+        IDS_ASH_STATUS_TRAY_SETTINGS);
     container_->AddChildView(settings_button_);
     local_state_pref_change_registrar_.Init(Shell::Get()->local_state());
     local_state_pref_change_registrar_.Add(
diff --git a/ash/system/unified/top_shortcuts_view.h b/ash/system/unified/top_shortcuts_view.h
index 74ef741..07e7b54c 100644
--- a/ash/system/unified/top_shortcuts_view.h
+++ b/ash/system/unified/top_shortcuts_view.h
@@ -19,7 +19,7 @@
 namespace ash {
 
 class CollapseButton;
-class TopShortcutButton;
+class IconButton;
 class TopShortcutsViewTest;
 class UnifiedSystemTrayController;
 
@@ -76,9 +76,9 @@
   views::Button* user_avatar_button_ = nullptr;
   views::Button* sign_out_button_ = nullptr;
   TopShortcutButtonContainer* container_ = nullptr;
-  TopShortcutButton* lock_button_ = nullptr;
-  TopShortcutButton* settings_button_ = nullptr;
-  TopShortcutButton* power_button_ = nullptr;
+  IconButton* lock_button_ = nullptr;
+  IconButton* settings_button_ = nullptr;
+  IconButton* power_button_ = nullptr;
   CollapseButton* collapse_button_ = nullptr;
 
   PrefChangeRegistrar local_state_pref_change_registrar_;
diff --git a/ash/system/unified/top_shortcuts_view_unittest.cc b/ash/system/unified/top_shortcuts_view_unittest.cc
index f93a20af..0fc0dbb 100644
--- a/ash/system/unified/top_shortcuts_view_unittest.cc
+++ b/ash/system/unified/top_shortcuts_view_unittest.cc
@@ -6,8 +6,8 @@
 
 #include "ash/constants/ash_pref_names.h"
 #include "ash/session/test_session_controller_client.h"
+#include "ash/style/icon_button.h"
 #include "ash/system/unified/collapse_button.h"
-#include "ash/system/unified/top_shortcut_button.h"
 #include "ash/system/unified/unified_system_tray_controller.h"
 #include "ash/system/unified/unified_system_tray_model.h"
 #include "ash/test/ash_test_base.h"
diff --git a/ash/system/unified/unified_slider_view.h b/ash/system/unified/unified_slider_view.h
index fdda64c..c0a0165 100644
--- a/ash/system/unified/unified_slider_view.h
+++ b/ash/system/unified/unified_slider_view.h
@@ -5,9 +5,8 @@
 #ifndef ASH_SYSTEM_UNIFIED_UNIFIED_SLIDER_VIEW_H_
 #define ASH_SYSTEM_UNIFIED_UNIFIED_SLIDER_VIEW_H_
 
-#include "ash/system/unified/top_shortcut_button.h"
 #include "ui/gfx/vector_icon_types.h"
-#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/slider.h"
 #include "ui/views/view.h"
 
diff --git a/ash/system/unified/user_chooser_view.cc b/ash/system/unified/user_chooser_view.cc
index 58bab84c..3d0d0c3 100644
--- a/ash/system/unified/user_chooser_view.cc
+++ b/ash/system/unified/user_chooser_view.cc
@@ -15,12 +15,12 @@
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
+#include "ash/style/icon_button.h"
 #include "ash/system/model/enterprise_domain_model.h"
 #include "ash/system/model/system_tray_model.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_popup_utils.h"
 #include "ash/system/tray/tri_view.h"
-#include "ash/system/unified/top_shortcut_button.h"
 #include "ash/system/unified/top_shortcuts_view.h"
 #include "ash/system/unified/user_chooser_detailed_view_controller.h"
 #include "base/bind.h"
@@ -126,9 +126,9 @@
 
   if (user_session->user_info.type == user_manager::USER_TYPE_GUEST) {
     // In guest mode, the user avatar is just a disabled button pod.
-    auto* image_view = new TopShortcutButton(views::Button::PressedCallback(),
-                                             kSystemMenuGuestIcon,
-                                             IDS_ASH_STATUS_TRAY_GUEST_LABEL);
+    auto* image_view = new IconButton(
+        views::Button::PressedCallback(), IconButton::Type::kSmall,
+        kSystemMenuGuestIcon, IDS_ASH_STATUS_TRAY_GUEST_LABEL);
     image_view->SetEnabled(false);
     return image_view;
   }
@@ -235,11 +235,11 @@
   AddChildView(capture_icon_);
 
   if (has_close_button) {
-    AddChildView(std::make_unique<TopShortcutButton>(
+    AddChildView(std::make_unique<IconButton>(
         base::BindRepeating(
             &UserChooserDetailedViewController::TransitionToMainView,
             base::Unretained(controller)),
-        views::kIcCloseIcon, IDS_APP_ACCNAME_CLOSE));
+        IconButton::Type::kSmall, views::kIcCloseIcon, IDS_APP_ACCNAME_CLOSE));
   }
 
   SetTooltipText(GetUserItemAccessibleString(user_index));
diff --git a/ash/webui/camera_app_ui/resources/strings/camera_strings.grd b/ash/webui/camera_app_ui/resources/strings/camera_strings.grd
index 3a8c790e..6623ee6 100644
--- a/ash/webui/camera_app_ui/resources/strings/camera_strings.grd
+++ b/ash/webui/camera_app_ui/resources/strings/camera_strings.grd
@@ -324,7 +324,7 @@
         Enable expert mode
       </message>
       <message desc="Label for expert mode option: enable full-sized video snapshot." name="IDS_EXPERT_ENABLE_FULL_SIZED_VIDEO_SNAPSHOT">
-        Enable full-sized video snaphost
+        Enable full-sized video snapshot
       </message>
       <message desc="Label for expert mode option: Show GIF recording option." name="IDS_EXPERT_SHOW_GIF_RECORDING_OPTION">
         Show GIF recording option
diff --git a/ash/webui/camera_app_ui/resources/strings/camera_strings_grd/IDS_EXPERT_ENABLE_FULL_SIZED_VIDEO_SNAPSHOT.png.sha1 b/ash/webui/camera_app_ui/resources/strings/camera_strings_grd/IDS_EXPERT_ENABLE_FULL_SIZED_VIDEO_SNAPSHOT.png.sha1
index 5944dd1..780bac5f 100644
--- a/ash/webui/camera_app_ui/resources/strings/camera_strings_grd/IDS_EXPERT_ENABLE_FULL_SIZED_VIDEO_SNAPSHOT.png.sha1
+++ b/ash/webui/camera_app_ui/resources/strings/camera_strings_grd/IDS_EXPERT_ENABLE_FULL_SIZED_VIDEO_SNAPSHOT.png.sha1
@@ -1 +1 @@
-7831444f3b73099f8a555f370d4ade8ba1e7f1f8
\ No newline at end of file
+8e12946db3c1f1c5cc8af7117cf73c59cf492366
\ No newline at end of file
diff --git a/ash/webui/personalization_app/personalization_app_ui.cc b/ash/webui/personalization_app/personalization_app_ui.cc
index db3a00c5..fbf5705 100644
--- a/ash/webui/personalization_app/personalization_app_ui.cc
+++ b/ash/webui/personalization_app/personalization_app_ui.cc
@@ -38,10 +38,6 @@
     if (ShouldIncludeResource(resource))
       source->AddResourcePath(resource.path, resource.id);
   }
-  // Mirror assert.m.js here so that it is accessible at the same path in
-  // trusted and untrusted context.
-  source->AddResourcePath("assert.m.js", IDR_WEBUI_JS_ASSERT_M_JS);
-
   source->AddResourcePath("test_loader.html", IDR_WEBUI_HTML_TEST_LOADER_HTML);
   source->AddResourcePath("test_loader.js", IDR_WEBUI_JS_TEST_LOADER_JS);
   source->AddResourcePath("test_loader_util.js",
@@ -90,7 +86,9 @@
         {"googlePhotosAlbumsTabLabel",
          IDS_PERSONALIZATION_APP_GOOGLE_PHOTOS_ALBUMS_TAB},
         {"googlePhotosPhotosTabLabel",
-         IDS_PERSONALIZATION_APP_GOOGLE_PHOTOS_PHOTOS_TAB}};
+         IDS_PERSONALIZATION_APP_GOOGLE_PHOTOS_PHOTOS_TAB},
+        {"googlePhotosZeroStateMessage",
+         IDS_PERSONALIZATION_APP_GOOGLE_PHOTOS_ZERO_STATE_MESSAGE}};
     source->AddLocalizedStrings(kGooglePhotosLocalizedStrings);
   }
 
diff --git a/ash/webui/personalization_app/resources/BUILD.gn b/ash/webui/personalization_app/resources/BUILD.gn
index cc20a82de..4f02bee 100644
--- a/ash/webui/personalization_app/resources/BUILD.gn
+++ b/ash/webui/personalization_app/resources/BUILD.gn
@@ -16,9 +16,9 @@
 # `static_resource_files` are non-js files, e.g. image, html, css
 static_js_files = [
   "common/constants.ts",
-  "common/iframe_api.js",
   "common/utils.js",
 
+  "trusted/iframe_api.ts",
   "trusted/mojo_interface_provider.ts",
   "trusted/personalization_actions.js",
   "trusted/personalization_app.js",
@@ -30,6 +30,7 @@
   "trusted/personalization_test_api.js",
   "trusted/utils.js",
 
+  "untrusted/iframe_api.ts",
   "untrusted/setup.js",
 ]
 
@@ -95,12 +96,22 @@
       [ "$target_gen_dir/$preprocess_folder/trusted/{{source_file_part}}" ]
 }
 
+# This is to work around the problem untrusted/ cannot access shared resources
+# in chrome://. In the future when we merge trusted/ and untrusted/ we should
+# remove this.
+copy("copy_js_to_common") {
+  deps = [ "//ui/webui/resources/js:modulize_local" ]
+  sources = [ "$root_gen_dir/ui/webui/resources/js/assert.m.js" ]
+  outputs = [ "$target_gen_dir/$preprocess_folder/common/{{source_file_part}}" ]
+}
+
 ts_library("build_ts") {
   root_dir = "$target_gen_dir/$preprocess_folder"
   out_dir = "$target_gen_dir/tsc"
   tsconfig_base = "tsconfig_base.json"
 
   in_files = static_js_files + polymer_js_files + [
+               "common/assert.m.js",
                "trusted/personalization_app.mojom-webui.js",
                "untrusted/untrusted_shared_vars_css.js",
              ]
@@ -114,6 +125,7 @@
   definitions = []
 
   extra_deps = [
+    ":copy_js_to_common",
     ":copy_mojo_to_trusted",
     ":preprocess",
     ":preprocess_generated",
diff --git a/ash/webui/personalization_app/resources/common/constants.ts b/ash/webui/personalization_app/resources/common/constants.ts
index dc01671..7430095 100644
--- a/ash/webui/personalization_app/resources/common/constants.ts
+++ b/ash/webui/personalization_app/resources/common/constants.ts
@@ -11,7 +11,7 @@
 
 import {Url} from 'chrome://resources/mojo/url/mojom/url.mojom-webui.js';
 
-import {WallpaperCollection, WallpaperImage} from '../trusted/personalization_app.mojom-webui.js';
+import {WallpaperCollection} from '../trusted/personalization_app.mojom-webui.js';
 
 export const untrustedOrigin = 'chrome-untrusted://personalization';
 
@@ -27,7 +27,7 @@
   SELECT_GOOGLE_PHOTOS_COLLECTION = 'select_google_photos_collection',
   SELECT_LOCAL_COLLECTION = 'select_local_collection',
   SEND_IMAGE_COUNTS = 'send_image_counts',
-  SEND_IMAGES = 'send_images',
+  SEND_IMAGE_TILES = 'send_image_tiles',
   SEND_LOCAL_IMAGE_DATA = 'send_local_image_data',
   SEND_LOCAL_IMAGES = 'send_local_images',
   SEND_CURRENT_WALLPAPER_ASSET_ID = 'send_current_wallpaper_asset_id',
@@ -37,68 +37,39 @@
   SEND_VISIBLE = 'send_visible',
 }
 
-type BaseEvent = {
-  type: EventType,
-};
-
-export type SendCollectionsEvent = BaseEvent&{
+export type SendCollectionsEvent = {
+  type: EventType.SEND_COLLECTIONS,
   collections: WallpaperCollection[],
 };
 
-export type SendGooglePhotosCountEvent = BaseEvent&{
-  count?: number,
+export type SendGooglePhotosCountEvent = {
+  type: EventType.SEND_GOOGLE_PHOTOS_COUNT,
+  count: number|null,
 };
 
-export type SendGooglePhotosPhotosEvent = BaseEvent&{
-  photos?: any[],
+export type SendGooglePhotosPhotosEvent = {
+  type: EventType.SEND_GOOGLE_PHOTOS_PHOTOS,
+  photos: any[]|null,
 };
 
-export type SelectCollectionEvent = BaseEvent&{
+export type SelectCollectionEvent = {
+  type: EventType.SELECT_COLLECTION,
   collectionId: string,
 };
 
-export type SelectGooglePhotosCollectionEvent = BaseEvent;
+export type SelectGooglePhotosCollectionEvent = {
+  type: EventType.SELECT_GOOGLE_PHOTOS_COLLECTION,
+};
 
-export type SelectLocalCollectionEvent = BaseEvent;
+export type SelectLocalCollectionEvent = {
+  type: EventType.SELECT_LOCAL_COLLECTION,
+};
 
-export type SendImageCountsEvent = BaseEvent&{
+export type SendImageCountsEvent = {
+  type: EventType.SEND_IMAGE_COUNTS,
   counts: {[key: string]: number},
 };
 
-export type SendImagesEvent = BaseEvent&{
-  images: WallpaperImage[],
-};
-
-export type SendLocalImagesEvent = BaseEvent&{
-  images: FilePath[],
-};
-
-/**
- * Sends local image data keyed by stringified local image path.
- */
-export type SendLocalImageDataEvent = BaseEvent&{
-  data: {[key: string]: string},
-};
-
-export type SendCurrentWallpaperAssetIdEvent = BaseEvent&{
-  assetId?: bigint,
-};
-
-export type SendPendingWallpaperAssetIdEvent = BaseEvent&{
-  assetId?: bigint,
-};
-
-export type SelectImageEvent = BaseEvent&{
-  assetId: bigint,
-};
-
-/**
- * Notify an iframe if its visible state changes.
- */
-export type SendVisibleEvent = BaseEvent&{
-  visible: boolean,
-};
-
 /**
  * A displayable type constructed from WallpaperImages to display them as a
  * single unit. e.g. Dark/Light wallpaper images.
@@ -109,3 +80,51 @@
   unitId: bigint,
   preview: Url[],
 };
+
+export type SendImageTilesEvent = {
+  type: EventType.SEND_IMAGE_TILES,
+  tiles: ImageTile[],
+};
+
+export type SendLocalImagesEvent = {
+  type: EventType.SEND_LOCAL_IMAGES,
+  images: FilePath[],
+};
+
+/**
+ * Sends local image data keyed by stringified local image path.
+ */
+export type SendLocalImageDataEvent = {
+  type: EventType.SEND_LOCAL_IMAGE_DATA,
+  data: {[key: string]: string},
+};
+
+export type SendCurrentWallpaperAssetIdEvent = {
+  type: EventType.SEND_CURRENT_WALLPAPER_ASSET_ID,
+  assetId?: bigint,
+};
+
+export type SendPendingWallpaperAssetIdEvent = {
+  type: EventType.SEND_PENDING_WALLPAPER_ASSET_ID,
+  assetId?: bigint,
+};
+
+export type SelectImageEvent = {
+  type: EventType.SELECT_IMAGE,
+  assetId: bigint,
+};
+
+/**
+ * Notify an iframe if its visible state changes.
+ */
+export type SendVisibleEvent = {
+  type: EventType.SEND_VISIBLE,
+  visible: boolean,
+};
+
+export type Events = SendCollectionsEvent|SendGooglePhotosCountEvent|
+    SendGooglePhotosPhotosEvent|SelectCollectionEvent|
+    SelectGooglePhotosCollectionEvent|SelectLocalCollectionEvent|
+    SendImageCountsEvent|SendImageTilesEvent|SendLocalImagesEvent|
+    SendLocalImageDataEvent|SendCurrentWallpaperAssetIdEvent|
+    SendPendingWallpaperAssetIdEvent|SelectImageEvent|SendVisibleEvent;
diff --git a/ash/webui/personalization_app/resources/common/iframe_api.js b/ash/webui/personalization_app/resources/common/iframe_api.js
deleted file mode 100644
index 693ad03..0000000
--- a/ash/webui/personalization_app/resources/common/iframe_api.js
+++ /dev/null
@@ -1,275 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {assert, assertNotReached} from '/assert.m.js';
-import {EventType, ImageTile, SelectCollectionEvent, SelectGooglePhotosCollectionEvent, SelectImageEvent, SelectLocalCollectionEvent, SendCollectionsEvent, SendCurrentWallpaperAssetIdEvent, SendGooglePhotosCountEvent, SendGooglePhotosPhotosEvent, SendImageCountsEvent, SendImageTilesEvent, SendLocalImageDataEvent, SendLocalImagesEvent, SendPendingWallpaperAssetIdEvent, SendVisibleEvent, trustedOrigin, untrustedOrigin} from './constants.js';
-import {isNonEmptyArray, isNullOrArray, isNullOrNumber} from './utils.js';
-
-/**
- * @fileoverview Helper functions for communicating between trusted and
- * untrusted. All trusted <-> untrusted communication must happen through the
- * functions in this file.
- */
-
-/****************** Trusted -> Untrusted **************************************/
-
-/**
- * Send an array of wallpaper collections to untrusted.
- * @param {!Object} target the untrusted iframe window to send the message to.
- * @param {!Array<!WallpaperCollection>}
- *     collections
- */
-export function sendCollections(target, collections) {
-  /** @type {!SendCollectionsEvent} */
-  const event = {type: EventType.SEND_COLLECTIONS, collections};
-  target.postMessage(event, untrustedOrigin);
-}
-
-/**
- * Sends the count of Google Photos photos to untrusted.
- * @param {!Window} target the untrusted iframe window to send the message to.
- * @param {?number} count
- */
-export function sendGooglePhotosCount(target, count) {
-  /** @type {!SendGooglePhotosCountEvent} */
-  const event = {type: EventType.SEND_GOOGLE_PHOTOS_COUNT, count};
-  target.postMessage(event, untrustedOrigin);
-}
-
-/**
- * Sends the list of Google Photos photos to untrusted.
- * @param {!Window} target the untrusted iframe window to send the message to.
- * @param {?Array<undefined>} photos
- */
-export function sendGooglePhotosPhotos(target, photos) {
-  /** @type {!SendGooglePhotosPhotosEvent} */
-  const event = {type: EventType.SEND_GOOGLE_PHOTOS_PHOTOS, photos};
-  target.postMessage(event, untrustedOrigin);
-}
-
-/**
- * Send a mapping of collectionId to the number of images in that collection.
- * A value of null for a given collection id represents that the collection
- * failed to load.
- * @param {!Window} target
- * @param {!Object<string, ?number>} counts
- */
-export function sendImageCounts(target, counts) {
-  /** @type {!SendImageCountsEvent} */
-  const event = {type: EventType.SEND_IMAGE_COUNTS, counts};
-  target.postMessage(event, untrustedOrigin);
-}
-
-/**
- * Send visibility status to a target iframe. Currently used to trigger a
- * resize event on iron-list when an iframe becomes visible again so that
- * iron-list will run layout with the current size.
- * @param {!Window} target
- * @param {boolean} visible
- */
-export function sendVisible(target, visible) {
-  /** @type {!SendVisibleEvent} */
-  const event = {type: EventType.SEND_VISIBLE, visible};
-  target.postMessage(event, untrustedOrigin);
-}
-
-/**
- * Send an array of wallpaper tiles to chrome-untrusted://.
- * Will clear the page if images is empty array.
- * @param {!Window} target the iframe window to send the message to.
- * @param {!Array<!ImageTile>} tiles
- */
-export function sendImageTiles(target, tiles) {
-  /** @type {!SendImageTilesEvent} */
-  const event = {type: EventType.SEND_IMAGE_TILES, tiles};
-  target.postMessage(event, untrustedOrigin);
-}
-
-/**
- * Send an array of local images to chrome-untrusted://.
- * @param {!Window} target the iframe window to send the message to.
- * @param {!Array<!mojoBase.mojom.FilePath>} images
- */
-export function sendLocalImages(target, images) {
-  /** @type {!SendLocalImagesEvent} */
-  const event = {type: EventType.SEND_LOCAL_IMAGES, images};
-  target.postMessage(event, untrustedOrigin);
-}
-
-/**
- * Sends image data keyed by stringified image id.
- * @param {!Window} target
- * @param {!Object<string, string>} data
- */
-export function sendLocalImageData(target, data) {
-  /** @type {!SendLocalImageDataEvent} */
-  const event = {type: EventType.SEND_LOCAL_IMAGE_DATA, data};
-  target.postMessage(event, untrustedOrigin);
-}
-
-/**
- * Send the |assetId| of the currently selected wallpaper to |target| iframe
- * window. Sending null indicates that no image is selected.
- * @param {!Window} target
- * @param {?bigint} assetId
- */
-export function sendCurrentWallpaperAssetId(target, assetId) {
-  /** @type {!SendCurrentWallpaperAssetIdEvent} */
-  const event = {type: EventType.SEND_CURRENT_WALLPAPER_ASSET_ID, assetId};
-  target.postMessage(event, untrustedOrigin);
-}
-
-/**
- * Send the |assetId| to the |target| iframe when the user clicks on online
- * wallpaper image.
- * @param {!Window} target
- * @param {?bigint} assetId
- */
-export function sendPendingWallpaperAssetId(target, assetId) {
-  /** @type {!SendPendingWallpaperAssetIdEvent} */
-  const event = {type: EventType.SEND_PENDING_WALLPAPER_ASSET_ID, assetId};
-  target.postMessage(event, untrustedOrigin);
-}
-
-/**
- * Called from trusted code to validate that a received postMessage event
- * contains valid data. Ignores messages that are not of the expected type.
- * @param {!Event} event from untrusted to select a collection or image
- * @param {Array<!T>} choices array of valid objects to pick from
- * @return {!T}
- * @template T
- */
-export function validateReceivedSelection(event, choices) {
-  assert(isNonEmptyArray(choices), 'choices must be a non-empty array');
-
-  /** @type {SelectCollectionEvent|SelectImageEvent} */
-  const data = event.data;
-  let selected;
-  switch (data.type) {
-    case EventType.SELECT_COLLECTION:
-      assert(!!data.collectionId, 'Expected a collection id parameter');
-      selected = choices.find(choice => choice.id === data.collectionId);
-      break;
-    case EventType.SELECT_IMAGE:
-      assert(
-          data.hasOwnProperty('assetId'),
-          'Expected an image assetId parameter');
-      assert(
-          typeof data.assetId === 'bigint', 'assetId parameter must be bigint');
-      selected = choices.find(choice => choice.assetId === data.assetId);
-      break;
-    default:
-      assertNotReached('Unknown event type');
-  }
-
-  assert(!!selected, 'No valid selection found in choices');
-  return selected;
-}
-
-/****************** Untrusted -> Trusted **************************************/
-
-/**
- * Select a collection. Sent from untrusted to trusted.
- * @param {!Object} target the window object to post the message to.
- * @param {!string} collectionId the selected collection id.
- */
-export function selectCollection(target, collectionId) {
-  /** @type {!SelectCollectionEvent} */
-  const event = {type: EventType.SELECT_COLLECTION, collectionId};
-  target.postMessage(event, trustedOrigin);
-}
-
-/**
- * Select the Google Photos collection. Sent from untrusted to trusted.
- * @param {!Object} target the window object to post the message to.
- */
-export function selectGooglePhotosCollection(target) {
-  /** @type {!SelectGooglePhotosCollectionEvent} */
-  const event = {type: EventType.SELECT_GOOGLE_PHOTOS_COLLECTION};
-  target.postMessage(event, trustedOrigin);
-}
-
-/**
- * Select the local collection. Sent from untrusted to trusted.
- * @param {!Object} target the window object to post the message to.
- */
-export function selectLocalCollection(target) {
-  /** @type {!SelectLocalCollectionEvent} */
-  const event = {type: EventType.SELECT_LOCAL_COLLECTION};
-  target.postMessage(event, trustedOrigin);
-}
-
-/**
- * Select an image. Sent from untrusted to trusted.
- * @param {!Object} target the window to post the message to.
- * @param {!bigint} assetId the selected image assetId.
- */
-export function selectImage(target, assetId) {
-  /** @type {!SelectImageEvent} */
-  const event = {type: EventType.SELECT_IMAGE, assetId};
-  target.postMessage(event, trustedOrigin);
-}
-
-/**
- * Called from untrusted code to validate that a received event is of an
- * expected type and contains the expected data.
- * @param {!Event} event
- * @param {!EventType} expectedEventType
- * @return {?T}
- * @template T
- */
-export function validateReceivedData(event, expectedEventType) {
-  assert(
-      event.origin === trustedOrigin, 'Message is not from the correct origin');
-  assert(
-      event.data.type === expectedEventType,
-      `Expected event type: ${expectedEventType}`);
-
-  /**
-   * @type {
-   *   SendCollectionsEvent|
-   *   SendGooglePhotosCountEvent|
-   *   SendGooglePhotosPhotosEvent|
-   *   SendImageTilesEvent|
-   *   SendCurrentWallpaperAssetIdEvent|
-   *   SendPendingWallpaperAssetIdEvent|
-   *   SendLocalImagesEvent|
-   *   SendLocalImageDataEvent|
-   *   SendVisibleEvent
-   * }
-   */
-  const data = event.data;
-  switch (data.type) {
-    case EventType.SEND_COLLECTIONS:
-      assert(isNonEmptyArray(data.collections), 'Expected collections array');
-      return data.collections;
-    case EventType.SEND_GOOGLE_PHOTOS_COUNT:
-      assert(isNullOrNumber(data.count), 'Expected photos count');
-      return data.count;
-    case EventType.SEND_GOOGLE_PHOTOS_PHOTOS:
-      assert(isNullOrArray(data.photos), 'Expected photos array');
-      return data.photos;
-    case EventType.SEND_LOCAL_IMAGE_DATA:
-      assert(typeof data.data === 'object', 'Expected data object');
-      return data.data;
-    case EventType.SEND_LOCAL_IMAGES:
-      // Images array may be empty.
-      assert(Array.isArray(data.images), 'Expected images array');
-      return data.images;
-    case EventType.SEND_IMAGE_TILES:
-      // tiles array may be empty.
-      assert(Array.isArray(data.tiles), 'Expected images array');
-      return data.tiles;
-    case EventType.SEND_CURRENT_WALLPAPER_ASSET_ID:
-    case EventType.SEND_PENDING_WALLPAPER_ASSET_ID:
-      assert(data.assetId === null || typeof data.assetId === 'bigint');
-      return data.assetId;
-    case EventType.SEND_VISIBLE:
-      assert(typeof data.visible === 'boolean');
-      return data.visible;
-    default:
-      assertNotReached('Unknown event type');
-  }
-  return null;
-}
diff --git a/ash/webui/personalization_app/resources/trusted/google_photos_element.html b/ash/webui/personalization_app/resources/trusted/google_photos_element.html
index df68df19..a334138 100644
--- a/ash/webui/personalization_app/resources/trusted/google_photos_element.html
+++ b/ash/webui/personalization_app/resources/trusted/google_photos_element.html
@@ -64,6 +64,32 @@
     display: flex;
     justify-content: center;
   }
+
+  #zeroState {
+    align-items: center;
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    justify-content: center;
+    width: 100%;
+  }
+
+  #zeroStateImage {
+    margin-bottom: 16px;
+    width: 180px;
+  }
+
+  #zeroStateText {
+    color: var(--cros-text-color-secondary);
+    font: var(--cros-body-1-font);
+    max-width: 236px;
+    text-align: center;
+  }
+
+  a {
+    color: var(--cros-link-color);
+    text-decoration: none;
+  }
 </style>
 <main id="main" aria-label$="[[i18n('googlePhotosLabel')]]" tabindex="-1">
   <template is="dom-if" if="[[!isAlbumsEmpty_(albums_)]]">
@@ -78,23 +104,25 @@
       </cr-button>
     </div>
   </template>
-  <div id="photosContent" hidden$="[[!isPhotosTabSelected_(tab_)]]">
-    <iron-list id="photosGrid" items="[[photosByRow_]]" as="row">
-      <template>
-        <div class="row" rowindex$="[[index]]" tabindex$="[[tabIndex]]"
-          on-focus="onPhotosGridRowFocused_"
-          on-keydown="onPhotosGridRowKeyDown_">
-          <template is="dom-repeat" items="[[row]]" as="photo">
-            <personalization-grid-item>
-              <div class="photo" colindex$="[[index]]" tabindex="-1">
-                [[photo]]
-              </div>
-            </personalization-grid-item>
-          </template>
-        </div>
-      </template>
-    </iron-list>
-  </div>
+  <template is="dom-if" if="[[!isPhotosEmpty_(photos_)]]">
+    <div id="photosContent" hidden$="[[!isPhotosTabSelected_(tab_)]]">
+      <iron-list id="photosGrid" items="[[photosByRow_]]" as="row">
+        <template>
+          <div class="row" rowindex$="[[index]]" tabindex$="[[tabIndex]]"
+            on-focus="onPhotosGridRowFocused_"
+            on-keydown="onPhotosGridRowKeyDown_">
+            <template is="dom-repeat" items="[[row]]" as="photo">
+              <personalization-grid-item>
+                <div class="photo" colindex$="[[index]]" tabindex="-1">
+                  [[photo]]
+                </div>
+              </personalization-grid-item>
+            </template>
+          </div>
+        </template>
+      </iron-list>
+    </div>
+  </template>
   <template is="dom-if" if="[[!isAlbumsEmpty_(albums_)]]">
     <div id="albumsContent" hidden$="[[!isAlbumsTabSelected_(tab_)]]">
       <iron-list id="albumsGrid" items="[[albums_]]" as="album" grid>
@@ -111,4 +139,10 @@
       </iron-list>
     </div>
   </template>
+  <template is="dom-if" if="[[isPhotosEmpty_(photos_)]]">
+    <div id="zeroState">
+      <img id="zeroStateImage" src="//personalization/common/no_images.svg" aria-hidden="true">
+      <div id="zeroStateText" inner-h-t-m-l="[[getZeroStateMessage_()]]"></div>
+    </div>
+  </template>
 </main>
diff --git a/ash/webui/personalization_app/resources/trusted/google_photos_element.js b/ash/webui/personalization_app/resources/trusted/google_photos_element.js
index 8437fe5..5621246 100644
--- a/ash/webui/personalization_app/resources/trusted/google_photos_element.js
+++ b/ash/webui/personalization_app/resources/trusted/google_photos_element.js
@@ -11,7 +11,7 @@
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import './styles.js';
 import '../common/styles.js';
-import {assert, assertNotReached} from '/assert.m.js';
+import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
 import {afterNextRender, html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {getNumberOfGridItemsPerRow, isNonEmptyArray, isSelectionEvent, normalizeKeyForRTL} from '../common/utils.js';
 import {getWallpaperProvider} from './mojo_interface_provider.js';
@@ -311,6 +311,19 @@
   }
 
   /**
+   * Creates the html for the Google Photos link in zero state.
+   * @return {string}
+   * @private
+   */
+  getZeroStateMessage_() {
+    return this.i18nAdvanced('googlePhotosZeroStateMessage', {
+      substitutions: [
+        '<a target="_blank" href="https://photos.google.com">photos.google.com</a>'
+      ]
+    });
+  }
+
+  /**
    * Invalidates the grid for the currently selected tab to force relayout.
    * @private
    */
@@ -318,11 +331,11 @@
     switch (this.tab_) {
       case Tab.Albums:
         // Firing 'iron-resize' event forces relayout of 'iron-list'.
-        this.shadowRoot.querySelector('#albumsGrid').fire('iron-resize');
+        this.shadowRoot.querySelector('#albumsGrid')?.fire('iron-resize');
         return;
       case Tab.Photos:
         // Firing 'iron-resize' event forces relayout of 'iron-list'.
-        this.shadowRoot.querySelector('#photosGrid').fire('iron-resize');
+        this.shadowRoot.querySelector('#photosGrid')?.fire('iron-resize');
         return;
       default:
         assertNotReached();
@@ -349,6 +362,15 @@
   }
 
   /**
+   * Whether the list of photos is empty.
+   * @return {boolean}
+   * @private
+   */
+  isPhotosEmpty_() {
+    return !isNonEmptyArray(this.photos_);
+  }
+
+  /**
    * Whether the photos tab is currently selected.
    * @return {boolean}
    * @private
diff --git a/ash/webui/personalization_app/resources/trusted/iframe_api.ts b/ash/webui/personalization_app/resources/trusted/iframe_api.ts
new file mode 100644
index 0000000..b54997c
--- /dev/null
+++ b/ash/webui/personalization_app/resources/trusted/iframe_api.ts
@@ -0,0 +1,182 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Helper functions for communicating between trusted and
+ * untrusted. All trusted -> untrusted communication must happen through the
+ * functions in this file.
+ */
+
+import {assert, assertNotReached} from 'chrome://resources/js/assert.m.js';
+import {FilePath} from 'chrome://resources/mojo/mojo/public/mojom/base/file_path.mojom-webui.js';
+
+import * as constants from '../common/constants.js';
+import {isNonEmptyArray} from '../common/utils.js';
+
+import {WallpaperCollection, WallpaperImage} from './personalization_app.mojom-webui.js';
+
+
+/**
+ * Send an array of wallpaper collections to untrusted.
+ */
+export function sendCollections(
+    target: Window, collections: Array<WallpaperCollection>) {
+  const event: constants.SendCollectionsEvent = {
+    type: constants.EventType.SEND_COLLECTIONS,
+    collections
+  };
+  target.postMessage(event, constants.untrustedOrigin);
+}
+
+/**
+ * Sends the count of Google Photos photos to untrusted.
+ */
+export function sendGooglePhotosCount(target: Window, count: number|null) {
+  const event: constants.SendGooglePhotosCountEvent = {
+    type: constants.EventType.SEND_GOOGLE_PHOTOS_COUNT,
+    count
+  };
+  target.postMessage(event, constants.untrustedOrigin);
+}
+
+/**
+ * Sends the list of Google Photos photos to untrusted.
+ */
+export function sendGooglePhotosPhotos(
+    target: Window, photos: Array<any>|null) {
+  const event: constants.SendGooglePhotosPhotosEvent = {
+    type: constants.EventType.SEND_GOOGLE_PHOTOS_PHOTOS,
+    photos
+  };
+  target.postMessage(event, constants.untrustedOrigin);
+}
+
+/**
+ * Send a mapping of collectionId to the number of images in that collection.
+ * A value of null for a given collection id represents that the collection
+ * failed to load.
+ */
+export function sendImageCounts(
+    target: Window, counts: {[key: string]: number}) {
+  const event: constants.SendImageCountsEvent = {
+    type: constants.EventType.SEND_IMAGE_COUNTS,
+    counts
+  };
+  target.postMessage(event, constants.untrustedOrigin);
+}
+
+/**
+ * Send visibility status to a target iframe. Currently used to trigger a
+ * resize event on iron-list when an iframe becomes visible again so that
+ * iron-list will run layout with the current size.
+ */
+export function sendVisible(target: Window, visible: boolean) {
+  const event: constants.SendVisibleEvent = {
+    type: constants.EventType.SEND_VISIBLE,
+    visible
+  };
+  target.postMessage(event, constants.untrustedOrigin);
+}
+
+/**
+ * Send an array of wallpaper images to chrome-untrusted://.
+ * Will clear the page if images is empty array.
+ */
+export function sendImageTiles(target: Window, tiles: constants.ImageTile[]) {
+  const event: constants.SendImageTilesEvent = {
+    type: constants.EventType.SEND_IMAGE_TILES,
+    tiles
+  };
+  target.postMessage(event, constants.untrustedOrigin);
+}
+
+/**
+ * Send an array of local images to chrome-untrusted://.
+ */
+export function sendLocalImages(target: Window, images: FilePath[]) {
+  const event: constants.SendLocalImagesEvent = {
+    type: constants.EventType.SEND_LOCAL_IMAGES,
+    images
+  };
+  target.postMessage(event, constants.untrustedOrigin);
+}
+
+/**
+ * Sends image data keyed by stringified image id.
+ */
+export function sendLocalImageData(
+    target: Window, data: {[key: string]: string}) {
+  const event: constants.SendLocalImageDataEvent = {
+    type: constants.EventType.SEND_LOCAL_IMAGE_DATA,
+    data
+  };
+  target.postMessage(event, constants.untrustedOrigin);
+}
+
+/**
+ * Send the |assetId| of the currently selected wallpaper to |target| iframe
+ * window. Sending null indicates that no image is selected.
+ */
+export function sendCurrentWallpaperAssetId(
+    target: Window, assetId: bigint|undefined) {
+  const event: constants.SendCurrentWallpaperAssetIdEvent = {
+    type: constants.EventType.SEND_CURRENT_WALLPAPER_ASSET_ID,
+    assetId
+  };
+  target.postMessage(event, constants.untrustedOrigin);
+}
+
+/**
+ * Send the |assetId| to the |target| iframe when the user clicks on online
+ * wallpaper image.
+ */
+export function sendPendingWallpaperAssetId(
+    target: Window, assetId: bigint|undefined) {
+  const event: constants.SendPendingWallpaperAssetIdEvent = {
+    type: constants.EventType.SEND_PENDING_WALLPAPER_ASSET_ID,
+    assetId
+  };
+  target.postMessage(event, constants.untrustedOrigin);
+}
+
+
+/**
+ * Called from trusted code to validate that a received postMessage event
+ * contains valid data. Ignores messages that are not of the expected type.
+ */
+export function validateReceivedSelection(
+    event: MessageEvent, choices: WallpaperCollection[]): WallpaperCollection;
+export function validateReceivedSelection(
+    event: MessageEvent, choices: WallpaperImage[]): WallpaperImage;
+export function validateReceivedSelection(
+    event: MessageEvent, choices: (WallpaperCollection|WallpaperImage)[]):
+    WallpaperCollection|WallpaperImage {
+  assert(isNonEmptyArray(choices), 'choices must be a non-empty array');
+
+  const data: constants.Events = event.data;
+  let selected: WallpaperCollection|WallpaperImage|undefined = undefined;
+  switch (data.type) {
+    case constants.EventType.SELECT_COLLECTION: {
+      assert(!!data.collectionId, 'Expected a collection id parameter');
+      selected = (choices as WallpaperCollection[])
+                     .find(choice => choice.id === data.collectionId);
+      break;
+    }
+    case constants.EventType.SELECT_IMAGE: {
+      assert(
+          data.hasOwnProperty('assetId'),
+          'Expected an image assetId parameter');
+      assert(
+          typeof data.assetId === 'bigint', 'assetId parameter must be bigint');
+      selected = (choices as WallpaperImage[])
+                     .find(choice => choice.assetId === data.assetId);
+      break;
+    }
+    default:
+      assertNotReached('Unknown event type');
+  }
+
+  assert(!!selected, 'No valid selection found in choices');
+  return selected!;
+}
diff --git a/ash/webui/personalization_app/resources/trusted/local_images_element.js b/ash/webui/personalization_app/resources/trusted/local_images_element.js
index a312f6e2..9491c19 100644
--- a/ash/webui/personalization_app/resources/trusted/local_images_element.js
+++ b/ash/webui/personalization_app/resources/trusted/local_images_element.js
@@ -15,7 +15,7 @@
 import './styles.js';
 import '../common/icons.js';
 import '../common/styles.js';
-import {assert} from '/assert.m.js';
+import {assert} from 'chrome://resources/js/assert.m.js';
 import {afterNextRender, html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {getLoadingPlaceholderAnimationDelay} from '../common/utils.js';
 import {isSelectionEvent} from '../common/utils.js';
diff --git a/ash/webui/personalization_app/resources/trusted/personalization_message_handler.js b/ash/webui/personalization_app/resources/trusted/personalization_message_handler.js
index ecf594a..6e37030 100644
--- a/ash/webui/personalization_app/resources/trusted/personalization_message_handler.js
+++ b/ash/webui/personalization_app/resources/trusted/personalization_message_handler.js
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {assert} from '/assert.m.js';
+import {assert} from 'chrome://resources/js/assert.m.js';
 import {EventType, untrustedOrigin} from '../common/constants.js';
-import {validateReceivedSelection} from '../common/iframe_api.js';
+import {validateReceivedSelection} from '../trusted/iframe_api.js';
 import {getWallpaperProvider} from './mojo_interface_provider.js';
 import {selectWallpaper} from './personalization_controller.js';
 import {PersonalizationRouter} from './personalization_router_element.js';
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper_collections_element.js b/ash/webui/personalization_app/resources/trusted/wallpaper_collections_element.js
index a1c6f662..dbc49f1 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper_collections_element.js
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper_collections_element.js
@@ -9,10 +9,13 @@
  */
 
 import './styles.js';
+
 import {afterNextRender, html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 import {kMaximumLocalImagePreviews} from '../common/constants.js';
-import {sendCollections, sendGooglePhotosCount, sendGooglePhotosPhotos, sendImageCounts, sendLocalImageData, sendLocalImages, sendVisible} from '../common/iframe_api.js';
 import {isNonEmptyArray, isNullOrArray, isNullOrNumber, promisifyOnload} from '../common/utils.js';
+import {sendCollections, sendGooglePhotosCount, sendGooglePhotosPhotos, sendImageCounts, sendLocalImageData, sendLocalImages, sendVisible} from '../trusted/iframe_api.js';
+
 import {getWallpaperProvider} from './mojo_interface_provider.js';
 import {initializeBackdropData} from './personalization_controller.js';
 import {WithPersonalizationStore} from './personalization_store.js';
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper_images_element.js b/ash/webui/personalization_app/resources/trusted/wallpaper_images_element.js
index 65aab9e..2f823f8 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper_images_element.js
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper_images_element.js
@@ -11,10 +11,13 @@
 
 import 'chrome://resources/polymer/v3_0/iron-media-query/iron-media-query.js';
 import './styles.js';
+
 import {afterNextRender, html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 import {ImageTile} from '../common/constants.js';
-import {sendCurrentWallpaperAssetId, sendImageTiles, sendPendingWallpaperAssetId, sendVisible} from '../common/iframe_api.js';
 import {isNonEmptyArray, promisifyOnload} from '../common/utils.js';
+import {sendCurrentWallpaperAssetId, sendImageTiles, sendPendingWallpaperAssetId, sendVisible} from '../trusted/iframe_api.js';
+
 import {DisplayableImage, OnlineImageType, WallpaperType} from './personalization_app.mojom-webui.js';
 import {PersonalizationRouter} from './personalization_router_element.js';
 import {WithPersonalizationStore} from './personalization_store.js';
diff --git a/ash/webui/personalization_app/resources/untrusted/collections_grid.js b/ash/webui/personalization_app/resources/untrusted/collections_grid.js
index e8660fb..653d766 100644
--- a/ash/webui/personalization_app/resources/untrusted/collections_grid.js
+++ b/ash/webui/personalization_app/resources/untrusted/collections_grid.js
@@ -5,10 +5,12 @@
 import 'chrome-untrusted://personalization/polymer/v3_0/iron-list/iron-list.js';
 import './setup.js';
 import './styles.js';
+
 import {afterNextRender, html, PolymerElement} from 'chrome-untrusted://personalization/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 import {EventType, kMaximumLocalImagePreviews} from '../common/constants.js';
-import {selectCollection, selectGooglePhotosCollection, selectLocalCollection, validateReceivedData} from '../common/iframe_api.js';
 import {getLoadingPlaceholderAnimationDelay, getNumberOfGridItemsPerRow, isNullOrArray, isNullOrNumber, isSelectionEvent} from '../common/utils.js';
+import {selectCollection, selectGooglePhotosCollection, selectLocalCollection, validateReceivedData} from '../untrusted/iframe_api.js';
 
 /**
  * @fileoverview Responds to |SendCollectionsEvent| from trusted. Handles user
diff --git a/ash/webui/personalization_app/resources/untrusted/iframe_api.ts b/ash/webui/personalization_app/resources/untrusted/iframe_api.ts
new file mode 100644
index 0000000..d637ff0
--- /dev/null
+++ b/ash/webui/personalization_app/resources/untrusted/iframe_api.ts
@@ -0,0 +1,110 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {assert, assertNotReached} from '../common/assert.m.js';
+import * as constants from '../common/constants.js';
+import {isNonEmptyArray, isNullOrArray, isNullOrNumber} from '../common/utils.js';
+
+/**
+ * @fileoverview Helper functions for communicating between trusted and
+ * untrusted. All untrusted -> trusted communication must happen through the
+ * functions in this file.
+ */
+
+/**
+ * Select a collection. Sent from untrusted to trusted.
+ */
+export function selectCollection(target: Window, collectionId: string) {
+  const event: constants.SelectCollectionEvent = {
+    type: constants.EventType.SELECT_COLLECTION,
+    collectionId
+  };
+  target.postMessage(event, constants.trustedOrigin);
+}
+
+/**
+ * Select the Google Photos collection. Sent from untrusted to trusted.
+ */
+export function selectGooglePhotosCollection(target: Window) {
+  const event: constants.SelectGooglePhotosCollectionEvent = {
+    type: constants.EventType.SELECT_GOOGLE_PHOTOS_COLLECTION
+  };
+  target.postMessage(event, constants.trustedOrigin);
+}
+
+/**
+ * Select the local collection. Sent from untrusted to trusted.
+ */
+export function selectLocalCollection(target: Window) {
+  const event: constants.SelectLocalCollectionEvent = {
+    type: constants.EventType.SELECT_LOCAL_COLLECTION
+  };
+  target.postMessage(event, constants.trustedOrigin);
+}
+
+/**
+ * Select an image. Sent from untrusted to trusted.
+ */
+export function selectImage(target: Window, assetId: bigint) {
+  const event: constants.SelectImageEvent = {
+    type: constants.EventType.SELECT_IMAGE,
+    assetId
+  };
+  target.postMessage(event, constants.trustedOrigin);
+}
+
+/**
+ * Called from untrusted code to validate that a received event is of an
+ * expected type and contains the expected data.
+ */
+export function validateReceivedData(
+    event: MessageEvent, expectedEventType: constants.EventType) {
+  assert(
+      event.origin === constants.trustedOrigin,
+      'Message is not from the correct origin');
+  assert(
+      event.data.type === expectedEventType,
+      `Expected event type: ${expectedEventType}`);
+
+  const data: constants.Events = event.data;
+  switch (data.type) {
+    case constants.EventType.SEND_COLLECTIONS: {
+      assert(isNonEmptyArray(data.collections), 'Expected collections array');
+      return data.collections;
+    }
+    case constants.EventType.SEND_GOOGLE_PHOTOS_COUNT: {
+      assert(isNullOrNumber(data.count), 'Expected photos count');
+      return data.count;
+    }
+    case constants.EventType.SEND_GOOGLE_PHOTOS_PHOTOS: {
+      assert(isNullOrArray(data.photos), 'Expected photos array');
+      return data.photos;
+    }
+    case constants.EventType.SEND_LOCAL_IMAGE_DATA: {
+      assert(typeof data.data === 'object', 'Expected data object');
+      return data.data;
+    }
+    case constants.EventType.SEND_LOCAL_IMAGES:
+      // Images array may be empty.
+      assert(Array.isArray(data.images), 'Expected images array');
+      return data.images;
+    case constants.EventType.SEND_IMAGE_TILES: {
+      // Images array may be empty.
+      assert(Array.isArray(data.tiles), 'Expected images array');
+      return data.tiles;
+    }
+    case constants.EventType.SEND_CURRENT_WALLPAPER_ASSET_ID:
+    case constants.EventType.SEND_PENDING_WALLPAPER_ASSET_ID: {
+      assert(data.assetId === null || typeof data.assetId === 'bigint');
+      return data.assetId;
+    }
+    case constants.EventType.SEND_VISIBLE: {
+      assert(typeof data.visible === 'boolean');
+      return data.visible;
+    }
+    default:
+      assertNotReached('Unknown event type');
+  }
+  return null;
+}
diff --git a/ash/webui/personalization_app/resources/untrusted/images_grid.js b/ash/webui/personalization_app/resources/untrusted/images_grid.js
index 7d3b4a2..40ca645f 100644
--- a/ash/webui/personalization_app/resources/untrusted/images_grid.js
+++ b/ash/webui/personalization_app/resources/untrusted/images_grid.js
@@ -5,10 +5,12 @@
 import 'chrome-untrusted://personalization/polymer/v3_0/iron-list/iron-list.js';
 import './setup.js';
 import './styles.js';
+
 import {html, PolymerElement} from 'chrome-untrusted://personalization/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 import {EventType, ImageTile} from '../common/constants.js';
-import {selectImage, validateReceivedData} from '../common/iframe_api.js';
 import {isSelectionEvent} from '../common/utils.js';
+import {selectImage, validateReceivedData} from '../untrusted/iframe_api.js';
 
 /**
  * @fileoverview Responds to |SendImageTilesEvent| from trusted. Handles user
diff --git a/ash/webui/personalization_app/untrusted_personalization_app_ui_config.cc b/ash/webui/personalization_app/untrusted_personalization_app_ui_config.cc
index 0e1a0c4..1cf9fe2 100644
--- a/ash/webui/personalization_app/untrusted_personalization_app_ui_config.cc
+++ b/ash/webui/personalization_app/untrusted_personalization_app_ui_config.cc
@@ -83,10 +83,6 @@
           base::StartsWith(resource.path, "common"))
         source->AddResourcePath(resource.path, resource.id);
     }
-    // Mirror assert.m.js here so that it is accessible at the same path in
-    // trusted and untrusted context.
-    source->AddResourcePath("assert.m.js", IDR_WEBUI_JS_ASSERT_M_JS);
-
     // Add WebUI resources like polymer and iron-list so that it is accessible
     // inside untrusted iframe.
     source->AddResourcePaths(base::make_span(kWebuiGeneratedResources,
diff --git a/ash/webui/print_management/resources/print_management_fonts_css.html b/ash/webui/print_management/resources/print_management_fonts_css.html
index 031a106..eddcc37 100644
--- a/ash/webui/print_management/resources/print_management_fonts_css.html
+++ b/ash/webui/print_management/resources/print_management_fonts_css.html
@@ -16,10 +16,10 @@
       --print-management-header-font-weight: 500; /* medium */
       --print-management-button-font-weight: 500; /* medium */
 
-      --print-management-title-text-color: var(--google-grey-900);
-      --print-management-header-text-color: var(--google-grey-700);
-      --print-management-default-text-color: var(--google-grey-900);
-      --print-management-button-text-color: var(--google-blue-600);
+      --print-management-title-text-color: var(--cros-text-color-primary);
+      --print-management-header-text-color: var(--cros-text-color-secondary);
+      --print-management-default-text-color: var(--cros-text-color-primary);
+      --print-management-button-text-color: var(--cros-text-color-prominent);
 
       --print-management-title-font: {
           font-family: var(--print-management-title-font-family);
diff --git a/ash/webui/shimless_rma/resources/onboarding_network_page.js b/ash/webui/shimless_rma/resources/onboarding_network_page.js
index e0f05d1..5588403 100644
--- a/ash/webui/shimless_rma/resources/onboarding_network_page.js
+++ b/ash/webui/shimless_rma/resources/onboarding_network_page.js
@@ -116,6 +116,16 @@
         type: String,
         value: '',
       },
+
+      /**
+       * Set to true to when connected to at least one active network.
+       * @protected
+       */
+      isOnline_: {
+        type: Boolean,
+        value: false,
+        observer: 'onIsOnlineChange_',
+      },
     };
   }
 
@@ -155,6 +165,10 @@
           (network) => [chromeos.networkConfig.mojom.NetworkType.kWiFi,
                         chromeos.networkConfig.mojom.NetworkType.kEthernet,
       ].includes(network.type));
+
+      this.isOnline_ = this.networks_.some(function(network) {
+        return OncMojo.connectionStateIsConnected(network.connectionState);
+      });
     });
   }
 
@@ -294,6 +308,18 @@
   onNextButtonClick() {
     return this.shimlessRmaService_.networkSelectionComplete();
   }
+
+  /** @private */
+  onIsOnlineChange_() {
+    this.dispatchEvent(new CustomEvent(
+        'set-next-button-label',
+        {
+          bubbles: true,
+          composed: true,
+          detail: this.isOnline_ ? 'nextButtonLabel' : 'skipButtonLabel'
+        },
+        ));
+  }
 }
 
 customElements.define(OnboardingNetworkPage.is, OnboardingNetworkPage);
diff --git a/ash/webui/shimless_rma/resources/shimless_rma.js b/ash/webui/shimless_rma/resources/shimless_rma.js
index 6fcb1233..112658cef 100644
--- a/ash/webui/shimless_rma/resources/shimless_rma.js
+++ b/ash/webui/shimless_rma/resources/shimless_rma.js
@@ -77,6 +77,7 @@
     componentIs: 'onboarding-network-page',
     requiresReloadWhenShown: false,
     buttonNext: ButtonState.DISABLED,
+    buttonNextLabelKey: 'skipButtonLabel',
     buttonCancel: ButtonState.HIDDEN,
     buttonBack: ButtonState.HIDDEN,
   },
diff --git a/base/allocator/allocator_shim_override_mac_default_zone.h b/base/allocator/allocator_shim_override_mac_default_zone.h
index 5a4dd2b..2edab779 100644
--- a/base/allocator/allocator_shim_override_mac_default_zone.h
+++ b/base/allocator/allocator_shim_override_mac_default_zone.h
@@ -11,6 +11,8 @@
 #error This header must be included iff PartitionAlloc-Everywhere is enabled.
 #endif
 
+#include <string.h>
+
 #include "base/allocator/partition_allocator/partition_alloc_constants.h"
 #include "base/bits.h"
 #include "base/ignore_result.h"
@@ -186,6 +188,52 @@
 // receives an address allocated by the system allocator.
 __attribute__((constructor(0))) void
 InitializeDefaultMallocZoneWithPartitionAlloc() {
+  static constexpr const char* kZoneName = "PartitionAlloc";
+
+  // HACK: This should really only be called once, but it is not.
+  //
+  // This function is a static constructor of its binary. If it is included in a
+  // dynamic library, then the same process may end up executing this code
+  // multiple times, once per library. As a consequence, each new library will
+  // add its own allocator as the default zone. Aside from splitting the heap
+  // further, the main issue arises if/when the last library to be loaded
+  // (dlopen()-ed) gets dlclose()-ed.
+  //
+  // See crbug.com/1271139 for details.
+  //
+  // In this case, subsequent free() will be routed by libmalloc to the deleted
+  // zone (since its code has been unloaded from memory), and crash inside
+  // libsystem's free(). This in practice happens as soon as dlclose() is
+  // called, inside the dynamic linker (dyld).
+  //
+  // Since we are talking about different library, and issues inside the dynamic
+  // linker, we cannot use a global static variable (which would be
+  // per-library), or anything from pthread.
+  //
+  // The solution used here is to check whether the current default zone is
+  // already ours, in which case we are not the first dynamic library here, and
+  // should do nothing. This is racy, and hacky.
+  vm_address_t* zones = nullptr;
+  unsigned int zone_count = 0;
+  // *Not* using malloc_default_zone(), as it seems to be hardcoded to return
+  // something else than the default zone. See the difference between
+  // malloc_default_zone() and inline_malloc_default_zone() in Apple's malloc.c
+  // (in libmalloc).
+  kern_return_t result =
+      malloc_get_all_zones(mach_task_self(), nullptr, &zones, &zone_count);
+  MACH_CHECK(result == KERN_SUCCESS, result) << "malloc_get_all_zones";
+  malloc_zone_t* default_zone = reinterpret_cast<malloc_zone_t*>(zones[0]);
+
+  // strcmp() and not a pointer comparison, as the zone was registered from
+  // another library, the pointers don't match.
+  if (default_zone->zone_name &&
+      (strcmp(default_zone->zone_name, kZoneName) == 0)) {
+    // The default zone is already provided by PartitionAlloc, so this function
+    // has been called from another library (or the main executable), nothing to
+    // do.
+    return;
+  }
+
   // Instantiate the existing regular and purgeable zones in order to make the
   // existing purgeable zone use the existing regular zone since PartitionAlloc
   // doesn't support a purgeable zone.
@@ -226,7 +274,7 @@
   //   version >= 11: introspect.print_task is supported
   //   version >= 12: introspect.task_statistics is supported
   g_mac_malloc_zone.version = 9;
-  g_mac_malloc_zone.zone_name = "PartitionAlloc";
+  g_mac_malloc_zone.zone_name = kZoneName;
   g_mac_malloc_zone.introspect = &g_mac_malloc_introspection;
   g_mac_malloc_zone.size = MallocZoneSize;
   g_mac_malloc_zone.malloc = MallocZoneMalloc;
@@ -243,10 +291,9 @@
   g_mac_malloc_zone.claimed_address = nullptr;
 
   // Make our own zone the default zone.
-  vm_address_t* zones = nullptr;
-  unsigned int zone_count = 0;
-  kern_return_t result =
-      malloc_get_all_zones(mach_task_self(), nullptr, &zones, &zone_count);
+  zones = nullptr;
+  zone_count = 0;
+  result = malloc_get_all_zones(mach_task_self(), nullptr, &zones, &zone_count);
   MACH_CHECK(result == KERN_SUCCESS, result) << "malloc_get_all_zones";
   malloc_zone_t* system_default_zone =
       reinterpret_cast<malloc_zone_t*>(zones[0]);
diff --git a/base/big_endian.cc b/base/big_endian.cc
index ad6a101..e114216 100644
--- a/base/big_endian.cc
+++ b/base/big_endian.cc
@@ -11,9 +11,17 @@
 
 namespace base {
 
-BigEndianReader::BigEndianReader(const char* buf, size_t len)
+BigEndianReader BigEndianReader::FromStringPiece(
+    base::StringPiece string_piece) {
+  return BigEndianReader(base::as_bytes(base::make_span(string_piece)));
+}
+
+BigEndianReader::BigEndianReader(const uint8_t* buf, size_t len)
     : ptr_(buf), end_(ptr_ + len) {}
 
+BigEndianReader::BigEndianReader(base::span<const uint8_t> buf)
+    : ptr_(buf.data()), end_(buf.data() + buf.size()) {}
+
 bool BigEndianReader::Skip(size_t len) {
   if (len > remaining())
     return false;
@@ -32,7 +40,15 @@
 bool BigEndianReader::ReadPiece(base::StringPiece* out, size_t len) {
   if (len > remaining())
     return false;
-  *out = base::StringPiece(ptr_, len);
+  *out = base::StringPiece(reinterpret_cast<const char*>(ptr_), len);
+  ptr_ += len;
+  return true;
+}
+
+bool BigEndianReader::ReadSpan(base::span<const uint8_t>* out, size_t len) {
+  if (len > remaining())
+    return false;
+  *out = base::make_span(ptr_, len);
   ptr_ += len;
   return true;
 }
@@ -68,12 +84,12 @@
   if (!Read(&t_len))
     return false;
   size_t len = strict_cast<size_t>(t_len);
-  const char* original_ptr = ptr_;
+  const uint8_t* original_ptr = ptr_;
   if (!Skip(len)) {
     ptr_ -= sizeof(T);
     return false;
   }
-  *out = base::StringPiece(original_ptr, len);
+  *out = base::StringPiece(reinterpret_cast<const char*>(original_ptr), len);
   return true;
 }
 
diff --git a/base/big_endian.h b/base/big_endian.h
index 13a0b9b..53bac4a3c 100644
--- a/base/big_endian.h
+++ b/base/big_endian.h
@@ -10,6 +10,7 @@
 #include <type_traits>
 
 #include "base/base_export.h"
+#include "base/containers/span.h"
 #include "base/strings/string_piece.h"
 
 namespace base {
@@ -19,17 +20,16 @@
 // NOTE(szym): glibc dns-canon.c use ntohs(*(uint16_t*)ptr) which is
 // potentially unaligned.
 // This would cause SIGBUS on ARMv5 or earlier and ARMv6-M.
-template<typename T>
-inline void ReadBigEndian(const char buf[], T* out) {
+template <typename T>
+inline void ReadBigEndian(const uint8_t buf[], T* out) {
   static_assert(std::is_integral<T>::value, "T has to be an integral type.");
   // Make an unsigned version of the output type to make shift possible
   // without UB.
-  typename std::make_unsigned<T>::type unsigned_result =
-      static_cast<uint8_t>(buf[0]);
+  typename std::make_unsigned<T>::type unsigned_result = buf[0];
   for (size_t i = 1; i < sizeof(T); ++i) {
     unsigned_result <<= 8;
     // Must cast to uint8_t to avoid clobbering by sign extension.
-    unsigned_result |= static_cast<uint8_t>(buf[i]);
+    unsigned_result |= buf[i];
   }
   *out = unsigned_result;
 }
@@ -48,7 +48,7 @@
 
 // Specializations to make clang happy about the (dead code) shifts above.
 template <>
-inline void ReadBigEndian<uint8_t>(const char buf[], uint8_t* out) {
+inline void ReadBigEndian<uint8_t>(const uint8_t buf[], uint8_t* out) {
   *out = buf[0];
 }
 
@@ -58,7 +58,7 @@
 }
 
 template <>
-inline void ReadBigEndian<int8_t>(const char buf[], int8_t* out) {
+inline void ReadBigEndian<int8_t>(const uint8_t buf[], int8_t* out) {
   *out = buf[0];
 }
 
@@ -71,15 +71,20 @@
 // an underlying buffer. All the reading functions advance the internal pointer.
 class BASE_EXPORT BigEndianReader {
  public:
-  BigEndianReader(const char* buf, size_t len);
+  static BigEndianReader FromStringPiece(base::StringPiece string_piece);
 
-  const char* ptr() const { return ptr_; }
+  BigEndianReader(const uint8_t* buf, size_t len);
+  explicit BigEndianReader(base::span<const uint8_t> buf);
+
+  const uint8_t* ptr() const { return ptr_; }
   size_t remaining() const { return end_ - ptr_; }
 
   bool Skip(size_t len);
   bool ReadBytes(void* out, size_t len);
   // Creates a StringPiece in |out| that points to the underlying buffer.
   bool ReadPiece(base::StringPiece* out, size_t len);
+  bool ReadSpan(base::span<const uint8_t>* out, size_t len);
+
   bool ReadU8(uint8_t* value);
   bool ReadU16(uint16_t* value);
   bool ReadU32(uint32_t* value);
@@ -106,8 +111,8 @@
   template <typename T>
   bool ReadLengthPrefixed(base::StringPiece* out);
 
-  const char* ptr_;
-  const char* end_;
+  const uint8_t* ptr_;
+  const uint8_t* end_;
 };
 
 // Allows writing integers in network order (big endian) while iterating over
diff --git a/base/big_endian_unittest.cc b/base/big_endian_unittest.cc
index 7f73ab1..00aef35d 100644
--- a/base/big_endian_unittest.cc
+++ b/base/big_endian_unittest.cc
@@ -14,7 +14,7 @@
 namespace base {
 
 TEST(ReadBigEndianTest, ReadSignedPositive) {
-  char data[] = {0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x1A, 0x2A};
+  uint8_t data[] = {0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x1A, 0x2A};
   int8_t s8 = 0;
   int16_t s16 = 0;
   int32_t s32 = 0;
@@ -35,10 +35,10 @@
   int16_t s16 = 0;
   int32_t s32 = 0;
   int64_t s64 = 0;
-  ReadBigEndian(reinterpret_cast<const char*>(data), &s8);
-  ReadBigEndian(reinterpret_cast<const char*>(data), &s16);
-  ReadBigEndian(reinterpret_cast<const char*>(data), &s32);
-  ReadBigEndian(reinterpret_cast<const char*>(data), &s64);
+  ReadBigEndian(data, &s8);
+  ReadBigEndian(data, &s16);
+  ReadBigEndian(data, &s32);
+  ReadBigEndian(data, &s64);
   EXPECT_EQ(-1, s8);
   EXPECT_EQ(-1, s16);
   EXPECT_EQ(-1, s32);
@@ -51,10 +51,10 @@
   uint16_t u16 = 0;
   uint32_t u32 = 0;
   uint64_t u64 = 0;
-  ReadBigEndian(reinterpret_cast<const char*>(data), &u8);
-  ReadBigEndian(reinterpret_cast<const char*>(data), &u16);
-  ReadBigEndian(reinterpret_cast<const char*>(data), &u32);
-  ReadBigEndian(reinterpret_cast<const char*>(data), &u64);
+  ReadBigEndian(data, &u8);
+  ReadBigEndian(data, &u16);
+  ReadBigEndian(data, &u32);
+  ReadBigEndian(data, &u64);
   EXPECT_EQ(0xA0, u8);
   EXPECT_EQ(0xA0B0, u16);
   EXPECT_EQ(0xA0B0C0D0, u32);
@@ -63,20 +63,20 @@
 
 TEST(ReadBigEndianTest, TryAll16BitValues) {
   using signed_type = int16_t;
-  char data[sizeof(signed_type)];
+  uint8_t data[sizeof(signed_type)];
   for (int i = std::numeric_limits<signed_type>::min();
        i <= std::numeric_limits<signed_type>::max(); i++) {
     signed_type expected = i;
     signed_type actual = 0;
-    WriteBigEndian(data, expected);
+    WriteBigEndian(reinterpret_cast<char*>(data), expected);
     ReadBigEndian(data, &actual);
     EXPECT_EQ(expected, actual);
   }
 }
 
 TEST(BigEndianReaderTest, ReadsValues) {
-  char data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
-                  0x1A, 0x2B, 0x3C, 0x4D, 0x5E };
+  uint8_t data[] = {0,   1,   2,   3,   4,   5,    6,    7,    8,    9,   0xA,
+                    0xB, 0xC, 0xD, 0xE, 0xF, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E};
   char buf[2];
   uint8_t u8;
   uint16_t u16;
@@ -99,7 +99,7 @@
   EXPECT_EQ(0x0708090Au, u32);
   EXPECT_TRUE(reader.ReadU64(&u64));
   EXPECT_EQ(0x0B0C0D0E0F1A2B3Cllu, u64);
-  base::StringPiece expected(reader.ptr(), 2);
+  base::StringPiece expected(reinterpret_cast<const char*>(reader.ptr()), 2);
   EXPECT_TRUE(reader.ReadPiece(&piece, 2));
   EXPECT_EQ(2u, piece.size());
   EXPECT_EQ(expected.data(), piece.data());
@@ -107,8 +107,8 @@
 
 TEST(BigEndianReaderTest, ReadsLengthPrefixedValues) {
   {
-    char u8_prefixed_data[] = {8,   8,   9,    0xA,  0xB,  0xC,  0xD,
-                               0xE, 0xF, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E};
+    uint8_t u8_prefixed_data[] = {8,   8,   9,    0xA,  0xB,  0xC,  0xD,
+                                  0xE, 0xF, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E};
     BigEndianReader reader(u8_prefixed_data, sizeof(u8_prefixed_data));
 
     base::StringPiece piece;
@@ -116,19 +116,21 @@
     // |reader| should skip both a u8 and the length-8 length-prefixed field.
     EXPECT_EQ(reader.ptr(), u8_prefixed_data + 9);
     EXPECT_EQ(piece.size(), 8u);
-    EXPECT_EQ(piece.data(), u8_prefixed_data + 1);
+    EXPECT_EQ(reinterpret_cast<const uint8_t*>(piece.data()),
+              u8_prefixed_data + 1);
   }
 
   {
-    char u16_prefixed_data[] = {0,    8,    0xD,  0xE,  0xF,
-                                0x1A, 0x2B, 0x3C, 0x4D, 0x5E};
+    uint8_t u16_prefixed_data[] = {0,    8,    0xD,  0xE,  0xF,
+                                   0x1A, 0x2B, 0x3C, 0x4D, 0x5E};
     BigEndianReader reader(u16_prefixed_data, sizeof(u16_prefixed_data));
     base::StringPiece piece;
     ASSERT_TRUE(reader.ReadU16LengthPrefixed(&piece));
     // |reader| should skip both a u16 and the length-8 length-prefixed field.
     EXPECT_EQ(reader.ptr(), u16_prefixed_data + 10);
     EXPECT_EQ(piece.size(), 8u);
-    EXPECT_EQ(piece.data(), u16_prefixed_data + 2);
+    EXPECT_EQ(reinterpret_cast<const uint8_t*>(piece.data()),
+              u16_prefixed_data + 2);
 
     // With no data left, we shouldn't be able to
     // read another u8 length prefix (or a u16 length prefix,
@@ -139,12 +141,13 @@
 
   {
     // Make sure there's no issue reading a zero-value length prefix.
-    char u16_prefixed_data[3] = {};
+    uint8_t u16_prefixed_data[3] = {};
     BigEndianReader reader(u16_prefixed_data, sizeof(u16_prefixed_data));
     base::StringPiece piece;
     ASSERT_TRUE(reader.ReadU16LengthPrefixed(&piece));
     EXPECT_EQ(reader.ptr(), u16_prefixed_data + 2);
-    EXPECT_EQ(piece.data(), u16_prefixed_data + 2);
+    EXPECT_EQ(reinterpret_cast<const uint8_t*>(piece.data()),
+              u16_prefixed_data + 2);
     EXPECT_EQ(piece.size(), 0u);
   }
 }
@@ -152,8 +155,8 @@
 TEST(BigEndianReaderTest, LengthPrefixedReadsFailGracefully) {
   // We can't read 0xF (or, for that matter, 0xF8) bytes after the length
   // prefix: there isn't enough data.
-  char data[] = {0xF, 8,   9,    0xA,  0xB,  0xC,  0xD,
-                 0xE, 0xF, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E};
+  uint8_t data[] = {0xF, 8,   9,    0xA,  0xB,  0xC,  0xD,
+                    0xE, 0xF, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E};
   BigEndianReader reader(data, sizeof(data));
   base::StringPiece piece;
   EXPECT_FALSE(reader.ReadU8LengthPrefixed(&piece));
@@ -164,7 +167,7 @@
 }
 
 TEST(BigEndianReaderTest, RespectsLength) {
-  char data[8];
+  uint8_t data[8];
   char buf[2];
   uint8_t u8;
   uint16_t u16;
@@ -192,7 +195,7 @@
 }
 
 TEST(BigEndianReaderTest, SafePointerMath) {
-  char data[] = "foo";
+  uint8_t data[] = "foo";
   BigEndianReader reader(data, sizeof(data));
   // The test should fail without ever dereferencing the |dummy_buf| pointer.
   char* dummy_buf = reinterpret_cast<char*>(0xdeadbeef);
diff --git a/base/bits.h b/base/bits.h
index ffa4192..f71bc3f 100644
--- a/base/bits.h
+++ b/base/bits.h
@@ -209,12 +209,16 @@
   return CountTrailingZeroBits(x);
 }
 
-// Returns the integer i such as 2^i <= n < 2^(i+1)
+// Returns the integer i such as 2^i <= n < 2^(i+1).
+//
+// There is a common `BitLength` function, which returns the number of bits
+// required to represent a value. Rather than implement that function,
+// use `Log2Floor` and add 1 to the result.
 constexpr int Log2Floor(uint32_t n) {
   return 31 - CountLeadingZeroBits(n);
 }
 
-// Returns the integer i such as 2^(i-1) < n <= 2^i
+// Returns the integer i such as 2^(i-1) < n <= 2^i.
 constexpr int Log2Ceiling(uint32_t n) {
   // When n == 0, we want the function to return -1.
   // When n == 0, (n - 1) will underflow to 0xFFFFFFFF, which is
diff --git a/build/toolchain/cros/BUILD.gn b/build/toolchain/cros/BUILD.gn
index cd1825cb..4cff006 100644
--- a/build/toolchain/cros/BUILD.gn
+++ b/build/toolchain/cros/BUILD.gn
@@ -23,7 +23,7 @@
       extra_cppflags += " --gomacc-path $goma_dir/gomacc"
     }
     if (use_remoteexec && toolchain_args.needs_gomacc_path_arg) {
-      extra_cppflags += " --gomacc-path $rbe_cros_cc_wrapper"
+      extra_cppflags += "--rewrapper-path $rbe_cros_cc_wrapper --rewrapper-cfg ${rbe_cc_cfg_file}"
     }
 
     # Relativize path if compiler is specified such that not to lookup from $PATH
diff --git a/build/toolchain/rbe.gni b/build/toolchain/rbe.gni
index baf7462b..ae80b9c 100644
--- a/build/toolchain/rbe.gni
+++ b/build/toolchain/rbe.gni
@@ -23,7 +23,7 @@
   rbe_cc_cfg_file = ""
 
   # Set to the path of the RBE recleint wrapper for ChromeOS.
-  rbe_cros_cc_wrapper = ""
+  rbe_cros_cc_wrapper = "${rbe_bin_dir}/rewrapper"
 }
 
 # Set use_remoteexec if use_rbe is set.  Remove this once use_rbe is no longer
diff --git a/buildtools/reclient_cfgs/README.md b/buildtools/reclient_cfgs/README.md
new file mode 100644
index 0000000..ecab882
--- /dev/null
+++ b/buildtools/reclient_cfgs/README.md
@@ -0,0 +1 @@
+This directory contains the config files accepted by re-client's rewrapper command in place of inline flags.
diff --git a/buildtools/reclient_cfgs/rewrapper_chroot_compile.cfg b/buildtools/reclient_cfgs/rewrapper_chroot_compile.cfg
new file mode 100644
index 0000000..44371bbe
--- /dev/null
+++ b/buildtools/reclient_cfgs/rewrapper_chroot_compile.cfg
@@ -0,0 +1,10 @@
+service=remotebuildexecution.googleapis.com:443
+instance=projects/goma-foundry-experiments/instances/default_instance
+use_application_default_credentials=true
+platform=container-image=docker://gcr.io/cloud-marketplace/google/rbe-ubuntu16-04@sha256:f6568d8168b14aafd1b707019927a63c2d37113a03bcee188218f99bd0327ea1,dockerChrootPath=.,dockerRuntime=runsc
+server_address=unix:///tmp/reproxy.sock
+log_path=text:///tmp/reproxy_log.txt
+labels=type=compile,compiler=clang,lang=cpp
+inputs=etc/env.d/05gcc-x86_64-cros-linux-gnu,usr/share/gcc-data/x86_64-pc-linux-gnu/,usr/lib/gcc/x86_64-pc-linux-gnu,usr/x86_64-pc-linux-gnu/,var/cache/chromeos-chrome/chrome-src/src/out_amd64-generic/,bin/bash,bin/cat,usr/lib64/libreadline.so.8,lib64/libc.so.6,lib64/libtinfow.so.5,lib64/ld-linux-x86-64.so.2,usr/bin/x86_64-cros-linux-gnu-clang++,usr/bin/x86_64-cros-linux-gnu-clang,lib64/libpthread.so.0,usr/lib/locale/,usr/bin/clang++,usr/bin/clang,usr/bin/ccache,lib64/libm.so.6,/usr/lib64/libz.so.1,lib64/libdl.so.2,lib64/libtinfo.so.5,usr/lib64/libc++.so.1,usr/lib64/libc++abi.so.1,usr/lib/gcc/x86_64-pc-linux-gnu/10.2.0/libgcc_s.so.1,usr/lib/gcc/
+exec_root=/
+env_var_allowlist=PATH
diff --git a/cc/input/input_handler.h b/cc/input/input_handler.h
index 212f6d3..4216f35 100644
--- a/cc/input/input_handler.h
+++ b/cc/input/input_handler.h
@@ -214,6 +214,13 @@
     // detected a case where it cannot reliably target a scroll node and needs
     // the main thread to perform a hit test.
     bool needs_main_thread_hit_test = false;
+
+    // Used only in scroll unification. Tells the caller that we have performed
+    // the scroll (i.e. updated the offset in the scroll tree) on the compositor
+    // thread, but we will need a main thread lifecycle update + commit before
+    // the user will see the new pixels (for example, because the scroller does
+    // not have a composited layer).
+    bool needs_main_thread_repaint = false;
   };
 
   enum class TouchStartOrMoveEventListenerType {
diff --git a/cc/input/main_thread_scrolling_reason.h b/cc/input/main_thread_scrolling_reason.h
index 0378bdac..38444ca 100644
--- a/cc/input/main_thread_scrolling_reason.h
+++ b/cc/input/main_thread_scrolling_reason.h
@@ -30,20 +30,26 @@
     // value 0, so the 0th bit should never be used.
     // See also blink::RecordScrollReasonsMetric().
 
-    // Non-transient scrolling reasons.
+    // Non-transient scrolling reasons. These are set on the ScrollNode.
     kHasBackgroundAttachmentFixedObjects = 1 << 1,
     kThreadedScrollingDisabled = 1 << 3,
 
-    // Style-related scrolling on main reasons.
-    // These *AndLCDText reasons are due to subpixel text rendering which can
-    // only be applied by blending glyphs with the background at a specific
-    // screen position; transparency and transforms break this.
+    // Style-related scrolling on main reasons. Subpixel (LCD) text rendering
+    // requires blending glyphs with the background at a specific screen
+    // position; transparency and transforms break this.
+    // These are only reported by the main-thread scroll gesture event codepath.
+    // After scroll unification, we report kNoScrollingLayer instead.
     kNonCompositedReasonsFirst = 18,
     kNotOpaqueForTextAndLCDText = 1 << 19,
     kCantPaintScrollingBackgroundAndLCDText = 1 << 20,
     kNonCompositedReasonsLast = 23,
 
-    // Transient scrolling reasons. These are computed for each scroll begin.
+    // Transient scrolling reasons. These are computed for each scroll gesture.
+    // When computed inside ScrollBegin, these prevent the InputHandler from
+    // reporting a status with SCROLL_ON_IMPL_THREAD. In other cases, the
+    // InputHandler is scrolling "on impl", but we report a transient main
+    // thread scrolling reason to UMA when we determine that some other aspect
+    // of handling the scroll has been (or will be) blocked on the main thread.
     kScrollbarScrolling = 1 << 4,
     kNonFastScrollableRegion = 1 << 6,
     kFailedHitTest = 1 << 8,
diff --git a/cc/input/threaded_input_handler.cc b/cc/input/threaded_input_handler.cc
index 3a41b30d..246e829 100644
--- a/cc/input/threaded_input_handler.cc
+++ b/cc/input/threaded_input_handler.cc
@@ -124,20 +124,23 @@
   ClearCurrentlyScrollingNode();
 
   ElementId target_element_id = scroll_state->target_element_id();
+  ScrollTree& scroll_tree = GetScrollTree();
+  bool unification_enabled =
+      base::FeatureList::IsEnabled(features::kScrollUnification);
 
   if (target_element_id && !scroll_state->is_main_thread_hit_tested()) {
     TRACE_EVENT_INSTANT0("cc", "Latched scroll node provided",
                          TRACE_EVENT_SCOPE_THREAD);
     // If the caller passed in an element_id we can skip all the hit-testing
     // bits and provide a node straight-away.
-    scrolling_node = GetScrollTree().FindNodeFromElementId(target_element_id);
+    scrolling_node = scroll_tree.FindNodeFromElementId(target_element_id);
 
     // In unified scrolling, if we found a node we get to scroll it.
-    if (!base::FeatureList::IsEnabled(features::kScrollUnification)) {
+    if (!unification_enabled) {
       // We still need to confirm the targeted node exists and can scroll on
       // the compositor.
       if (scrolling_node) {
-        scroll_status = TryScroll(GetScrollTree(), scrolling_node);
+        scroll_status = TryScroll(scroll_tree, scrolling_node);
         if (IsMainThreadScrolling(scroll_status, scrolling_node))
           scroll_on_main_thread = true;
       }
@@ -153,8 +156,8 @@
       // unification is enabled and the targeted scroller comes back from a
       // main thread hit test.
       DCHECK(scroll_state->data()->is_main_thread_hit_tested);
-      DCHECK(base::FeatureList::IsEnabled(features::kScrollUnification));
-      starting_node = GetScrollTree().FindNodeFromElementId(target_element_id);
+      DCHECK(unification_enabled);
+      starting_node = scroll_tree.FindNodeFromElementId(target_element_id);
 
       if (!starting_node) {
         // The main thread sent us an element_id that the compositor doesn't
@@ -167,7 +170,7 @@
         scroll_status.thread = InputHandler::ScrollThread::SCROLL_IGNORED;
         return scroll_status;
       }
-    } else {
+    } else {  // !target_element_id
       TRACE_EVENT_INSTANT0("cc", "Hit Testing for ScrollNode",
                            TRACE_EVENT_SCOPE_THREAD);
       gfx::Point viewport_point(scroll_state->position_x(),
@@ -176,7 +179,7 @@
           gfx::ScalePoint(gfx::PointF(viewport_point),
                           compositor_delegate_.DeviceScaleFactor());
 
-      if (base::FeatureList::IsEnabled(features::kScrollUnification)) {
+      if (unification_enabled) {
         if (scroll_state->data()->is_main_thread_hit_tested) {
           // The client should have discarded the scroll when the hit test came
           // back with an invalid element id. If we somehow get here, we should
@@ -226,7 +229,7 @@
         }
 
         starting_node = scroll_hit_test.scroll_node;
-      } else {
+      } else {  // !unification_enabled
         LayerImpl* layer_impl =
             ActiveTree().FindLayerThatIsHitByPoint(device_viewport_point);
 
@@ -273,7 +276,7 @@
   if (scroll_on_main_thread) {
     // Under scroll unification we can request a main thread hit test, but we
     // should never send scrolls to the main thread.
-    DCHECK(!base::FeatureList::IsEnabled(features::kScrollUnification));
+    DCHECK(!unification_enabled);
 
     RecordCompositorSlowScrollMetric(type, MAIN_THREAD);
     scroll_status.thread = InputHandler::ScrollThread::SCROLL_ON_MAIN_THREAD;
@@ -308,8 +311,13 @@
             MainThreadScrollingReason::kNotScrollingOnMain);
   DCHECK_EQ(scroll_status.thread,
             InputHandler::ScrollThread::SCROLL_ON_IMPL_THREAD);
+  DCHECK(scrolling_node);
 
   ActiveTree().SetCurrentlyScrollingNode(scrolling_node);
+  if (unification_enabled &&
+      !scroll_tree.CanRealizeScrollsOnCompositor(*scrolling_node)) {
+    scroll_status.needs_main_thread_repaint = true;
+  }
 
   DidLatchToScroller(*scroll_state, type);
 
diff --git a/cc/trees/layer_tree_impl.cc b/cc/trees/layer_tree_impl.cc
index 8f72598..215ae8d 100644
--- a/cc/trees/layer_tree_impl.cc
+++ b/cc/trees/layer_tree_impl.cc
@@ -229,19 +229,14 @@
   // immediate changes in the compositor, we want the scroll to propagate
   // through Blink in a commit and have Blink update properties, paint,
   // compositing, etc. Thus, we avoid mutating the transform tree in this case.
-  // TODO(bokan): We SetNeedsCommit in LTHI when a scroll happens but in a
-  // normal compositor scroll there isn't much urgency for a commit to be
-  // scheduled. We should look into what we can do to make sure this is
-  // proritized accordingly. https://crbug.com/1082618.
-  bool can_realize_scroll_on_compositor =
+  bool should_realize_scroll_on_compositor =
       !base::FeatureList::IsEnabled(features::kScrollUnification) ||
-      (scroll_node->is_composited &&
-       !scroll_node->main_thread_scrolling_reasons);
+      scroll_tree.CanRealizeScrollsOnCompositor(*scroll_node);
 
   DCHECK(scroll_node->transform_id != TransformTree::kInvalidNodeId);
   TransformTree& transform_tree = property_trees()->transform_tree;
   auto* transform_node = transform_tree.Node(scroll_node->transform_id);
-  if (can_realize_scroll_on_compositor) {
+  if (should_realize_scroll_on_compositor) {
     if (transform_node->scroll_offset !=
         scroll_tree.current_scroll_offset(id)) {
       transform_node->scroll_offset = scroll_tree.current_scroll_offset(id);
diff --git a/cc/trees/property_tree.cc b/cc/trees/property_tree.cc
index fb49599..4a53999 100644
--- a/cc/trees/property_tree.cc
+++ b/cc/trees/property_tree.cc
@@ -1271,6 +1271,10 @@
   return node.is_composited;
 }
 
+bool ScrollTree::CanRealizeScrollsOnCompositor(const ScrollNode& node) const {
+  return node.is_composited && !node.main_thread_scrolling_reasons;
+}
+
 void ScrollTree::clear() {
   PropertyTree<ScrollNode>::clear();
 
diff --git a/cc/trees/property_tree.h b/cc/trees/property_tree.h
index 5e3770d..ec92e1d 100644
--- a/cc/trees/property_tree.h
+++ b/cc/trees/property_tree.h
@@ -513,6 +513,10 @@
   // repainting.
   bool IsComposited(const ScrollNode& node) const;
 
+  // Returns true iff the node is composited and does not have any non-transient
+  // main-thread scrolling reasons (see main_thread_scrolling_reason.h).
+  bool CanRealizeScrollsOnCompositor(const ScrollNode& node) const;
+
  private:
   // ScrollTree doesn't use the needs_update flag.
   using PropertyTree::needs_update;
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index 379e628..61f2f1f 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -446,7 +446,6 @@
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestPhoneTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestRemoveBillingAddressTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestRetryTest.java",
-  "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerExpandablePaymentHandlerTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerPaymentAppTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShippingAddressAndOptionTest.java",
   "javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShippingAddressChangeTest.java",
diff --git a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java
index 4c37cf5..3ab5997 100644
--- a/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java
+++ b/chrome/android/features/start_surface/internal/java/src/org/chromium/chrome/features/start_surface/StartSurfaceCoordinator.java
@@ -258,6 +258,9 @@
 
         boolean excludeMVTiles = StartSurfaceConfiguration.START_SURFACE_EXCLUDE_MV_TILES.getValue()
                 || !mIsStartSurfaceEnabled;
+        boolean excludeQueryTiles =
+                StartSurfaceConfiguration.START_SURFACE_EXCLUDE_QUERY_TILES.getValue()
+                || !mIsStartSurfaceEnabled;
         if (!mIsStartSurfaceEnabled) {
             // Create Tab switcher directly to save one layer in the view hierarchy.
             mTabSwitcher = TabManagementModuleProvider.getDelegate().createGridTabSwitcher(activity,
@@ -268,7 +271,7 @@
         } else {
             // createSwipeRefreshLayout has to be called before creating any surface.
             createSwipeRefreshLayout();
-            createAndSetStartSurface(excludeMVTiles);
+            createAndSetStartSurface(excludeMVTiles, excludeQueryTiles);
         }
 
         TabSwitcher.Controller controller =
@@ -519,7 +522,7 @@
         return mTasksSurface.isMVTilesInitialized();
     }
 
-    private void createAndSetStartSurface(boolean excludeMVTiles) {
+    private void createAndSetStartSurface(boolean excludeMVTiles, boolean excludeQueryTiles) {
         ArrayList<PropertyKey> allProperties =
                 new ArrayList<>(Arrays.asList(TasksSurfaceProperties.ALL_KEYS));
         allProperties.addAll(Arrays.asList(StartSurfaceProperties.ALL_KEYS));
@@ -532,10 +535,10 @@
         }
         mTasksSurface = TabManagementModuleProvider.getDelegate().createTasksSurface(mActivity,
                 mScrimCoordinator, mPropertyModel, tabSwitcherType, mParentTabSupplier,
-                !excludeMVTiles, mWindowAndroid, mActivityLifecycleDispatcher, mTabModelSelector,
-                mSnackbarManager, mDynamicResourceLoaderSupplier, mTabContentManager,
-                mModalDialogManager, mBrowserControlsManager, mTabCreatorManager,
-                mMenuOrKeyboardActionController, mShareDelegateSupplier,
+                !excludeMVTiles, !excludeQueryTiles, mWindowAndroid, mActivityLifecycleDispatcher,
+                mTabModelSelector, mSnackbarManager, mDynamicResourceLoaderSupplier,
+                mTabContentManager, mModalDialogManager, mBrowserControlsManager,
+                mTabCreatorManager, mMenuOrKeyboardActionController, mShareDelegateSupplier,
                 mMultiWindowModeStateDispatcher, mContainerView);
         mTasksSurface.getView().setId(R.id.primary_tasks_surface_view);
         mTasksSurface.addFakeSearchBoxShrinkAnimation();
@@ -562,14 +565,14 @@
 
         PropertyModel propertyModel = new PropertyModel(TasksSurfaceProperties.ALL_KEYS);
         mStartSurfaceMediator.setSecondaryTasksSurfacePropertyModel(propertyModel);
-        mSecondaryTasksSurface =
-                TabManagementModuleProvider.getDelegate().createTasksSurface(mActivity,
-                        mScrimCoordinator, propertyModel, TabSwitcherType.GRID, mParentTabSupplier,
-                        /* hasMVTiles= */ false, mWindowAndroid, mActivityLifecycleDispatcher,
-                        mTabModelSelector, mSnackbarManager, mDynamicResourceLoaderSupplier,
-                        mTabContentManager, mModalDialogManager, mBrowserControlsManager,
-                        mTabCreatorManager, mMenuOrKeyboardActionController, mShareDelegateSupplier,
-                        mMultiWindowModeStateDispatcher, mContainerView);
+        mSecondaryTasksSurface = TabManagementModuleProvider.getDelegate().createTasksSurface(
+                mActivity, mScrimCoordinator, propertyModel, TabSwitcherType.GRID,
+                mParentTabSupplier,
+                /* hasMVTiles= */ false, /* hasQueryTiles= */ false, mWindowAndroid,
+                mActivityLifecycleDispatcher, mTabModelSelector, mSnackbarManager,
+                mDynamicResourceLoaderSupplier, mTabContentManager, mModalDialogManager,
+                mBrowserControlsManager, mTabCreatorManager, mMenuOrKeyboardActionController,
+                mShareDelegateSupplier, mMultiWindowModeStateDispatcher, mContainerView);
         if (mIsInitializedWithNative) {
             mSecondaryTasksSurface.onFinishNativeInitialization(
                     mActivity, mOmniboxStubSupplier.get());
diff --git a/chrome/android/features/start_surface/public/java/src/org/chromium/chrome/features/start_surface/StartSurfaceConfiguration.java b/chrome/android/features/start_surface/public/java/src/org/chromium/chrome/features/start_surface/StartSurfaceConfiguration.java
index 3f701cc..84def972 100644
--- a/chrome/android/features/start_surface/public/java/src/org/chromium/chrome/features/start_surface/StartSurfaceConfiguration.java
+++ b/chrome/android/features/start_surface/public/java/src/org/chromium/chrome/features/start_surface/StartSurfaceConfiguration.java
@@ -40,6 +40,9 @@
     public static final BooleanCachedFieldTrialParameter START_SURFACE_EXCLUDE_MV_TILES =
             new BooleanCachedFieldTrialParameter(
                     ChromeFeatureList.START_SURFACE_ANDROID, "exclude_mv_tiles", false);
+    public static final BooleanCachedFieldTrialParameter START_SURFACE_EXCLUDE_QUERY_TILES =
+            new BooleanCachedFieldTrialParameter(
+                    ChromeFeatureList.START_SURFACE_ANDROID, "exclude_query_tiles", true);
     public static final BooleanCachedFieldTrialParameter
             START_SURFACE_HIDE_INCOGNITO_SWITCH_NO_TAB =
                     new BooleanCachedFieldTrialParameter(ChromeFeatureList.START_SURFACE_ANDROID,
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java
index 7c1e4b7..2fcf955 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/TasksSurfaceCoordinator.java
@@ -67,7 +67,7 @@
     public TasksSurfaceCoordinator(@NonNull Activity activity,
             @NonNull ScrimCoordinator scrimCoordinator, @NonNull PropertyModel propertyModel,
             @TabSwitcherType int tabSwitcherType, @NonNull Supplier<Tab> parentTabSupplier,
-            boolean hasMVTiles, @NonNull WindowAndroid windowAndroid,
+            boolean hasMVTiles, boolean hasQueryTiles, @NonNull WindowAndroid windowAndroid,
             @NonNull ActivityLifecycleDispatcher activityLifecycleDispatcher,
             @NonNull TabModelSelector tabModelSelector, @NonNull SnackbarManager snackbarManager,
             @NonNull Supplier<DynamicResourceLoader> dynamicResourceLoaderSupplier,
@@ -131,6 +131,9 @@
                     mPropertyModel, parentTabSupplier, snackbarManager, windowAndroid);
             mMostVisitedList.initialize();
         }
+        if (hasQueryTiles) {
+            // TODO(qinmin): show QueryTiles.
+        }
     }
 
     /**
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java
index efda7d5..533bdf3 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegate.java
@@ -97,7 +97,7 @@
     TasksSurface createTasksSurface(@NonNull Activity activity,
             @NonNull ScrimCoordinator scrimCoordinator, @NonNull PropertyModel propertyModel,
             @TabSwitcherType int tabSwitcherType, @NonNull Supplier<Tab> parentTabSupplier,
-            boolean hasMVTiles, @NonNull WindowAndroid windowAndroid,
+            boolean hasMVTiles, boolean hasQueryTiles, @NonNull WindowAndroid windowAndroid,
             @NonNull ActivityLifecycleDispatcher activityLifecycleDispatcher,
             @NonNull TabModelSelector tabModelSelector, @NonNull SnackbarManager snackbarManager,
             @NonNull Supplier<DynamicResourceLoader> dynamicResourceLoaderSupplier,
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java
index f455996..ae1fdd2 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabManagementDelegateImpl.java
@@ -64,7 +64,7 @@
     public TasksSurface createTasksSurface(@NonNull Activity activity,
             @NonNull ScrimCoordinator scrimCoordinator, @NonNull PropertyModel propertyModel,
             @TabSwitcherType int tabSwitcherType, @NonNull Supplier<Tab> parentTabSupplier,
-            boolean hasMVTiles, @NonNull WindowAndroid windowAndroid,
+            boolean hasMVTiles, boolean hasQueryTiles, @NonNull WindowAndroid windowAndroid,
             @NonNull ActivityLifecycleDispatcher activityLifecycleDispatcher,
             @NonNull TabModelSelector tabModelSelector, @NonNull SnackbarManager snackbarManager,
             @NonNull Supplier<DynamicResourceLoader> dynamicResourceLoaderSupplier,
@@ -77,7 +77,7 @@
             @NonNull MultiWindowModeStateDispatcher multiWindowModeStateDispatcher,
             @NonNull ViewGroup rootView) {
         return new TasksSurfaceCoordinator(activity, scrimCoordinator, propertyModel,
-                tabSwitcherType, parentTabSupplier, hasMVTiles, windowAndroid,
+                tabSwitcherType, parentTabSupplier, hasMVTiles, hasQueryTiles, windowAndroid,
                 activityLifecycleDispatcher, tabModelSelector, snackbarManager,
                 dynamicResourceLoaderSupplier, tabContentManager, modalDialogManager,
                 browserControlsStateProvider, tabCreatorManager, menuOrKeyboardActionController,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
index 6e59342b..7f0624fb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
@@ -142,6 +142,7 @@
                         add(StartSurfaceConfiguration.SIGNIN_PROMO_NTP_RESET_AFTER_HOURS);
                         add(StartSurfaceConfiguration.SPARE_RENDERER_DELAY_MS);
                         add(StartSurfaceConfiguration.START_SURFACE_EXCLUDE_MV_TILES);
+                        add(StartSurfaceConfiguration.START_SURFACE_EXCLUDE_QUERY_TILES);
                         add(StartSurfaceConfiguration.START_SURFACE_HIDE_INCOGNITO_SWITCH_NO_TAB);
                         add(StartSurfaceConfiguration.START_SURFACE_LAST_ACTIVE_TAB_ONLY);
                         add(StartSurfaceConfiguration.START_SURFACE_OPEN_NTP_INSTEAD_OF_START);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestRetryTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestRetryTest.java
index 36fc42e..3a25802f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestRetryTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestRetryTest.java
@@ -25,6 +25,7 @@
 import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.ChromeRenderTestRule;
+import org.chromium.components.payments.PaymentFeatureList;
 import org.chromium.ui.modaldialog.ModalDialogProperties;
 import org.chromium.ui.test.util.DisableAnimationsTestRule;
 import org.chromium.ui.test.util.RenderTestRule;
@@ -73,7 +74,8 @@
     @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testDoNotAllowPaymentAppChange() throws TimeoutException {
+    @CommandLineFlags.Add({"enable-features=" + PaymentFeatureList.PAYMENT_REQUEST_BASIC_CARD})
+    public void testDoNotAllowPaymentAppChange_WithBasicCardEnabled() throws TimeoutException {
         mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
         mPaymentRequestTestRule.clickAndWait(
                 R.id.button_primary, mPaymentRequestTestRule.getReadyForUnmaskInput());
@@ -92,6 +94,30 @@
         Assert.assertEquals(1, mPaymentRequestTestRule.getNumberOfPaymentApps());
     }
 
+    /** Tests that only the initially selected payment app is available during retry(). */
+    @Test
+    @MediumTest
+    @Feature({"Payments"})
+    @CommandLineFlags.Add({"disable-features=" + PaymentFeatureList.PAYMENT_REQUEST_BASIC_CARD})
+    public void testDoNotAllowPaymentAppChange() throws TimeoutException {
+        // Note that the bobpay app has been added in onMainActivityStarted(), so we will have two
+        // payment apps in total.
+        mPaymentRequestTestRule.addPaymentAppFactory(
+                "https://kylepay.com/webpay", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
+
+        mPaymentRequestTestRule.triggerUIAndWait(
+                "buyWithUrlMethod", mPaymentRequestTestRule.getReadyToPay());
+        Assert.assertEquals(2, mPaymentRequestTestRule.getNumberOfPaymentApps());
+        mPaymentRequestTestRule.clickInPaymentMethodAndWait(
+                R.id.payments_section, mPaymentRequestTestRule.getReadyForInput());
+        mPaymentRequestTestRule.clickAndWait(
+                R.id.button_primary, mPaymentRequestTestRule.getPaymentResponseReady());
+
+        // Confirm that only one payment app is available for retry().
+        mPaymentRequestTestRule.retryPaymentRequest("{}", mPaymentRequestTestRule.getReadyToPay());
+        Assert.assertEquals(1, mPaymentRequestTestRule.getNumberOfPaymentApps());
+    }
+
     /**
      * Tests that adding new cards is disabled during retry().
      */
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerExpandablePaymentHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerExpandablePaymentHandlerTest.java
deleted file mode 100644
index 98eb372..0000000
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerExpandablePaymentHandlerTest.java
+++ /dev/null
@@ -1,375 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.payments;
-
-import android.app.Activity;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.drawable.BitmapDrawable;
-
-import androidx.test.filters.MediumTest;
-
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import org.chromium.base.test.util.CommandLineFlags;
-import org.chromium.base.test.util.Feature;
-import org.chromium.chrome.R;
-import org.chromium.chrome.browser.ActivityUtils;
-import org.chromium.chrome.browser.autofill.AutofillTestHelper;
-import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
-import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
-import org.chromium.chrome.browser.flags.ChromeSwitches;
-import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
-import org.chromium.components.payments.PaymentAppFactoryDelegate;
-import org.chromium.components.payments.PaymentAppFactoryInterface;
-import org.chromium.components.payments.PaymentAppService;
-import org.chromium.components.payments.PaymentAppServiceBridge;
-import org.chromium.components.payments.PaymentFeatureList;
-import org.chromium.components.payments.SupportedDelegations;
-import org.chromium.content_public.browser.WebContents;
-import org.chromium.ui.test.util.DisableAnimationsTestRule;
-
-import java.util.concurrent.TimeoutException;
-
-/**
- * A payment integration test for service worker based payment apps.
- */
-@RunWith(ChromeJUnit4ClassRunner.class)
-@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
-        // Prevent crawling the web for real payment apps.
-        "disable-features=" + PaymentFeatureList.SERVICE_WORKER_PAYMENT_APPS})
-public class PaymentRequestServiceWorkerExpandablePaymentHandlerTest {
-    // Disable animations to reduce flakiness.
-    @ClassRule
-    public static DisableAnimationsTestRule sNoAnimationsRule = new DisableAnimationsTestRule();
-
-    @Rule
-    public PaymentRequestTestRule mPaymentRequestTestRule = new PaymentRequestTestRule(
-            "payment_request_bobpay_and_basic_card_with_modifier_optional_data_test.html");
-
-    /**
-     * Installs a mock service worker based payment app with given supported delegations for
-     * testing.
-     *
-     * @param scope                Service worker scope that identifies the payment app. Must be
-     *                             unique.
-     * @param supportedMethodNames The supported payment methods of the mock payment app.
-     * @param name                 The name of the mocked payment app.
-     * @param withIcon             Whether provide payment app icon.
-     * @param supportedDelegations The supported delegations of the mock payment app.
-     */
-    private void installMockServiceWorkerPaymentApp(String scope, String[] supportedMethodNames,
-            String name, boolean withIcon, SupportedDelegations supportedDelegations) {
-        PaymentAppService.getInstance().addFactory(new PaymentAppFactoryInterface() {
-            @Override
-            public void create(PaymentAppFactoryDelegate delegate) {
-                WebContents webContents = delegate.getParams().getWebContents();
-                Activity activity = ActivityUtils.getActivityFromWebContents(webContents);
-                BitmapDrawable icon = withIcon
-                        ? new BitmapDrawable(activity.getResources(),
-                                Bitmap.createBitmap(new int[] {Color.RED}, 1 /* width */,
-                                        1 /* height */, Bitmap.Config.ARGB_8888))
-                        : null;
-                delegate.onCanMakePaymentCalculated(true);
-                delegate.onPaymentAppCreated(new MockPaymentApp(/*identifier=*/scope, name, icon,
-                        supportedMethodNames, supportedDelegations));
-                delegate.onDoneCreatingPaymentApps(this);
-            }
-        });
-    }
-
-    /**
-     * Installs a mock service worker based payment app with no supported delegations for testing.
-     *
-     * @param scope                The service worker scope that identifies this payment app. Must
-     *                             be unique.
-     * @param supportedMethodNames The supported payment methods of the mock payment app.
-     * @param withName             Whether provide payment app name.
-     * @param withIcon             Whether provide payment app icon.
-     */
-    private void installMockServiceWorkerPaymentApp(
-            String scope, String[] supportedMethodNames, boolean withName, boolean withIcon) {
-        installMockServiceWorkerPaymentApp(scope, supportedMethodNames, withName ? "BobPay" : null,
-                withIcon, new SupportedDelegations());
-    }
-
-    /**
-     * Installs a mock service worker based payment app for bobpay with given supported delegations
-     * for testing.
-     *
-     * @param scope             The service worker scope that identifies this payment app. Must be
-     *                          unique.
-     * @param shippingAddress   Whether or not the mock payment app provides shipping address.
-     * @param payerName         Whether or not the mock payment app provides payer's name.
-     * @param payerPhone        Whether or not the mock payment app provides payer's phone number.
-     * @param payerEmail        Whether or not the mock payment app provides payer's email address.
-     * @param name              The name of the mocked payment app.
-     */
-    private void installMockServiceWorkerPaymentAppWithDelegations(String scope,
-            boolean shippingAddress, boolean payerName, boolean payerPhone, boolean payerEmail,
-            String name) {
-        String[] supportedMethodNames = {"https://bobpay.xyz"};
-        installMockServiceWorkerPaymentApp(scope, supportedMethodNames, name, true /*withIcon*/,
-                new SupportedDelegations(shippingAddress, payerName, payerPhone, payerEmail));
-    }
-
-    /**
-     * Adds a credit cart to ensure that autofill app is available.
-     */
-    public void addCreditCard() throws TimeoutException {
-        AutofillTestHelper helper = new AutofillTestHelper();
-        String billingAddressId = helper.setProfile(
-                new AutofillProfile("", "https://example.com", true, "" /* honorific prefix */,
-                        "John Smith", "Google", "340 Main St", "CA", "Los Angeles", "", "90291", "",
-                        "US", "310-310-6000", "john.smith@gmail.com", "en-US"));
-        helper.setCreditCard(new CreditCard("", "https://example.com", true, true, "Jon Doe",
-                "4111111111111111", "1111", "12", "2050", "visa", R.drawable.visa_card,
-                billingAddressId, "" /* serverId */));
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"Payments"})
-    public void testNoSupportedPaymentMethods() throws TimeoutException {
-        mPaymentRequestTestRule.openPageAndClickNodeAndWait(
-                "buy_with_bobpay", mPaymentRequestTestRule.getShowFailed());
-        mPaymentRequestTestRule.expectResultContains(
-                new String[] {"show() rejected", "The payment method", "not supported"});
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"Payments"})
-    public void testHasSupportedPaymentMethods() throws TimeoutException {
-        String[] supportedMethodNames = {"https://bobpay.com"};
-        installMockServiceWorkerPaymentApp("https://bobpay.com", supportedMethodNames, true, true);
-
-        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);
-        // Payment sheet skips to the app since it is the only available app.
-        mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getDismissed());
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"Payments"})
-    public void testDoNotCallCanMakePayment() throws TimeoutException {
-        // Add a credit card to force showing payment sheet UI.
-        addCreditCard();
-        String[] supportedMethodNames = {"basic-card"};
-        installMockServiceWorkerPaymentApp("https://bobpay.com", supportedMethodNames, true, true);
-
-        // Sets setCanMakePaymentForTesting(false) to return false for CanMakePayment since there is
-        // no real sw payment app, so if CanMakePayment is called then no payment apps will be
-        // available, otherwise CanMakePayment is not called.
-        PaymentAppServiceBridge.setCanMakePaymentForTesting(false);
-
-        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
-        Assert.assertEquals(2, mPaymentRequestTestRule.getNumberOfPaymentApps());
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"Payments"})
-    public void testCanPreselect() throws TimeoutException {
-        String[] supportedMethodNames = {"https://bobpay.com"};
-        installMockServiceWorkerPaymentApp("https://bobpay.com", supportedMethodNames, true, true);
-
-        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);
-
-        // Payment sheet skips to the app since it is the only available app.
-        mPaymentRequestTestRule.openPageAndClickBuyAndWait(mPaymentRequestTestRule.getDismissed());
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"Payments"})
-    public void testCanNotPreselectWithoutName() throws TimeoutException {
-        String[] supportedMethodNames = {"https://bobpay.com"};
-        installMockServiceWorkerPaymentApp("https://bobpay.com", supportedMethodNames, false, true);
-
-        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);
-
-        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
-        Assert.assertNull(mPaymentRequestTestRule.getSelectedPaymentAppLabel());
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"Payments"})
-    public void testCanNotPreselectWithoutIcon() throws TimeoutException {
-        String[] supportedMethodNames = {"https://bobpay.com"};
-        installMockServiceWorkerPaymentApp("https://bobpay.com", supportedMethodNames, true, false);
-
-        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);
-
-        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
-        Assert.assertNull(mPaymentRequestTestRule.getSelectedPaymentAppLabel());
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"Payments"})
-    public void testCanNotPreselectWithoutNameAndIcon() throws TimeoutException {
-        String[] supportedMethodNames = {"https://bobpay.com"};
-        installMockServiceWorkerPaymentApp(
-                "https://bobpay.com", supportedMethodNames, false, false);
-
-        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);
-
-        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
-        Assert.assertNull(mPaymentRequestTestRule.getSelectedPaymentAppLabel());
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"Payments"})
-    public void testPaymentAppProvidingShippingComesFirst() throws TimeoutException {
-        installMockServiceWorkerPaymentAppWithDelegations("https://alicepay.com" /*scope*/,
-                false /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/,
-                false /*payerEmail*/, "noSupportedDelegation" /*name*/);
-        installMockServiceWorkerPaymentAppWithDelegations("https://bobpay.com" /*scope*/,
-                true /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/,
-                false /*payerEmail*/, "shippingSupported1" /*name */);
-        // Install the second app supporting shipping delegation to force showing payment sheet.
-        installMockServiceWorkerPaymentAppWithDelegations("https://charliepay.com" /*scope*/,
-                true /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/,
-                false /*payerEmail*/, "shippingSupported2" /*name */);
-
-        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);
-
-        mPaymentRequestTestRule.triggerUIAndWait(
-                "buy_with_shipping_requested", mPaymentRequestTestRule.getReadyForInput());
-        Assert.assertEquals(3, mPaymentRequestTestRule.getNumberOfPaymentApps());
-
-        // The payment app which provides shipping address must be preselected.
-        Assert.assertTrue(
-                mPaymentRequestTestRule.getSelectedPaymentAppLabel().contains("shippingSupported"));
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"Payments"})
-    public void testPaymentAppProvidingContactComesFirst() throws TimeoutException {
-        installMockServiceWorkerPaymentAppWithDelegations("https://alicepay.com" /*scope*/,
-                false /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/,
-                false /*payerEmail*/, "noSupportedDelegation" /*name*/);
-        installMockServiceWorkerPaymentAppWithDelegations("https://bobpay.com" /*scope*/,
-                false /*shippingAddress*/, true /*payerName*/, true /*payerPhone*/,
-                true /*payerEmail*/, "contactSupported" /*name */);
-        installMockServiceWorkerPaymentAppWithDelegations("https://charliepay.com" /*scope*/,
-                false /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/,
-                true /*payerEmail*/, "emailOnlySupported" /*name */);
-        // Install the second app supporting contact delegation to force showing payment sheet.
-        installMockServiceWorkerPaymentAppWithDelegations("https://davepay.com" /*scope*/,
-                false /*shippingAddress*/, true /*payerName*/, true /*payerPhone*/,
-                true /*payerEmail*/, "contactSupported2" /*name */);
-
-        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);
-
-        mPaymentRequestTestRule.triggerUIAndWait(
-                "buy_with_contact_requested", mPaymentRequestTestRule.getReadyForInput());
-        Assert.assertEquals(4, mPaymentRequestTestRule.getNumberOfPaymentApps());
-
-        // The payment app which provides full contact details must be preselected.
-        Assert.assertTrue(
-                mPaymentRequestTestRule.getSelectedPaymentAppLabel().contains("contactSupported"));
-        // The payment app which partially provides the required contact details comes before the
-        // one that provides no contact information.
-        Assert.assertTrue(mPaymentRequestTestRule.getPaymentMethodSuggestionLabel(2).contains(
-                "emailOnlySupported"));
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"Payments"})
-    public void testPaymentAppProvidingAllRequiredInfoComesFirst() throws TimeoutException {
-        installMockServiceWorkerPaymentAppWithDelegations("https://alicepay.com" /*scope*/,
-                true /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/,
-                false /*payerEmail*/, "shippingSupported" /*name */);
-        installMockServiceWorkerPaymentAppWithDelegations("https://bobpay.com" /*scope*/,
-                false /*shippingAddress*/, true /*payerName*/, true /*payerPhone*/,
-                true /*payerEmail*/, "contactSupported" /*name */);
-        installMockServiceWorkerPaymentAppWithDelegations("https://charliepay.com" /*scope*/,
-                true /*shippingAddress*/, true /*payerName*/, true /*payerPhone*/,
-                true /*payerEmail*/, "shippingAndContactSupported" /*name*/);
-        // Install the second app supporting both shipping and contact delegations to force showing
-        // payment sheet.
-        installMockServiceWorkerPaymentAppWithDelegations("https://davepay.com" /*scope*/,
-                true /*shippingAddress*/, true /*payerName*/, true /*payerPhone*/,
-                true /*payerEmail*/, "shippingAndContactSupported2" /*name*/);
-
-        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);
-
-        mPaymentRequestTestRule.triggerUIAndWait("buy_with_shipping_and_contact_requested",
-                mPaymentRequestTestRule.getReadyForInput());
-        Assert.assertEquals(4, mPaymentRequestTestRule.getNumberOfPaymentApps());
-
-        // The payment app which provides all required information must be preselected.
-        Assert.assertTrue(mPaymentRequestTestRule.getSelectedPaymentAppLabel().contains(
-                "shippingAndContactSupported"));
-        // The payment app which provides shipping comes before the one which provides contact
-        // details when both required by merchant.
-        Assert.assertTrue(mPaymentRequestTestRule.getPaymentMethodSuggestionLabel(2).contains(
-                "shippingSupported"));
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"Payments"})
-    public void testSkipsToSinglePaymentAppProvidingShipping() throws TimeoutException {
-        installMockServiceWorkerPaymentAppWithDelegations("https://alicepay.com" /*scope*/,
-                false /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/,
-                false /*payerEmail*/, "noSupportedDelegation" /*name*/);
-        installMockServiceWorkerPaymentAppWithDelegations("https://bobpay.com" /*scope*/,
-                true /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/,
-                false /*payerEmail*/, "shippingSupported" /*name */);
-
-        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);
-        mPaymentRequestTestRule.openPageAndClickNodeAndWait(
-                "buy_with_shipping_requested", mPaymentRequestTestRule.getDismissed());
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"Payments"})
-    public void testSkipsToSinglePaymentAppProvidingContact() throws TimeoutException {
-        installMockServiceWorkerPaymentAppWithDelegations("https://alicepay.com" /*scope*/,
-                false /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/,
-                false /*payerEmail*/, "noSupportedDelegation" /*name*/);
-        installMockServiceWorkerPaymentAppWithDelegations("https://bobpay.com" /*scope*/,
-                false /*shippingAddress*/, true /*payerName*/, true /*payerPhone*/,
-                true /*payerEmail*/, "contactSupported" /*name */);
-        installMockServiceWorkerPaymentAppWithDelegations("https://charliepay.com" /*scope*/,
-                false /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/,
-                true /*payerEmail*/, "emailOnlySupported" /*name */);
-
-        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);
-        mPaymentRequestTestRule.openPageAndClickNodeAndWait(
-                "buy_with_contact_requested", mPaymentRequestTestRule.getDismissed());
-    }
-
-    @Test
-    @MediumTest
-    @Feature({"Payments"})
-    public void testSkipsToSinglePaymentAppProvidingAllRequiredInfo() throws TimeoutException {
-        installMockServiceWorkerPaymentAppWithDelegations("https://alicepay.com" /*scope*/,
-                true /*shippingAddress*/, false /*payerName*/, false /*payerPhone*/,
-                false /*payerEmail*/, "shippingSupported" /*name */);
-        installMockServiceWorkerPaymentAppWithDelegations("https://bobpay.com" /*scope*/,
-                false /*shippingAddress*/, true /*payerName*/, true /*payerPhone*/,
-                true /*payerEmail*/, "contactSupported" /*name */);
-        installMockServiceWorkerPaymentAppWithDelegations("https://charliepay.com" /*scope*/,
-                true /*shippingAddress*/, true /*payerName*/, true /*payerPhone*/,
-                true /*payerEmail*/, "shippingAndContactSupported" /*name*/);
-
-        PaymentAppServiceBridge.setCanMakePaymentForTesting(true);
-        mPaymentRequestTestRule.openPageAndClickNodeAndWait(
-                "buy_with_shipping_and_contact_requested", mPaymentRequestTestRule.getDismissed());
-    }
-}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerPaymentAppTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerPaymentAppTest.java
index 224cdd92..94672c0 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerPaymentAppTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestServiceWorkerPaymentAppTest.java
@@ -158,7 +158,8 @@
     @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testDoNotCallCanMakePayment() throws TimeoutException {
+    @CommandLineFlags.Add({"enable-features=PaymentRequestBasicCard"})
+    public void testDoNotCallCanMakePayment_WithBasicCardEnabled() throws TimeoutException {
         // Add a credit card to force showing payment sheet UI.
         addCreditCard();
         String[] supportedMethodNames = {"basic-card"};
@@ -176,6 +177,27 @@
     @Test
     @MediumTest
     @Feature({"Payments"})
+    @CommandLineFlags.Add({"disable-features=PaymentRequestBasicCard"})
+    public void testDoNotCallCanMakePayment() throws TimeoutException {
+        String[] supportedMethodNames1 = {"https://bobpay.com"};
+        installMockServiceWorkerPaymentApp("https://bobpay.com", supportedMethodNames1, true, true);
+
+        String[] supportedMethodNames2 = {"https://kylepay.com/webpay"};
+        installMockServiceWorkerPaymentApp(
+                "https://kylepay.com/webpay", supportedMethodNames2, true, true);
+
+        // Sets setCanMakePaymentForTesting(false) to return false for CanMakePayment since there is
+        // no real sw payment app, so if CanMakePayment is called then no payment apps will be
+        // available, otherwise CanMakePayment is not called.
+        PaymentAppServiceBridge.setCanMakePaymentForTesting(false);
+
+        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        Assert.assertEquals(2, mPaymentRequestTestRule.getNumberOfPaymentApps());
+    }
+
+    @Test
+    @MediumTest
+    @Feature({"Payments"})
     public void testCanPreselect() throws TimeoutException {
         String[] supportedMethodNames = {"https://bobpay.com"};
         installMockServiceWorkerPaymentApp("https://bobpay.com", supportedMethodNames, true, true);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseEmptyTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseEmptyTest.java
index 773429e..1e53cfa 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseEmptyTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestShowPromiseEmptyTest.java
@@ -22,6 +22,7 @@
 import org.chromium.chrome.browser.payments.PaymentRequestTestRule.FactorySpeed;
 import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.components.payments.PaymentFeatureList;
 import org.chromium.ui.test.util.DisableAnimationsTestRule;
 
 import java.util.concurrent.TimeoutException;
@@ -50,7 +51,8 @@
     @Test
     @MediumTest
     @Feature({"Payments"})
-    public void testResolveWithEmptyDictionary() throws TimeoutException {
+    @CommandLineFlags.Add({"enable-features=" + PaymentFeatureList.PAYMENT_REQUEST_BASIC_CARD})
+    public void testResolveWithEmptyDictionary_WithBasicCard() throws TimeoutException {
         mRule.addPaymentAppFactory("basic-card", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
         mRule.triggerUIAndWait(mRule.getReadyToPay());
 
@@ -70,4 +72,31 @@
 
         mRule.expectResultContains(new String[] {"3.00", "shipping-option-identifier"});
     }
+
+    @Test
+    @MediumTest
+    @Feature({"Payments"})
+    @CommandLineFlags.Add({"disable-features=" + PaymentFeatureList.PAYMENT_REQUEST_BASIC_CARD})
+    public void testResolveWithEmptyDictionary() throws TimeoutException {
+        mRule.addPaymentAppFactory(
+                "https://bobpay.com", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
+
+        mRule.triggerUIAndWait("buyWithUrlMethod", mRule.getReadyToPay());
+
+        Assert.assertEquals("USD $3.00", mRule.getOrderSummaryTotal());
+
+        mRule.clickInOrderSummaryAndWait(mRule.getReadyToPay());
+
+        Assert.assertEquals(2, mRule.getNumberOfLineItems());
+        Assert.assertEquals("$1.00", mRule.getLineItemAmount(0));
+        Assert.assertEquals("$1.00", mRule.getLineItemAmount(1));
+
+        mRule.clickInShippingAddressAndWait(R.id.payments_section, mRule.getReadyToPay());
+
+        Assert.assertEquals(null, mRule.getShippingAddressDescriptionLabel());
+
+        mRule.clickAndWait(R.id.button_primary, mRule.getDismissed());
+
+        mRule.expectResultContains(new String[] {"3.00", "shipping-option-identifier"});
+    }
 }
\ No newline at end of file
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTabTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTabTest.java
index 1d03f5da..383a9887 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTabTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTabTest.java
@@ -20,6 +20,8 @@
 import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
 import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.AppPresence;
+import org.chromium.chrome.browser.payments.PaymentRequestTestRule.FactorySpeed;
 import org.chromium.chrome.browser.payments.PaymentRequestTestRule.MainActivityStartCallback;
 import org.chromium.chrome.browser.tab.TabLaunchType;
 import org.chromium.chrome.browser.tabmodel.TabModel;
@@ -44,7 +46,7 @@
 
     @Rule
     public PaymentRequestTestRule mPaymentRequestTestRule =
-            new PaymentRequestTestRule("payment_request_dynamic_shipping_test.html", this);
+            new PaymentRequestTestRule("payment_request_metrics_test.html", this);
 
     @Override
     public void onMainActivityStarted() throws TimeoutException {
@@ -63,7 +65,14 @@
     @MediumTest
     @Feature({"Payments"})
     public void testDismissOnTabSwitch() throws TimeoutException {
-        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        // Install two apps to force showing the payment request UI.
+        mPaymentRequestTestRule.addPaymentAppFactory(
+                "https://bobpay.com", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
+        mPaymentRequestTestRule.addPaymentAppFactory(
+                "https://kylepay.com/webpay", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
+
+        mPaymentRequestTestRule.triggerUIAndWait(
+                "buyWithUrlMethods", mPaymentRequestTestRule.getReadyToPay());
         Assert.assertEquals(0, mPaymentRequestTestRule.getDismissed().getCallCount());
         TestThreadUtils.runOnUiThreadBlocking(
                 (Runnable) () -> mPaymentRequestTestRule.getActivity().getTabCreator(false).createNewTab(
@@ -79,7 +88,14 @@
     @Test
     @DisabledTest
     public void testDismissOnTabClose() throws TimeoutException {
-        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        // Install two apps to force showing the payment request UI.
+        mPaymentRequestTestRule.addPaymentAppFactory(
+                "https://bobpay.com", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
+        mPaymentRequestTestRule.addPaymentAppFactory(
+                "https://kylepay.com/webpay", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
+
+        mPaymentRequestTestRule.triggerUIAndWait(
+                "buyWithUrlMethods", mPaymentRequestTestRule.getReadyToPay());
         Assert.assertEquals(0, mPaymentRequestTestRule.getDismissed().getCallCount());
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             TabModel currentModel = mPaymentRequestTestRule.getActivity().getCurrentTabModel();
@@ -93,7 +109,14 @@
     @MediumTest
     @Feature({"Payments"})
     public void testDismissOnTabNavigate() throws TimeoutException {
-        mPaymentRequestTestRule.triggerUIAndWait(mPaymentRequestTestRule.getReadyForInput());
+        // Install two apps to force showing the payment request UI.
+        mPaymentRequestTestRule.addPaymentAppFactory(
+                "https://bobpay.com", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
+        mPaymentRequestTestRule.addPaymentAppFactory(
+                "https://kylepay.com/webpay", AppPresence.HAVE_APPS, FactorySpeed.FAST_FACTORY);
+
+        mPaymentRequestTestRule.triggerUIAndWait(
+                "buyWithUrlMethods", mPaymentRequestTestRule.getReadyToPay());
         Assert.assertEquals(0, mPaymentRequestTestRule.getDismissed().getCallCount());
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             TabModel currentModel = mPaymentRequestTestRule.getActivity().getCurrentTabModel();
diff --git a/chrome/app/chromeos_shared_strings.grdp b/chrome/app/chromeos_shared_strings.grdp
index b6f9951f..f4eced8 100644
--- a/chrome/app/chromeos_shared_strings.grdp
+++ b/chrome/app/chromeos_shared_strings.grdp
@@ -4,20 +4,6 @@
      Everything in this file is wrapped in <if expr="chromeos or lacros">. -->
 <grit-part>
 
-  <!-- Slow UI -->
-  <message name="IDS_SLOW_DISABLE" desc="The text of the button that disables performance collection for feedback reports">
-    Disable performance data collection
-  </message>
-  <message name="IDS_SLOW_ENABLE" desc="The text of the button that enables performance collection for feedback reports">
-    Enable performance data collection
-  </message>
-  <message name="IDS_SLOW_DESCRIPTION" desc="The description of the performance tracing feature for feedback reports.">
-    Enabling collection of performance data will help Google improve the system over time. No data is sent until you file a feedback report (Alt-Shift-I) and include performance data. You can return to this screen to disable collection at any time.
-  </message>
-  <message name="IDS_SLOW_WARNING" desc="The warning that informs users that enabling this can have a negative effect on their performance">
-    <ph name="BEGIN_BOLD">&lt;strong&gt;</ph>Note:<ph name="END_BOLD">&lt;/strong&gt;</ph> Only enable if you know what you are doing or if you have been asked to do so, as collection of data may reduce performance.
-  </message>
-
   <!-- Settings -->
   <message name="IDS_OS_SETTINGS_PEOPLE_V2" desc="Name of a section in the OS settings page." meaning="People and their accounts.">
     Accounts
diff --git a/chrome/app/chromeos_strings.grdp b/chrome/app/chromeos_strings.grdp
index f3c3e67..588a79c 100644
--- a/chrome/app/chromeos_strings.grdp
+++ b/chrome/app/chromeos_strings.grdp
@@ -1715,6 +1715,20 @@
     No language
   </message>
 
+  <!-- Slow UI -->
+  <message name="IDS_SLOW_DISABLE" desc="The text of the button that disables performance collection for feedback reports">
+    Disable performance data collection
+  </message>
+  <message name="IDS_SLOW_ENABLE" desc="The text of the button that enables performance collection for feedback reports">
+    Enable performance data collection
+  </message>
+  <message name="IDS_SLOW_DESCRIPTION" desc="The description of the performance tracing feature for feedback reports.">
+    Enabling collection of performance data will help Google improve the system over time. No data is sent until you file a feedback report (Alt-Shift-I) and include performance data. You can return to this screen to disable collection at any time.
+  </message>
+  <message name="IDS_SLOW_WARNING" desc="The warning that informs users that enabling this can have a negative effect on their performance">
+    <ph name="BEGIN_BOLD">&lt;strong&gt;</ph>Note:<ph name="END_BOLD">&lt;/strong&gt;</ph> Only enable if you know what you are doing or if you have been asked to do so, as collection of data may reduce performance.
+  </message>
+
   <!-- EULA -->
   <message name="IDS_EULA_BACK_BUTTON" desc="Back button shown on EULA/OOBE screens.">
     Back
diff --git a/chrome/app/chromeos_shared_strings_grdp/IDS_SLOW_DESCRIPTION.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_SLOW_DESCRIPTION.png.sha1
similarity index 100%
rename from chrome/app/chromeos_shared_strings_grdp/IDS_SLOW_DESCRIPTION.png.sha1
rename to chrome/app/chromeos_strings_grdp/IDS_SLOW_DESCRIPTION.png.sha1
diff --git a/chrome/app/chromeos_shared_strings_grdp/IDS_SLOW_DISABLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_SLOW_DISABLE.png.sha1
similarity index 100%
rename from chrome/app/chromeos_shared_strings_grdp/IDS_SLOW_DISABLE.png.sha1
rename to chrome/app/chromeos_strings_grdp/IDS_SLOW_DISABLE.png.sha1
diff --git a/chrome/app/chromeos_shared_strings_grdp/IDS_SLOW_ENABLE.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_SLOW_ENABLE.png.sha1
similarity index 100%
rename from chrome/app/chromeos_shared_strings_grdp/IDS_SLOW_ENABLE.png.sha1
rename to chrome/app/chromeos_strings_grdp/IDS_SLOW_ENABLE.png.sha1
diff --git a/chrome/app/chromeos_shared_strings_grdp/IDS_SLOW_WARNING.png.sha1 b/chrome/app/chromeos_strings_grdp/IDS_SLOW_WARNING.png.sha1
similarity index 100%
rename from chrome/app/chromeos_shared_strings_grdp/IDS_SLOW_WARNING.png.sha1
rename to chrome/app/chromeos_strings_grdp/IDS_SLOW_WARNING.png.sha1
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 3cefe87..934d76c 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -8736,6 +8736,17 @@
       <message name="IDS_SYNC_CONFIRMATION_SETTINGS_BUTTON_LABEL" desc="Label of the button in the sync confirmation dialog of the tab modal signin flow to open settings">
         Settings
       </message>
+      <message name="IDS_SYNC_CONFIRMATION_REFRESHED_SETTINGS_BUTTON_LABEL" desc="Label of the button in the sync confirmation screen to open sync settings">
+        Sync settings
+      </message>
+      <if expr="lacros">
+        <message name="IDS_SYNC_CONFIRMATION_TITLE_LACROS" desc="Title of the sync confirmation dialog/screen on lacros.">
+          Chrome browser sync is on
+        </message>
+        <message name="IDS_SYNC_CONFIRMATION_SYNC_INFO_TITLE_LACROS" desc="Text in the information about sync on the sync confirmation dialog/screen on lacros.">
+          Your bookmarks, passwords, history, and more are synced on all your devices
+        </message>
+      </if>
 
       <!--- Sync Confirmation section of the tab modal signin flow when sync is disabled by policy -->
       <message name="IDS_SYNC_DISABLED_CONFIRMATION_CHROME_SYNC_TITLE" desc="Title of the chrome sync section of the sync confirmation dialog in the tab modal signin flow when sync is disabled by policy">
diff --git a/chrome/app/generated_resources_grd/IDS_SYNC_CONFIRMATION_REFRESHED_SETTINGS_BUTTON_LABEL.png.sha1 b/chrome/app/generated_resources_grd/IDS_SYNC_CONFIRMATION_REFRESHED_SETTINGS_BUTTON_LABEL.png.sha1
new file mode 100644
index 0000000..261617f5
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_SYNC_CONFIRMATION_REFRESHED_SETTINGS_BUTTON_LABEL.png.sha1
@@ -0,0 +1 @@
+580504ddc7936eef13e346f0bf01143d2b59ec9b
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_SYNC_CONFIRMATION_SYNC_INFO_TITLE_LACROS.png.sha1 b/chrome/app/generated_resources_grd/IDS_SYNC_CONFIRMATION_SYNC_INFO_TITLE_LACROS.png.sha1
new file mode 100644
index 0000000..a98a995
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_SYNC_CONFIRMATION_SYNC_INFO_TITLE_LACROS.png.sha1
@@ -0,0 +1 @@
+0256ddab84c8d97a175de1e4d388cfbae91fd1de
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_SYNC_CONFIRMATION_TITLE_LACROS.png.sha1 b/chrome/app/generated_resources_grd/IDS_SYNC_CONFIRMATION_TITLE_LACROS.png.sha1
new file mode 100644
index 0000000..a98a995
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_SYNC_CONFIRMATION_TITLE_LACROS.png.sha1
@@ -0,0 +1 @@
+0256ddab84c8d97a175de1e4d388cfbae91fd1de
\ No newline at end of file
diff --git a/chrome/app/nearby_share_strings_grdp/DIR_METADATA b/chrome/app/nearby_share_strings_grdp/DIR_METADATA
index 135a13c..3d76e46 100644
--- a/chrome/app/nearby_share_strings_grdp/DIR_METADATA
+++ b/chrome/app/nearby_share_strings_grdp/DIR_METADATA
@@ -1,3 +1,3 @@
 monorail: {
-  component: "UI>Browser>Sharing>Nearby"
+  component: "OS>Systems>Multidevice>Nearby"
 }
diff --git a/chrome/app/sharesheet_strings.grdp b/chrome/app/sharesheet_strings.grdp
index c3f1fbfdc..5722853f 100644
--- a/chrome/app/sharesheet_strings.grdp
+++ b/chrome/app/sharesheet_strings.grdp
@@ -28,4 +28,7 @@
   <message name="IDS_SHARESHEET_COPY_TO_CLIPBOARD_SHARE_ACTION_LABEL" desc="The label for the button in the sharesheet that copies the data the user has selected to the clipboard.">
     Copy
   </message>
+  <message name="IDS_SHARESHEET_COPY_TO_CLIPBOARD_SUCCESS_TOAST_LABEL" desc="The label for the system toast at the bottom of the screen that notifies the user that their selected data has been copied to the clipboard.">
+    Copied
+  </message>
 </grit-part>
diff --git a/chrome/app/sharesheet_strings_grdp/IDS_SHARESHEET_COPY_TO_CLIPBOARD_SUCCESS_TOAST_LABEL.png.sha1 b/chrome/app/sharesheet_strings_grdp/IDS_SHARESHEET_COPY_TO_CLIPBOARD_SUCCESS_TOAST_LABEL.png.sha1
new file mode 100644
index 0000000..d73b02f9
--- /dev/null
+++ b/chrome/app/sharesheet_strings_grdp/IDS_SHARESHEET_COPY_TO_CLIPBOARD_SUCCESS_TOAST_LABEL.png.sha1
@@ -0,0 +1 @@
+ec1864726f0a72231e36d98c198c6afb27e872f6
\ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 69a74812..ea6e7d7 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3539,6 +3539,10 @@
      flag_descriptions::kChromeSharingHubLaunchAdjacentName,
      flag_descriptions::kChromeSharingHubLaunchAdjacentDescription, kOsAndroid,
      FEATURE_VALUE_TYPE(chrome::android::kChromeSharingHubLaunchAdjacent)},
+    {"persist-share-hub-on-app-switch",
+     flag_descriptions::kPersistShareHubOnAppSwitchName,
+     flag_descriptions::kPersistShareHubOnAppSwitchDescription, kOsAndroid,
+     FEATURE_VALUE_TYPE(share::kPersistShareHubOnAppSwitch)},
     {"webnotes-stylize", flag_descriptions::kWebNotesStylizeName,
      flag_descriptions::kWebNotesStylizeDescription, kOsAndroid,
      FEATURE_WITH_PARAMS_VALUE_TYPE(content_creation::kWebNotesStylizeEnabled,
diff --git a/chrome/browser/apps/app_service/publishers/arc_apps.cc b/chrome/browser/apps/app_service/publishers/arc_apps.cc
index 8bb55a07..d08b8cb 100644
--- a/chrome/browser/apps/app_service/publishers/arc_apps.cc
+++ b/chrome/browser/apps/app_service/publishers/arc_apps.cc
@@ -9,6 +9,7 @@
 
 #include "ash/components/arc/arc_util.h"
 #include "ash/components/arc/metrics/arc_metrics_constants.h"
+#include "ash/components/arc/metrics/arc_metrics_service.h"
 #include "ash/constants/ash_features.h"
 #include "ash/public/cpp/app_menu_constants.h"
 #include "base/bind.h"
@@ -44,8 +45,6 @@
 #include "components/app_restore/features.h"
 #include "components/app_restore/full_restore_save_handler.h"
 #include "components/app_restore/full_restore_utils.h"
-#include "components/arc/intent_helper/intent_constants.h"
-#include "components/arc/metrics/arc_metrics_service.h"
 #include "components/arc/mojom/app_permissions.mojom.h"
 #include "components/arc/mojom/compatibility_mode.mojom.h"
 #include "components/arc/mojom/file_system.mojom.h"
diff --git a/chrome/browser/apps/intent_helper/metrics/intent_handling_metrics.cc b/chrome/browser/apps/intent_helper/metrics/intent_handling_metrics.cc
index e5028fe..8a6c328 100644
--- a/chrome/browser/apps/intent_helper/metrics/intent_handling_metrics.cc
+++ b/chrome/browser/apps/intent_helper/metrics/intent_handling_metrics.cc
@@ -8,7 +8,7 @@
 #include "base/metrics/histogram_macros.h"
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
-#include "components/arc/metrics/arc_metrics_service.h"
+#include "ash/components/arc/metrics/arc_metrics_service.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 namespace apps {
diff --git a/chrome/browser/apps/intent_helper/metrics/intent_handling_metrics_unittest.cc b/chrome/browser/apps/intent_helper/metrics/intent_handling_metrics_unittest.cc
index e565e10..cc618282 100644
--- a/chrome/browser/apps/intent_helper/metrics/intent_handling_metrics_unittest.cc
+++ b/chrome/browser/apps/intent_helper/metrics/intent_handling_metrics_unittest.cc
@@ -14,10 +14,10 @@
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/components/arc/arc_prefs.h"
+#include "ash/components/arc/metrics/arc_metrics_service.h"
+#include "ash/components/arc/metrics/stability_metrics_manager.h"
 #include "chrome/browser/ash/arc/intent_helper/arc_external_protocol_dialog.h"
 #include "components/arc/intent_helper/arc_intent_helper_bridge.h"
-#include "components/arc/metrics/arc_metrics_service.h"
-#include "components/arc/metrics/stability_metrics_manager.h"
 #include "components/arc/session/arc_service_manager.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
diff --git a/chrome/browser/ash/accessibility/dictation_browsertest.cc b/chrome/browser/ash/accessibility/dictation_browsertest.cc
index ae89d08..cd05f65 100644
--- a/chrome/browser/ash/accessibility/dictation_browsertest.cc
+++ b/chrome/browser/ash/accessibility/dictation_browsertest.cc
@@ -121,6 +121,84 @@
   base::RunLoop run_loop_;
 };
 
+class TextMatchesWaiter {
+ public:
+  TextMatchesWaiter(const std::string& expected,
+                    base::RepeatingCallback<std::string()> checker)
+      : expected_(expected), checker_(std::move(checker)) {}
+  ~TextMatchesWaiter() = default;
+  TextMatchesWaiter(const TextMatchesWaiter&) = delete;
+  TextMatchesWaiter& operator=(const TextMatchesWaiter&) = delete;
+
+  void Wait() {
+    base::RepeatingTimer check_timer;
+    check_timer.Start(FROM_HERE, base::Milliseconds(10), this,
+                      &TextMatchesWaiter::OnTimer);
+    run_loop_.Run();
+  }
+
+ private:
+  void OnTimer() {
+    if (checker_.Run() == expected_)
+      run_loop_.Quit();
+  }
+
+  std::string expected_;
+  base::RepeatingCallback<std::string()> checker_;
+  base::RunLoop run_loop_;
+};
+
+class CaretBoundsChangedWaiter : public ui::InputMethodObserver {
+ public:
+  explicit CaretBoundsChangedWaiter(ui::InputMethod* input_method)
+      : input_method_(input_method) {
+    input_method_->AddObserver(this);
+  }
+  CaretBoundsChangedWaiter(const CaretBoundsChangedWaiter&) = delete;
+  CaretBoundsChangedWaiter& operator=(const CaretBoundsChangedWaiter&) = delete;
+  ~CaretBoundsChangedWaiter() override { input_method_->RemoveObserver(this); }
+
+  void Wait() { run_loop_.Run(); }
+
+ private:
+  // ui::InputMethodObserver:
+  void OnFocus() override {}
+  void OnBlur() override {}
+  void OnTextInputStateChanged(const ui::TextInputClient* client) override {}
+  void OnShowVirtualKeyboardIfEnabled() override {}
+  void OnInputMethodDestroyed(const ui::InputMethod* input_method) override {}
+  void OnCaretBoundsChanged(const ui::TextInputClient* client) override {
+    run_loop_.Quit();
+  }
+
+  ui::InputMethod* input_method_;
+  base::RunLoop run_loop_;
+};
+
+// Listens for changes to the clipboard. This class only allows `Wait()` to be
+// called once. If you need to call `Wait()` multiple times, create multiple
+// instances of this class.
+class ClipboardChangedWaiterOneShot : public ui::ClipboardObserver {
+ public:
+  ClipboardChangedWaiterOneShot() {
+    ui::ClipboardMonitor::GetInstance()->AddObserver(this);
+  }
+  ClipboardChangedWaiterOneShot(const ClipboardChangedWaiterOneShot&) = delete;
+  ClipboardChangedWaiterOneShot& operator=(
+      const ClipboardChangedWaiterOneShot&) = delete;
+  ~ClipboardChangedWaiterOneShot() override {
+    ui::ClipboardMonitor::GetInstance()->RemoveObserver(this);
+  }
+
+  void Wait() { run_loop_.Run(); }
+
+ private:
+  // ui::ClipboardObserver:
+  void OnClipboardDataChanged() override { run_loop_.Quit(); }
+
+  base::RunLoop run_loop_;
+};
+
 }  // namespace
 
 // This class performs common setup and teardown operations for Dictation tests,
@@ -573,33 +651,6 @@
   }
 }
 
-class TextMatchesWaiter {
- public:
-  TextMatchesWaiter(const std::string& expected,
-                    base::RepeatingCallback<std::string()> checker)
-      : expected_(expected), checker_(std::move(checker)) {}
-  ~TextMatchesWaiter() = default;
-  TextMatchesWaiter(const TextMatchesWaiter&) = delete;
-  TextMatchesWaiter& operator=(const TextMatchesWaiter&) = delete;
-
-  void Wait() {
-    base::RepeatingTimer check_timer;
-    check_timer.Start(FROM_HERE, base::Milliseconds(10), this,
-                      &TextMatchesWaiter::OnTimer);
-    run_loop_.Run();
-  }
-
- private:
-  void OnTimer() {
-    if (checker_.Run() == expected_)
-      run_loop_.Quit();
-  }
-
-  std::string expected_;
-  base::RepeatingCallback<std::string()> checker_;
-  base::RunLoop run_loop_;
-};
-
 // TODO(crbug.com/1216111): Use a MockIMEInputContextHandler to check
 // composition after supporting interim results.
 class DictationExtensionTest : public DictationBaseTest {
@@ -785,57 +836,6 @@
   }
 }
 
-class CaretBoundsChangedWaiter : public ui::InputMethodObserver {
- public:
-  explicit CaretBoundsChangedWaiter(ui::InputMethod* input_method)
-      : input_method_(input_method) {
-    input_method_->AddObserver(this);
-  }
-  CaretBoundsChangedWaiter(const CaretBoundsChangedWaiter&) = delete;
-  CaretBoundsChangedWaiter& operator=(const CaretBoundsChangedWaiter&) = delete;
-  ~CaretBoundsChangedWaiter() override { input_method_->RemoveObserver(this); }
-
-  void Wait() { run_loop_.Run(); }
-
- private:
-  // ui::InputMethodObserver:
-  void OnFocus() override {}
-  void OnBlur() override {}
-  void OnTextInputStateChanged(const ui::TextInputClient* client) override {}
-  void OnShowVirtualKeyboardIfEnabled() override {}
-  void OnInputMethodDestroyed(const ui::InputMethod* input_method) override {}
-  void OnCaretBoundsChanged(const ui::TextInputClient* client) override {
-    run_loop_.Quit();
-  }
-
-  ui::InputMethod* input_method_;
-  base::RunLoop run_loop_;
-};
-
-// Listens for changes to the clipboard. This class only allows `Wait()` to be
-// called once. If you need to call `Wait()` multiple times, create multiple
-// instances of this class.
-class ClipboardChangedWaiterOneShot : public ui::ClipboardObserver {
- public:
-  ClipboardChangedWaiterOneShot() {
-    ui::ClipboardMonitor::GetInstance()->AddObserver(this);
-  }
-  ClipboardChangedWaiterOneShot(const ClipboardChangedWaiterOneShot&) = delete;
-  ClipboardChangedWaiterOneShot& operator=(
-      const ClipboardChangedWaiterOneShot&) = delete;
-  ~ClipboardChangedWaiterOneShot() override {
-    ui::ClipboardMonitor::GetInstance()->RemoveObserver(this);
-  }
-
-  void Wait() { run_loop_.Run(); }
-
- private:
-  // ui::ClipboardObserver:
-  void OnClipboardDataChanged() override { run_loop_.Quit(); }
-
-  base::RunLoop run_loop_;
-};
-
 class DictationCommandsExtensionTest : public DictationExtensionTest {
  protected:
   DictationCommandsExtensionTest() {}
diff --git a/chrome/browser/ash/app_restore/app_restore_arc_task_handler.cc b/chrome/browser/ash/app_restore/app_restore_arc_task_handler.cc
index 5d9d49a..7713934a 100644
--- a/chrome/browser/ash/app_restore/app_restore_arc_task_handler.cc
+++ b/chrome/browser/ash/app_restore/app_restore_arc_task_handler.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/ash/app_restore/app_restore_arc_task_handler.h"
 
+#include "ash/constants/ash_features.h"
 #include "chrome/browser/ash/app_restore/app_restore_arc_task_handler_factory.h"
 #include "chrome/browser/ash/app_restore/arc_app_launch_handler.h"
 #include "chrome/browser/ash/app_restore/arc_window_handler.h"
@@ -11,7 +12,6 @@
 #include "chrome/browser/ash/arc/session/arc_session_manager.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/app_restore/app_restore_arc_info.h"
-#include "components/app_restore/features.h"
 
 namespace ash {
 namespace app_restore {
@@ -41,7 +41,7 @@
     window_handler_ = std::make_unique<full_restore::ArcWindowHandler>();
 #endif
 
-  if (::app_restore::features::IsArcAppsForDesksTemplatesEnabled()) {
+  if (ash::features::AreDesksTemplatesEnabled()) {
     desks_templates_arc_app_launch_handler_ =
         std::make_unique<ArcAppLaunchHandler>();
   }
diff --git a/chrome/browser/ash/arc/arc_optin_uma.cc b/chrome/browser/ash/arc/arc_optin_uma.cc
index 19f4949..c6987a8a 100644
--- a/chrome/browser/ash/arc/arc_optin_uma.cc
+++ b/chrome/browser/ash/arc/arc_optin_uma.cc
@@ -7,6 +7,7 @@
 #include <string>
 
 #include "ash/components/arc/arc_util.h"
+#include "ash/components/arc/metrics/stability_metrics_manager.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "chrome/browser/ash/arc/arc_util.h"
@@ -15,7 +16,6 @@
 #include "chrome/browser/ash/login/demo_mode/demo_session.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
-#include "components/arc/metrics/stability_metrics_manager.h"
 #include "components/arc/mojom/app.mojom.h"
 #include "components/arc/mojom/auth.mojom.h"
 
diff --git a/chrome/browser/ash/arc/metrics/arc_metrics_service_proxy.h b/chrome/browser/ash/arc/metrics/arc_metrics_service_proxy.h
index bfd7661e..40d3636 100644
--- a/chrome/browser/ash/arc/metrics/arc_metrics_service_proxy.h
+++ b/chrome/browser/ash/arc/metrics/arc_metrics_service_proxy.h
@@ -5,9 +5,9 @@
 #ifndef CHROME_BROWSER_ASH_ARC_METRICS_ARC_METRICS_SERVICE_PROXY_H_
 #define CHROME_BROWSER_ASH_ARC_METRICS_ARC_METRICS_SERVICE_PROXY_H_
 
+#include "ash/components/arc/metrics/arc_metrics_service.h"
 #include "chrome/browser/ash/arc/session/arc_session_manager_observer.h"
 #include "chrome/browser/ui/app_list/arc/arc_app_list_prefs.h"
-#include "components/arc/metrics/arc_metrics_service.h"
 #include "components/keyed_service/core/keyed_service.h"
 
 namespace content {
diff --git a/chrome/browser/ash/arc/session/arc_service_launcher.cc b/chrome/browser/ash/arc/session/arc_service_launcher.cc
index b090e06..9cb5e53 100644
--- a/chrome/browser/ash/arc/session/arc_service_launcher.cc
+++ b/chrome/browser/ash/arc/session/arc_service_launcher.cc
@@ -17,6 +17,11 @@
 #include "ash/components/arc/crash_collector/arc_crash_collector_bridge.h"
 #include "ash/components/arc/dark_theme/arc_dark_theme_bridge.h"
 #include "ash/components/arc/disk_quota/arc_disk_quota_bridge.h"
+#include "ash/components/arc/keyboard_shortcut/arc_keyboard_shortcut_bridge.h"
+#include "ash/components/arc/lock_screen/arc_lock_screen_bridge.h"
+#include "ash/components/arc/memory_pressure/arc_memory_pressure_bridge.h"
+#include "ash/components/arc/metrics/arc_metrics_service.h"
+#include "ash/components/arc/midis/arc_midis_bridge.h"
 #include "ash/constants/ash_features.h"
 #include "base/bind.h"
 #include "base/check_op.h"
@@ -73,12 +78,7 @@
 #include "components/arc/ime/arc_ime_service.h"
 #include "components/arc/input_overlay/arc_input_overlay_manager.h"
 #include "components/arc/intent_helper/arc_intent_helper_bridge.h"
-#include "components/arc/keyboard_shortcut/arc_keyboard_shortcut_bridge.h"
-#include "components/arc/lock_screen/arc_lock_screen_bridge.h"
 #include "components/arc/media_session/arc_media_session_bridge.h"
-#include "components/arc/memory_pressure/arc_memory_pressure_bridge.h"
-#include "components/arc/metrics/arc_metrics_service.h"
-#include "components/arc/midis/arc_midis_bridge.h"
 #include "components/arc/net/arc_net_host_impl.h"
 #include "components/arc/obb_mounter/arc_obb_mounter_bridge.h"
 #include "components/arc/pay/arc_digital_goods_bridge.h"
diff --git a/chrome/browser/ash/arc/session/arc_session_manager.cc b/chrome/browser/ash/arc/session/arc_session_manager.cc
index 70356996..b882e9b01 100644
--- a/chrome/browser/ash/arc/session/arc_session_manager.cc
+++ b/chrome/browser/ash/arc/session/arc_session_manager.cc
@@ -11,6 +11,8 @@
 #include "ash/components/arc/arc_prefs.h"
 #include "ash/components/arc/arc_util.h"
 #include "ash/components/arc/metrics/arc_metrics_constants.h"
+#include "ash/components/arc/metrics/arc_metrics_service.h"
+#include "ash/components/arc/metrics/stability_metrics_manager.h"
 #include "ash/constants/ash_switches.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
@@ -58,8 +60,6 @@
 #include "chromeos/dbus/session_manager/session_manager_client.h"
 #include "chromeos/system/statistics_provider.h"
 #include "components/account_id/account_id.h"
-#include "components/arc/metrics/arc_metrics_service.h"
-#include "components/arc/metrics/stability_metrics_manager.h"
 #include "components/arc/session/arc_data_remover.h"
 #include "components/arc/session/arc_dlc_installer.h"
 #include "components/arc/session/arc_instance_mode.h"
diff --git a/chrome/browser/ash/child_accounts/parent_access_code/authenticator.cc b/chrome/browser/ash/child_accounts/parent_access_code/authenticator.cc
index b11540c..a14dba9 100644
--- a/chrome/browser/ash/child_accounts/parent_access_code/authenticator.cc
+++ b/chrome/browser/ash/child_accounts/parent_access_code/authenticator.cc
@@ -152,7 +152,7 @@
   int32_t result;
   std::vector<uint8_t> slice(digest.begin() + offset,
                              digest.begin() + offset + sizeof(result));
-  base::ReadBigEndian(reinterpret_cast<char*>(slice.data()), &result);
+  base::ReadBigEndian(slice.data(), &result);
   // Clear sign bit.
   result &= 0x7fffffff;
 
diff --git a/chrome/browser/ash/file_manager/arc_file_tasks.cc b/chrome/browser/ash/file_manager/arc_file_tasks.cc
index e4df7504..e14a12d3 100644
--- a/chrome/browser/ash/file_manager/arc_file_tasks.cc
+++ b/chrome/browser/ash/file_manager/arc_file_tasks.cc
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "ash/components/arc/metrics/arc_metrics_constants.h"
+#include "ash/components/arc/metrics/arc_metrics_service.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/files/file_path.h"
@@ -29,7 +30,6 @@
 #include "chrome/common/extensions/api/file_manager_private.h"
 #include "components/arc/intent_helper/arc_intent_helper_bridge.h"
 #include "components/arc/intent_helper/intent_constants.h"
-#include "components/arc/metrics/arc_metrics_service.h"
 #include "components/arc/mojom/file_system.mojom.h"
 #include "components/arc/mojom/intent_helper.mojom.h"
 #include "components/arc/session/arc_bridge_service.h"
diff --git a/chrome/browser/ash/note_taking_helper.cc b/chrome/browser/ash/note_taking_helper.cc
index 89c750dc..14b2cc9 100644
--- a/chrome/browser/ash/note_taking_helper.cc
+++ b/chrome/browser/ash/note_taking_helper.cc
@@ -9,6 +9,7 @@
 
 #include "apps/launcher.h"
 #include "ash/components/arc/metrics/arc_metrics_constants.h"
+#include "ash/components/arc/metrics/arc_metrics_service.h"
 #include "ash/constants/ash_switches.h"
 #include "ash/public/cpp/stylus_utils.h"
 #include "base/bind.h"
@@ -35,7 +36,6 @@
 #include "chrome/browser/web_applications/web_app_id_constants.h"
 #include "chrome/common/pref_names.h"
 #include "components/arc/intent_helper/arc_intent_helper_bridge.h"
-#include "components/arc/metrics/arc_metrics_service.h"
 #include "components/arc/mojom/file_system.mojom.h"
 #include "components/arc/mojom/intent_common.mojom.h"
 #include "components/arc/mojom/intent_helper.mojom.h"
diff --git a/chrome/browser/ash/sharesheet/copy_to_clipboard_share_action.cc b/chrome/browser/ash/sharesheet/copy_to_clipboard_share_action.cc
index cc691409..640b8c71 100644
--- a/chrome/browser/ash/sharesheet/copy_to_clipboard_share_action.cc
+++ b/chrome/browser/ash/sharesheet/copy_to_clipboard_share_action.cc
@@ -6,6 +6,8 @@
 
 #include <string>
 
+#include "ash/public/cpp/toast_data.h"
+#include "ash/public/cpp/toast_manager.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "chrome/browser/apps/app_service/file_utils.h"
 #include "chrome/browser/profiles/profile.h"
@@ -20,6 +22,11 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/views/view.h"
 
+namespace {
+const char kToastId[] = "copy_to_clipboard_share_action";
+const int kToastDurationMs = 2500;
+}  // namespace
+
 namespace ash {
 namespace sharesheet {
 
@@ -75,7 +82,14 @@
     clipboard_writer.WriteFilenames(ui::FileInfosToURIList(file_infos));
   }
 
-  // TODO(crbug.com/1244143) Add image copying logic.
+  ToastData toast(kToastId,
+                  l10n_util::GetStringUTF16(
+                      IDS_SHARESHEET_COPY_TO_CLIPBOARD_SUCCESS_TOAST_LABEL),
+                  kToastDurationMs,
+                  /*dismiss_text=*/absl::nullopt,
+                  /*visible_on_lock_screen=*/false);
+  ToastManager::Get()->Show(toast);
+
   if (controller_) {
     controller_->CloseBubble(::sharesheet::SharesheetResult::kSuccess);
   }
diff --git a/chrome/browser/ash/sharesheet/copy_to_clipboard_share_action_unittest.cc b/chrome/browser/ash/sharesheet/copy_to_clipboard_share_action_unittest.cc
new file mode 100644
index 0000000..3e808bc
--- /dev/null
+++ b/chrome/browser/ash/sharesheet/copy_to_clipboard_share_action_unittest.cc
@@ -0,0 +1,123 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ash/sharesheet/copy_to_clipboard_share_action.h"
+
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/sharesheet/share_action/share_action_cache.h"
+#include "chrome/browser/sharesheet/sharesheet_test_util.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/grit/generated_resources.h"
+#include "chrome/test/base/chrome_ash_test_base.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/services/app_service/public/cpp/intent_util.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/clipboard/file_info.h"
+#include "ui/base/clipboard/test/clipboard_test_util.h"
+#include "ui/base/clipboard/test/test_clipboard.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/chromeos/strings/grit/ui_chromeos_strings.h"
+#include "url/gurl.h"
+
+namespace ash {
+namespace sharesheet {
+
+class CopyToClipboardShareActionTest : public ChromeAshTestBase {
+ public:
+  CopyToClipboardShareActionTest() = default;
+
+  // ChromeAshTestBase:
+  void SetUp() override {
+    ChromeAshTestBase::SetUp();
+
+    scoped_feature_list_.InitAndEnableFeature(
+        features::kSharesheetCopyToClipboard);
+    profile_ = std::make_unique<TestingProfile>();
+    share_action_cache_ =
+        std::make_unique<::sharesheet::ShareActionCache>(profile_.get());
+  }
+
+  Profile* profile() { return profile_.get(); }
+  ::sharesheet::ShareActionCache* share_action_cache() {
+    return share_action_cache_.get();
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+  std::unique_ptr<TestingProfile> profile_;
+  std::unique_ptr<::sharesheet::ShareActionCache> share_action_cache_;
+};
+
+TEST_F(CopyToClipboardShareActionTest, CopyToClipboardText) {
+  auto* copy_action =
+      share_action_cache()->GetActionFromName(l10n_util::GetStringUTF16(
+          IDS_SHARESHEET_COPY_TO_CLIPBOARD_SHARE_ACTION_LABEL));
+  copy_action->LaunchAction(/*controller=*/nullptr, /*root_view=*/nullptr,
+                            ::sharesheet::CreateValidTextIntent());
+  // Check text copied correctly.
+  std::u16string clipboard_text;
+  ui::Clipboard::GetForCurrentThread()->ReadText(
+      ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+      &clipboard_text);
+  EXPECT_EQ(::sharesheet::kTestText, base::UTF16ToUTF8(clipboard_text));
+}
+
+TEST_F(CopyToClipboardShareActionTest, CopyToClipboardUrl) {
+  auto* copy_action =
+      share_action_cache()->GetActionFromName(l10n_util::GetStringUTF16(
+          IDS_SHARESHEET_COPY_TO_CLIPBOARD_SHARE_ACTION_LABEL));
+  copy_action->LaunchAction(/*controller=*/nullptr, /*root_view=*/nullptr,
+                            ::sharesheet::CreateValidUrlIntent());
+  // Check url copied correctly.
+  std::u16string clipboard_url;
+  ui::Clipboard::GetForCurrentThread()->ReadText(
+      ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
+      &clipboard_url);
+  EXPECT_EQ(::sharesheet::kTestUrl, base::UTF16ToUTF8(clipboard_url));
+}
+
+TEST_F(CopyToClipboardShareActionTest, CopyToClipboardOneFile) {
+  auto* copy_action =
+      share_action_cache()->GetActionFromName(l10n_util::GetStringUTF16(
+          IDS_SHARESHEET_COPY_TO_CLIPBOARD_SHARE_ACTION_LABEL));
+  storage::FileSystemURL url = ::sharesheet::FileInDownloads(
+      profile(), base::FilePath(::sharesheet::kTestTextFile));
+  copy_action->LaunchAction(
+      /*controller=*/nullptr, /*root_view=*/nullptr,
+      apps_util::CreateShareIntentFromFiles({url.ToGURL()},
+                                            {::sharesheet::kMimeTypeText}));
+
+  // Check filenames copied correctly.
+  std::vector<ui::FileInfo> filenames;
+  ui::Clipboard::GetForCurrentThread()->ReadFilenames(
+      ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr, &filenames);
+  EXPECT_EQ(filenames.size(), 1u);
+  EXPECT_EQ(url.path(), filenames[0].path);
+}
+
+TEST_F(CopyToClipboardShareActionTest, CopyToClipboardMultipleFiles) {
+  auto* copy_action =
+      share_action_cache()->GetActionFromName(l10n_util::GetStringUTF16(
+          IDS_SHARESHEET_COPY_TO_CLIPBOARD_SHARE_ACTION_LABEL));
+  storage::FileSystemURL url1 = ::sharesheet::FileInDownloads(
+      profile(), base::FilePath(::sharesheet::kTestPdfFile));
+  storage::FileSystemURL url2 = ::sharesheet::FileInDownloads(
+      profile(), base::FilePath(::sharesheet::kTestTextFile));
+  copy_action->LaunchAction(
+      /*controller=*/nullptr, /*root_view=*/nullptr,
+      apps_util::CreateShareIntentFromFiles(
+          {url1.ToGURL(), url2.ToGURL()},
+          {::sharesheet::kMimeTypePdf, ::sharesheet::kMimeTypeText}));
+
+  // Check filenames copied correctly.
+  std::vector<ui::FileInfo> filenames;
+  ui::Clipboard::GetForCurrentThread()->ReadFilenames(
+      ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr, &filenames);
+  EXPECT_EQ(filenames.size(), 2u);
+  EXPECT_EQ(url1.path(), filenames[0].path);
+  EXPECT_EQ(url2.path(), filenames[1].path);
+}
+
+}  // namespace sharesheet
+}  // namespace ash
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc
index 27de017..5682a3e 100644
--- a/chrome/browser/chrome_browser_main.cc
+++ b/chrome/browser/chrome_browser_main.cc
@@ -212,11 +212,11 @@
 #endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
+#include "ash/components/arc/metrics/stability_metrics_manager.h"
 #include "ash/components/settings/cros_settings_names.h"
 #include "ash/constants/ash_switches.h"
 #include "chrome/browser/ash/settings/cros_settings.h"
 #include "chrome/browser/ash/settings/stats_reporting_controller.h"
-#include "components/arc/metrics/stability_metrics_manager.h"
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 // TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index fbda6f6..7c6061f1 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -241,6 +241,7 @@
 #include "components/variations/variations_associated_data.h"
 #include "components/variations/variations_switches.h"
 #include "content/public/browser/browser_child_process_host.h"
+#include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_main_parts.h"
 #include "content/public/browser/browser_ppapi_host.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -1313,6 +1314,9 @@
                                 true);
   registry->RegisterDictionaryPref(
       policy::policy_prefs::kCopyPreventionSettings);
+  registry->RegisterIntegerPref(
+      prefs::kUserAgentReduction,
+      UserAgentReductionEnterprisePolicyState::kDefault);
 }
 
 // static
@@ -5309,7 +5313,7 @@
                                            cert_verifier_creation_params);
   } else {
     // Set default params.
-    network_context_params->user_agent = GetUserAgent();
+    network_context_params->user_agent = GetUserAgentBasedOnPolicy(context);
     network_context_params->accept_language = GetApplicationLocale();
   }
 }
@@ -5788,6 +5792,19 @@
   return embedder_support::GetUserAgent();
 }
 
+std::string ChromeContentBrowserClient::GetUserAgentBasedOnPolicy(
+    content::BrowserContext* context) {
+  switch (GetUserAgentReductionEnterprisePolicyState(context)) {
+    case UserAgentReductionEnterprisePolicyState::kForceDisabled:
+      return embedder_support::GetFullUserAgent();
+    case UserAgentReductionEnterprisePolicyState::kForceEnabled:
+      return GetReducedUserAgent();
+    case UserAgentReductionEnterprisePolicyState::kDefault:
+    default:
+      return GetUserAgent();
+  }
+}
+
 std::string ChromeContentBrowserClient::GetReducedUserAgent() {
   return embedder_support::GetReducedUserAgent();
 }
@@ -6308,3 +6325,20 @@
   }
 }
 #endif
+
+ChromeContentBrowserClient::UserAgentReductionEnterprisePolicyState
+ChromeContentBrowserClient::GetUserAgentReductionEnterprisePolicyState(
+    content::BrowserContext* context) {
+  int policy = Profile::FromBrowserContext(context)->GetPrefs()->GetInteger(
+      prefs::kUserAgentReduction);
+  switch (policy) {
+    case 0:
+      return UserAgentReductionEnterprisePolicyState::kDefault;
+    case 1:
+      return UserAgentReductionEnterprisePolicyState::kForceDisabled;
+    case 2:
+      return UserAgentReductionEnterprisePolicyState::kForceEnabled;
+  }
+
+  return UserAgentReductionEnterprisePolicyState::kDefault;
+}
diff --git a/chrome/browser/chrome_content_browser_client.h b/chrome/browser/chrome_content_browser_client.h
index a4f28371..9c4089f 100644
--- a/chrome/browser/chrome_content_browser_client.h
+++ b/chrome/browser/chrome_content_browser_client.h
@@ -634,6 +634,8 @@
 
   std::string GetProduct() override;
   std::string GetUserAgent() override;
+  std::string GetUserAgentBasedOnPolicy(
+      content::BrowserContext* context) override;
   std::string GetReducedUserAgent() override;
   blink::UserAgentMetadata GetUserAgentMetadata() override;
 
@@ -754,6 +756,12 @@
 
   bool IsFindInPageDisabledForOrigin(const url::Origin& origin) override;
 
+  enum UserAgentReductionEnterprisePolicyState {
+    kDefault = 0,
+    kForceDisabled = 1,
+    kForceEnabled = 2,
+  };
+
  protected:
   static bool HandleWebUI(GURL* url, content::BrowserContext* browser_context);
   static bool HandleWebUIReverse(GURL* url,
@@ -840,6 +848,9 @@
       std::unique_ptr<ScopedKeepAlive> keep_alive_handle);
 #endif
 
+  UserAgentReductionEnterprisePolicyState
+  GetUserAgentReductionEnterprisePolicyState(content::BrowserContext* context);
+
   // Vector of additional ChromeContentBrowserClientParts.
   // Parts are deleted in the reverse order they are added.
   std::vector<ChromeContentBrowserClientParts*> extra_parts_;
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 9b58d57..050b3180 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -4442,6 +4442,7 @@
     "../ash/settings/shutdown_policy_handler_unittest.cc",
     "../ash/settings/stats_reporting_controller_unittest.cc",
     "../ash/settings/stub_cros_settings_provider_unittest.cc",
+    "../ash/sharesheet/copy_to_clipboard_share_action_unittest.cc",
     "../ash/smb_client/discovery/fake_netbios_client.cc",
     "../ash/smb_client/discovery/fake_netbios_client.h",
     "../ash/smb_client/discovery/in_memory_host_locator_unittest.cc",
@@ -4581,7 +4582,6 @@
     "../ui/webui/settings/chromeos/device_name_handler_unittest.cc",
     "../ui/webui/settings/chromeos/device_storage_handler_unittest.cc",
     "../ui/webui/settings/chromeos/internet_handler_unittest.cc",
-    "../ui/webui/settings/chromeos/metrics_consent_handler_unittest.cc",
     "../ui/webui/settings/chromeos/multidevice_handler_unittest.cc",
     "../ui/webui/settings/chromeos/os_apps_page/app_notification_handler_unittest.cc",
     "../ui/webui/settings/chromeos/os_settings_manager_unittest.cc",
diff --git a/chrome/browser/devtools/devtools_ui_bindings.cc b/chrome/browser/devtools/devtools_ui_bindings.cc
index bce9e24..c0f38108 100644
--- a/chrome/browser/devtools/devtools_ui_bindings.cc
+++ b/chrome/browser/devtools/devtools_ui_bindings.cc
@@ -635,9 +635,6 @@
 
 void DevToolsUIBindings::FrontendWebContentsObserver::DidFinishNavigation(
     content::NavigationHandle* navigation_handle) {
-  // TODO(https://crbug.com/1218946): With MPArch there may be multiple main
-  // frames. This caller was converted automatically to the primary main frame
-  // to preserve its semantics. Follow up to confirm correctness.
   if (navigation_handle->IsInPrimaryMainFrame() &&
       navigation_handle->HasCommitted())
     devtools_bindings_->DidNavigateMainFrame();
@@ -1648,9 +1645,6 @@
 
 void DevToolsUIBindings::ReadyToCommitNavigation(
     content::NavigationHandle* navigation_handle) {
-  // TODO(https://crbug.com/1218946): With MPArch there may be multiple main
-  // frames. This caller was converted automatically to the primary main frame
-  // to preserve its semantics. Follow up to confirm correctness.
   if (navigation_handle->IsInPrimaryMainFrame()) {
     if (frontend_loaded_ && agent_host_.get()) {
       agent_host_->DetachClient(this);
@@ -1662,11 +1656,8 @@
       frontend_host_.reset();
       return;
     }
-    if (navigation_handle->GetRenderFrameHost() ==
-            web_contents_->GetMainFrame() &&
-        frontend_host_) {
+    if (frontend_host_)
       return;
-    }
     if (content::RenderFrameHost* opener = web_contents_->GetOpener()) {
       content::WebContents* opener_wc =
           content::WebContents::FromRenderFrameHost(opener);
diff --git a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/storage/StorageSummaryProvider.java b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/storage/StorageSummaryProvider.java
index f80da9e..350fd48 100644
--- a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/storage/StorageSummaryProvider.java
+++ b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/home/storage/StorageSummaryProvider.java
@@ -82,6 +82,8 @@
             @Override
             protected DirectoryOption doInBackground() {
                 File defaultDownloadDir = DownloadDirectoryProvider.getPrimaryDownloadDirectory();
+                if (defaultDownloadDir == null) return null;
+
                 DirectoryOption directoryOption = new DirectoryOption("",
                         defaultDownloadDir.getAbsolutePath(), defaultDownloadDir.getUsableSpace(),
                         defaultDownloadDir.getTotalSpace(),
diff --git a/chrome/browser/extensions/extension_view_host.cc b/chrome/browser/extensions/extension_view_host.cc
index 4bd58a0..1373c76e 100644
--- a/chrome/browser/extensions/extension_view_host.cc
+++ b/chrome/browser/extensions/extension_view_host.cc
@@ -30,28 +30,6 @@
 
 namespace extensions {
 
-// Notifies an ExtensionViewHost when a WebContents is destroyed.
-class ExtensionViewHost::AssociatedWebContentsObserver
-    : public content::WebContentsObserver {
- public:
-  AssociatedWebContentsObserver(ExtensionViewHost* host,
-                                content::WebContents* web_contents)
-      : WebContentsObserver(web_contents), host_(host) {}
-  AssociatedWebContentsObserver(const AssociatedWebContentsObserver&) = delete;
-  AssociatedWebContentsObserver& operator=(
-      const AssociatedWebContentsObserver&) = delete;
-  ~AssociatedWebContentsObserver() override = default;
-
-  // content::WebContentsObserver:
-  void WebContentsDestroyed() override {
-    // Deleting |this| from here is safe.
-    host_->SetAssociatedWebContents(nullptr);
-  }
-
- private:
-  ExtensionViewHost* host_;
-};
-
 ExtensionViewHost::ExtensionViewHost(const Extension* extension,
                                      content::SiteInstance* site_instance,
                                      const GURL& url,
@@ -108,15 +86,8 @@
 
 void ExtensionViewHost::SetAssociatedWebContents(
     content::WebContents* web_contents) {
-  associated_web_contents_ = web_contents;
-  if (associated_web_contents_) {
-    // Observe the new WebContents for deletion.
-    associated_web_contents_observer_ =
-        std::make_unique<AssociatedWebContentsObserver>(
-            this, associated_web_contents_);
-  } else {
-    associated_web_contents_observer_.reset();
-  }
+  associated_web_contents_ =
+      web_contents ? web_contents->GetWeakPtr() : nullptr;
 }
 
 bool ExtensionViewHost::UnhandledKeyboardEvent(
@@ -278,12 +249,12 @@
 }
 
 content::WebContents* ExtensionViewHost::GetAssociatedWebContents() const {
-  return associated_web_contents_;
+  return associated_web_contents_.get();
 }
 
 content::WebContents* ExtensionViewHost::GetVisibleWebContents() const {
   if (associated_web_contents_)
-    return associated_web_contents_;
+    return associated_web_contents_.get();
   return (extension_host_type() == mojom::ViewType::kExtensionPopup)
              ? host_contents()
              : nullptr;
diff --git a/chrome/browser/extensions/extension_view_host.h b/chrome/browser/extensions/extension_view_host.h
index d799ed98..7beb899 100644
--- a/chrome/browser/extensions/extension_view_host.h
+++ b/chrome/browser/extensions/extension_view_host.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/memory/weak_ptr.h"
 #include "base/scoped_observation.h"
 #include "build/build_config.h"
 #include "components/web_modal/web_contents_modal_dialog_host.h"
@@ -127,12 +128,7 @@
   ExtensionView* view_ = nullptr;
 
   // The relevant WebContents associated with this ExtensionViewHost, if any.
-  content::WebContents* associated_web_contents_ = nullptr;
-
-  // Observer to detect when the associated web contents is destroyed.
-  class AssociatedWebContentsObserver;
-  std::unique_ptr<AssociatedWebContentsObserver>
-      associated_web_contents_observer_;
+  base::WeakPtr<content::WebContents> associated_web_contents_;
 
   base::ScopedObservation<ExtensionHostRegistry,
                           ExtensionHostRegistry::Observer>
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index ace4d8d..38d55149 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -4412,6 +4412,11 @@
     "expiry_milestone": 100
   },
   {
+    "name": "persist-share-hub-on-app-switch",
+    "owners": [ "ellyjones", "chrome-sharing-eng@google.com" ],
+    "expiry_milestone": 100
+  },
+  {
     "name": "persistent-quota-is-temporary-quota",
     "owners": [ "mek", "cfredric" ],
     "expiry_milestone": 100
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index bfccc20f..7471e3a 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3300,6 +3300,10 @@
     "users and allows users to change their signed-in password through "
     "password reuse warnings on phishing or low reputation sites.";
 
+const char kPersistShareHubOnAppSwitchName[] = "Persist sharing hub";
+const char kPersistShareHubOnAppSwitchDescription[] =
+    "Persist the sharing hub across app pauses/resumes.";
+
 const char kPhotoPickerVideoSupportName[] = "Photo Picker Video Support";
 const char kPhotoPickerVideoSupportDescription[] =
     "Enables video files to be shown in the Photo Picker dialog";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index bfe2eff..e9de20d 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1902,6 +1902,9 @@
 extern const char kPasswordProtectionForSignedInUsersName[];
 extern const char kPasswordProtectionForSignedInUsersDescription[];
 
+extern const char kPersistShareHubOnAppSwitchName[];
+extern const char kPersistShareHubOnAppSwitchDescription[];
+
 extern const char kPhotoPickerVideoSupportName[];
 extern const char kPhotoPickerVideoSupportDescription[];
 
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 169bf7e5..2947a898 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -350,6 +350,7 @@
     &send_tab_to_self::kSendTabToSelfV2,
     &send_tab_to_self::kSendTabToSelfManageDevicesLink,
     &send_tab_to_self::kSendTabToSelfWhenSignedIn,
+    &share::kPersistShareHubOnAppSwitch,
     &share::kSwapAndroidShareHubRows,
     &share::kUpcomingSharingFeatures,
     &signin::kMobileIdentityConsistencyPromos,
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index be11118..099cf38 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -428,6 +428,7 @@
     public static final String PAINT_PREVIEW_SHOW_ON_STARTUP = "PaintPreviewShowOnStartup";
     public static final String PASSWORD_SCRIPTS_FETCHING = "PasswordScriptsFetching";
     public static final String PERMISSION_DELEGATION = "PermissionDelegation";
+    public static final String PERSIST_SHARE_HUB_ON_APP_SWITCH = "PersistShareHubOnAppSwitch";
     public static final String PORTALS = "Portals";
     public static final String PORTALS_CROSS_ORIGIN = "PortalsCrossOrigin";
     public static final String BOOKMARKS_IMPROVED_SAVE_FLOW = "BookmarksImprovedSaveFlow";
diff --git a/chrome/browser/media/media_engagement_preloaded_list_unittest.cc b/chrome/browser/media/media_engagement_preloaded_list_unittest.cc
index ca8c93c8..f2387415 100644
--- a/chrome/browser/media/media_engagement_preloaded_list_unittest.cc
+++ b/chrome/browser/media/media_engagement_preloaded_list_unittest.cc
@@ -18,8 +18,14 @@
 
 namespace {
 
+#if defined(OS_FUCHSIA)
+// Generated files are re-homed to the package root.
+const base::FilePath kTestDataPath = base::FilePath(
+    FILE_PATH_LITERAL("chrome/test/data/media/engagement/preload"));
+#else
 const base::FilePath kTestDataPath = base::FilePath(
     FILE_PATH_LITERAL("gen/chrome/test/data/media/engagement/preload"));
+#endif
 
 // This sample data is auto generated at build time.
 const base::FilePath kSampleDataPath = kTestDataPath.AppendASCII("test.pb");
@@ -202,7 +208,13 @@
   ExpectCheckResultNotLoadedCount(1);
 }
 
-TEST_F(MediaEngagementPreloadedListTest, LoadFileReadFailed) {
+#if defined(OS_FUCHSIA)
+// ".." is not a file that can be opened on Fuchsia.
+#define MAYBE_LoadFileReadFailed DISABLED_LoadFileReadFailed
+#else
+#define MAYBE_LoadFileReadFailed LoadFileReadFailed
+#endif  // defined(OS_FUCHSIA)
+TEST_F(MediaEngagementPreloadedListTest, MAYBE_LoadFileReadFailed) {
   ASSERT_FALSE(LoadFromFile(kFileReadFailedPath));
   EXPECT_FALSE(IsLoaded());
   EXPECT_TRUE(IsEmpty());
diff --git a/chrome/browser/media/router/discovery/access_code/BUILD.gn b/chrome/browser/media/router/discovery/access_code/BUILD.gn
new file mode 100644
index 0000000..a44d513e
--- /dev/null
+++ b/chrome/browser/media/router/discovery/access_code/BUILD.gn
@@ -0,0 +1,10 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//third_party/protobuf/proto_library.gni")
+
+proto_library("discovery_resources_proto") {
+  sources = [ "discovery_resources.proto" ]
+  cc_generator_options = "lite=true:"
+}
diff --git a/chrome/browser/media/router/discovery/access_code/OWNERS b/chrome/browser/media/router/discovery/access_code/OWNERS
new file mode 100644
index 0000000..da84d320
--- /dev/null
+++ b/chrome/browser/media/router/discovery/access_code/OWNERS
@@ -0,0 +1,7 @@
+file://chrome/browser/media/router/discovery/access_code/OWNERS
+
+# Cros Edu Team Members
+gbj@google.com
+bzielinski@google.com
+bmalcolm@google.com
+jacqueli@googlle.com
diff --git a/chrome/browser/media/router/discovery/access_code/discovery_resources.proto b/chrome/browser/media/router/discovery/access_code/discovery_resources.proto
new file mode 100644
index 0000000..50abf431
--- /dev/null
+++ b/chrome/browser/media/router/discovery/access_code/discovery_resources.proto
@@ -0,0 +1,66 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+syntax = "proto3";
+
+package chrome_browser_media.proto;
+
+// Chrome requires this.
+option optimize_for = LITE_RUNTIME;
+
+// A device able to receive casting
+message ReceiverDevice {
+  // 128 bit UUID that uniquely identifies the receiver
+  string id = 1;
+  // "friendly name" from Cast V2 spec, the name displayed to users
+  string display_name = 2;
+  // Model name of the receiver device
+  string model_name = 3;
+  // Receiver device icon url
+  string device_icon_url = 4;
+  // A unique id used for sender/receiver reporting. Either empty or a 16-byte
+  // hex string
+  string metrics_id = 5;
+  // Capabilities of the receiver
+  DeviceCapabilities device_capabilities = 6;
+  enum DeviceStatus {
+    IDLE = 0;
+    BUSY_JOIN = 1;
+  }
+  // Status of the receiver device
+  DeviceStatus status = 7;
+  // The cast protocol version supported by the receiver.
+  // Begins at 2, counts up by 1 for each subsequent version.
+  int32 cast_protocol_version = 8;
+  // Network information for the receiver
+  NetworkInfo network_info = 9;
+  // 256-bit Subject public key identifier from SSL cert
+  string subject_public_key = 10;
+}
+
+// This list of capabilities should be kept in sync with the bit-mask found in
+// CastMediaSink.capabilities
+// chromium/src/components/media_router/common/mojom/media_router.mojom
+message DeviceCapabilities {
+  bool video_out = 1;
+  bool video_in = 2;
+  bool audio_out = 3;
+  bool audio_in = 4;
+  bool dev_mode = 5;
+}
+
+message NetworkInfo {
+  string host_name = 1;
+  string port = 2;
+  string ip_v4_address = 3;
+  string ip_v6_address = 4;
+}
+
+// The device's casting data that is returned in a DiscoverReceiverRequest
+// google3/google/internal/chrome/castedu/messaging/v1/discovery_service.proto
+message DiscoveryDevice {
+  string display_name = 1;
+  DeviceCapabilities device_capabilities = 4;
+  NetworkInfo network_info = 5;
+  string id = 6;
+}
diff --git a/chrome/browser/media/webrtc/webrtc_rtp_dump_writer_unittest.cc b/chrome/browser/media/webrtc/webrtc_rtp_dump_writer_unittest.cc
index c475e12..ac0cafc3 100644
--- a/chrome/browser/media/webrtc/webrtc_rtp_dump_writer_unittest.cc
+++ b/chrome/browser/media/webrtc/webrtc_rtp_dump_writer_unittest.cc
@@ -159,7 +159,7 @@
 
     // Reads each packet dump.
     while (dump_pos < dump.size()) {
-      size_t packet_dump_length = 0;
+      uint16_t packet_dump_length = 0;
       if (!VerifyPacketDump(&dump[dump_pos],
                             dump.size() - dump_pos,
                             &packet_dump_length)) {
@@ -181,12 +181,11 @@
   // the packet dump.
   bool VerifyPacketDump(const uint8_t* dump,
                         size_t dump_length,
-                        size_t* packet_dump_length) {
+                        uint16_t* packet_dump_length) {
     static const size_t kDumpHeaderLength = 8;
 
     size_t dump_pos = 0;
-    base::ReadBigEndian(reinterpret_cast<const char*>(dump + dump_pos),
-                        reinterpret_cast<uint16_t*>(packet_dump_length));
+    base::ReadBigEndian(dump + dump_pos, packet_dump_length);
     if (*packet_dump_length < kDumpHeaderLength + kMinimumRtpHeaderLength)
       return false;
 
@@ -194,8 +193,7 @@
     dump_pos += sizeof(uint16_t);
 
     uint16_t rtp_packet_length = 0;
-    base::ReadBigEndian(reinterpret_cast<const char*>(dump + dump_pos),
-                        &rtp_packet_length);
+    base::ReadBigEndian(dump + dump_pos, &rtp_packet_length);
     if (rtp_packet_length < kMinimumRtpHeaderLength)
       return false;
 
@@ -220,9 +218,8 @@
       return false;
 
     uint16_t extension_count = 0;
-    base::ReadBigEndian(
-        reinterpret_cast<const char*>(header + header_length_without_extn + 2),
-        &extension_count);
+    base::ReadBigEndian(header + header_length_without_extn + 2,
+                        &extension_count);
 
     if (length < (extension_count + 1) * 4 + header_length_without_extn)
       return false;
diff --git a/chrome/browser/metrics/chromeos_metrics_provider.cc b/chrome/browser/metrics/chromeos_metrics_provider.cc
index 6419d02..0fbc59e 100644
--- a/chrome/browser/metrics/chromeos_metrics_provider.cc
+++ b/chrome/browser/metrics/chromeos_metrics_provider.cc
@@ -9,6 +9,7 @@
 #include <vector>
 
 #include "ash/components/arc/arc_features_parser.h"
+#include "ash/components/arc/metrics/stability_metrics_manager.h"
 #include "ash/constants/ash_features.h"
 #include "ash/constants/ash_pref_names.h"
 #include "base/barrier_closure.h"
@@ -39,7 +40,6 @@
 #include "chrome/common/pref_names.h"
 #include "chromeos/services/multidevice_setup/public/cpp/multidevice_setup_client.h"
 #include "chromeos/system/statistics_provider.h"
-#include "components/arc/metrics/stability_metrics_manager.h"
 #include "components/metrics/metrics_service.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
diff --git a/chrome/browser/optimization_guide/prediction/prediction_manager.cc b/chrome/browser/optimization_guide/prediction/prediction_manager.cc
index d06b43a0..919e2af 100644
--- a/chrome/browser/optimization_guide/prediction/prediction_manager.cc
+++ b/chrome/browser/optimization_guide/prediction/prediction_manager.cc
@@ -424,6 +424,7 @@
     base_model_info.add_supported_model_types(proto::MODEL_TYPE_TFLITE_2_3_0_1);
     base_model_info.add_supported_model_types(proto::MODEL_TYPE_TFLITE_2_4);
     base_model_info.add_supported_model_types(proto::MODEL_TYPE_TFLITE_2_7);
+    base_model_info.add_supported_model_types(proto::MODEL_TYPE_TFLITE_2_8);
   }
 
   std::string debug_msg;
diff --git a/chrome/browser/performance_manager/mechanisms/working_set_trimmer_chromeos.cc b/chrome/browser/performance_manager/mechanisms/working_set_trimmer_chromeos.cc
index 7971037..cd819cf 100644
--- a/chrome/browser/performance_manager/mechanisms/working_set_trimmer_chromeos.cc
+++ b/chrome/browser/performance_manager/mechanisms/working_set_trimmer_chromeos.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "ash/components/arc/memory/arc_memory_bridge.h"
 #include "base/bind.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
@@ -15,7 +16,6 @@
 #include "chrome/browser/ash/arc/session/arc_session_manager.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile_manager.h"
-#include "components/arc/memory/arc_memory_bridge.h"
 #include "components/performance_manager/public/graph/process_node.h"
 #include "content/public/browser/browser_thread.h"
 
diff --git a/chrome/browser/performance_manager/mechanisms/working_set_trimmer_chromeos_unittest.cc b/chrome/browser/performance_manager/mechanisms/working_set_trimmer_chromeos_unittest.cc
index 68bfa11..0094daf7f 100644
--- a/chrome/browser/performance_manager/mechanisms/working_set_trimmer_chromeos_unittest.cc
+++ b/chrome/browser/performance_manager/mechanisms/working_set_trimmer_chromeos_unittest.cc
@@ -8,6 +8,7 @@
 #include <string>
 #include <utility>
 
+#include "ash/components/arc/memory/arc_memory_bridge.h"
 #include "ash/components/arc/test/fake_arc_session.h"
 #include "base/run_loop.h"
 #include "base/test/bind.h"
@@ -15,7 +16,6 @@
 #include "chrome/browser/ash/arc/test/test_arc_session_manager.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chromeos/dbus/concierge/concierge_client.h"
-#include "components/arc/memory/arc_memory_bridge.h"
 #include "components/arc/session/arc_service_manager.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm.h b/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm.h
index dabce41..02c2bf8 100644
--- a/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm.h
+++ b/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm.h
@@ -5,12 +5,12 @@
 #ifndef CHROME_BROWSER_PERFORMANCE_MANAGER_POLICIES_WORKING_SET_TRIMMER_POLICY_ARCVM_H_
 #define CHROME_BROWSER_PERFORMANCE_MANAGER_POLICIES_WORKING_SET_TRIMMER_POLICY_ARCVM_H_
 
+#include "ash/components/arc/metrics/arc_metrics_service.h"
 #include "base/memory/memory_pressure_listener.h"
 #include "base/no_destructor.h"
 #include "base/time/time.h"
 #include "chrome/browser/ash/arc/session/arc_session_manager_observer.h"
 #include "chrome/browser/performance_manager/policies/working_set_trimmer_policy_chromeos.h"
-#include "components/arc/metrics/arc_metrics_service.h"
 #include "components/arc/mojom/app.mojom.h"
 #include "components/arc/session/arc_bridge_service.h"
 #include "components/arc/session/connection_holder.h"
diff --git a/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm_unittest.cc b/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm_unittest.cc
index 27637c3..5e70872 100644
--- a/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm_unittest.cc
+++ b/chrome/browser/performance_manager/policies/working_set_trimmer_policy_arcvm_unittest.cc
@@ -8,6 +8,8 @@
 #include <utility>
 
 #include "ash/components/arc/arc_prefs.h"
+#include "ash/components/arc/metrics/arc_metrics_service.h"
+#include "ash/components/arc/metrics/stability_metrics_manager.h"
 #include "ash/components/arc/test/fake_arc_session.h"
 #include "ash/constants/app_types.h"
 #include "ash/public/cpp/app_types_util.h"
@@ -17,8 +19,6 @@
 #include "chrome/browser/ash/arc/test/test_arc_session_manager.h"
 #include "chrome/test/base/testing_profile.h"
 #include "chromeos/dbus/concierge/concierge_client.h"
-#include "components/arc/metrics/arc_metrics_service.h"
-#include "components/arc/metrics/stability_metrics_manager.h"
 #include "components/arc/session/arc_service_manager.h"
 #include "components/prefs/testing_pref_service.h"
 #include "components/session_manager/core/session_manager.h"
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index 258e187..33ffa515 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -1544,7 +1544,6 @@
   { key::kWebSQLInThirdPartyContextEnabled,
     policy_prefs::kWebSQLInThirdPartyContextEnabled,
     base::Value::Type::BOOLEAN },
-
   { key::kCORSNonWildcardRequestHeadersSupport,
     prefs::kCorsNonWildcardRequestHeadersSupport,
     base::Value::Type::BOOLEAN },
@@ -1554,6 +1553,9 @@
   { key::kCopyPreventionSettings,
     policy_prefs::kCopyPreventionSettings,
     base::Value::Type::DICTIONARY},
+  { key::kUserAgentReduction,
+    prefs::kUserAgentReduction,
+    base::Value::Type::INTEGER},
 };
 // clang-format on
 
diff --git a/chrome/browser/resources/history/history_clusters/url_visit.html b/chrome/browser/resources/history/history_clusters/url_visit.html
index 96fc2623..1abcd2ef 100644
--- a/chrome/browser/resources/history/history_clusters/url_visit.html
+++ b/chrome/browser/resources/history/history_clusters/url_visit.html
@@ -1,6 +1,7 @@
 <style include="history-clusters-shared-style">
   :host {
     align-items: center;
+    cursor: pointer;
     display: flex;
     height: 64px;
   }
diff --git a/chrome/browser/resources/nearby_share/DIR_METADATA b/chrome/browser/resources/nearby_share/DIR_METADATA
index 135a13c..3d76e46 100644
--- a/chrome/browser/resources/nearby_share/DIR_METADATA
+++ b/chrome/browser/resources/nearby_share/DIR_METADATA
@@ -1,3 +1,3 @@
 monorail: {
-  component: "UI>Browser>Sharing>Nearby"
+  component: "OS>Systems>Multidevice>Nearby"
 }
diff --git a/chrome/browser/resources/read_later/side_panel/bookmark_folder.html b/chrome/browser/resources/read_later/side_panel/bookmark_folder.html
index cb7b559..c107184 100644
--- a/chrome/browser/resources/read_later/side_panel/bookmark_folder.html
+++ b/chrome/browser/resources/read_later/side_panel/bookmark_folder.html
@@ -179,14 +179,17 @@
     <div id="children" role="group">
       <template is="dom-repeat" items="[[folder.children]]" initial-count="20">
         <template is="dom-if" if="[[!item.url]]" restamp>
-          <bookmark-folder folder="[[item]]"
+          <bookmark-folder
+              id="bookmark-[[item.id]]"
+              folder="[[item]]"
               depth="[[childDepth_]]"
               open-folders="[[openFolders]]">
           </bookmark-folder>
         </template>
 
         <template is="dom-if" if="[[item.url]]" restamp>
-          <button role="treeitem" class="bookmark row"
+          <button
+              id="bookmark-[[item.id]]" role="treeitem" class="bookmark row"
               draggable="true" data-bookmark="[[item]]"
               on-click="onBookmarkClick_"
               on-auxclick="onBookmarkAuxClick_"
diff --git a/chrome/browser/resources/read_later/side_panel/bookmark_folder.ts b/chrome/browser/resources/read_later/side_panel/bookmark_folder.ts
index bf55105..4c2563e 100644
--- a/chrome/browser/resources/read_later/side_panel/bookmark_folder.ts
+++ b/chrome/browser/resources/read_later/side_panel/bookmark_folder.ts
@@ -185,6 +185,35 @@
         this.shadowRoot!.querySelectorAll('.row, bookmark-folder'));
   }
 
+  getFocusableElement(path: chrome.bookmarks.BookmarkTreeNode[]): (HTMLElement|
+                                                                   null) {
+    const currentNode = path.shift();
+    if (currentNode) {
+      const currentNodeId = currentNode.id;
+      const currentNodeElement =
+          this.shadowRoot!.querySelector(`#bookmark-${currentNodeId}`) as (
+              HTMLElement | null);
+      if (currentNodeElement &&
+          currentNodeElement.classList.contains('bookmark')) {
+        // Found a bookmark item.
+        return currentNodeElement;
+      }
+
+      if (currentNodeElement &&
+          currentNodeElement instanceof BookmarkFolderElement) {
+        // Bookmark item may be a grandchild or be deeper. Iterate through
+        // child BookmarkFolderElements until the bookmark item is found.
+        const nestedElement = currentNodeElement.getFocusableElement(path);
+        if (nestedElement) {
+          return nestedElement;
+        }
+      }
+    }
+
+    // If all else fails, return the focusable folder row.
+    return this.shadowRoot!.querySelector('#folder');
+  }
+
   moveFocus(delta: -1|1): boolean {
     const currentFocus = this.shadowRoot!.activeElement;
     if (currentFocus instanceof BookmarkFolderElement &&
diff --git a/chrome/browser/resources/read_later/side_panel/bookmarks_list.html b/chrome/browser/resources/read_later/side_panel/bookmarks_list.html
index e24c162..3e3826d 100644
--- a/chrome/browser/resources/read_later/side_panel/bookmarks_list.html
+++ b/chrome/browser/resources/read_later/side_panel/bookmarks_list.html
@@ -1,4 +1,5 @@
 <template is="dom-repeat" items="[[folders_]]">
-  <bookmark-folder folder="[[item]]" open-folders="[[openFolders_]]">
+  <bookmark-folder id="bookmark-[[item.id]]" folder="[[item]]"
+      open-folders="[[openFolders_]]">
   </bookmark-folder>
 </template>
\ No newline at end of file
diff --git a/chrome/browser/resources/read_later/side_panel/bookmarks_list.ts b/chrome/browser/resources/read_later/side_panel/bookmarks_list.ts
index c012428c..5c1e0136 100644
--- a/chrome/browser/resources/read_later/side_panel/bookmarks_list.ts
+++ b/chrome/browser/resources/read_later/side_panel/bookmarks_list.ts
@@ -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 {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {afterNextRender, html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {BookmarkFolderElement, FOLDER_OPEN_CHANGED_EVENT, getBookmarkFromElement, isBookmarkFolderElement} from './bookmark_folder.js';
 import {BookmarksApiProxy} from './bookmarks_api_proxy.js';
@@ -203,6 +203,9 @@
       parent.children = [];
     }
     this.splice(`${pathToParentString}.children`, node.index!, 0, node);
+    afterNextRender(this, () => {
+      this.focusBookmark_(node.id);
+    });
   }
 
   private changeFolderOpenStatus_(id: string, open: boolean) {
@@ -309,6 +312,27 @@
         `${oldParentPathString}.children`,
         oldParent.children!.indexOf(removedNode), 1);
   }
+
+  private focusBookmark_(id: string) {
+    const path = this.findPathToId_(id);
+    if (path.length === 0) {
+      // Could not find bookmark.
+      return;
+    }
+
+    const rootBookmark = path.shift();
+    const rootBookmarkElement =
+        this.shadowRoot!.querySelector(`#bookmark-${rootBookmark!.id}`) as
+        BookmarkFolderElement;
+    if (!rootBookmarkElement) {
+      return;
+    }
+
+    const bookmarkElement = rootBookmarkElement.getFocusableElement(path);
+    if (bookmarkElement) {
+      bookmarkElement.focus();
+    }
+  }
 }
 
 customElements.define(BookmarksListElement.is, BookmarksListElement);
diff --git a/chrome/browser/resources/settings/chromeos/BUILD.gn b/chrome/browser/resources/settings/chromeos/BUILD.gn
index 3bcd1f3..1540af35 100644
--- a/chrome/browser/resources/settings/chromeos/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/BUILD.gn
@@ -285,7 +285,6 @@
     "chromeos/personalization_page/wallpaper_browser_proxy.js",
     "chromeos/prefs_behavior.js",
     "chromeos/os_privacy_page/peripheral_data_access_browser_proxy.js",
-    "chromeos/os_privacy_page/metrics_consent_browser_proxy.js",
     "chromeos/os_people_page/account_manager_browser_proxy.js",
     "chromeos/route_observer_behavior.js",
     "chromeos/ambient_mode_page/ambient_mode_browser_proxy.js",
diff --git a/chrome/browser/resources/settings/chromeos/lazy_load.js b/chrome/browser/resources/settings/chromeos/lazy_load.js
index cae0d09da..63844efd 100644
--- a/chrome/browser/resources/settings/chromeos/lazy_load.js
+++ b/chrome/browser/resources/settings/chromeos/lazy_load.js
@@ -70,6 +70,5 @@
 export {PrinterType} from './os_printing_page/cups_printer_types.js';
 export {CupsPrintersBrowserProxy, CupsPrintersBrowserProxyImpl, PrinterSetupResult, PrintServerResult} from './os_printing_page/cups_printers_browser_proxy.js';
 export {CupsPrintersEntryManager} from './os_printing_page/cups_printers_entry_manager.js';
-export {MetricsConsentBrowserProxy, MetricsConsentBrowserProxyImpl, MetricsConsentState} from './os_privacy_page/metrics_consent_browser_proxy.js';
 export {DataAccessPolicyState, PeripheralDataAccessBrowserProxy, PeripheralDataAccessBrowserProxyImpl} from './os_privacy_page/peripheral_data_access_browser_proxy.js';
 export {OsResetBrowserProxyImpl} from './os_reset_page/os_reset_browser_proxy.js';
diff --git a/chrome/browser/resources/settings/chromeos/os_privacy_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_privacy_page/BUILD.gn
index 14ff8c9..436ce1a7 100644
--- a/chrome/browser/resources/settings/chromeos/os_privacy_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_privacy_page/BUILD.gn
@@ -18,7 +18,6 @@
 
 js_library("os_privacy_page") {
   deps = [
-    ":metrics_consent_browser_proxy",
     ":peripheral_data_access_browser_proxy",
     ":peripheral_data_access_protection_dialog",
     "..:deep_linking_behavior.m",
@@ -33,13 +32,6 @@
   externs_list = chrome_extension_public_externs
 }
 
-js_library("metrics_consent_browser_proxy") {
-  deps = [
-    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
-    "//ui/webui/resources/js:cr.m",
-  ]
-}
-
 js_library("peripheral_data_access_browser_proxy") {
   deps = [
     "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
diff --git a/chrome/browser/resources/settings/chromeos/os_privacy_page/metrics_consent_browser_proxy.js b/chrome/browser/resources/settings/chromeos/os_privacy_page/metrics_consent_browser_proxy.js
deleted file mode 100644
index fbfdeac..0000000
--- a/chrome/browser/resources/settings/chromeos/os_privacy_page/metrics_consent_browser_proxy.js
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// clang-format off
-import { addSingletonGetter, sendWithPromise } from 'chrome://resources/js/cr.m.js';
-// clang-format on
-
-/**
- * @fileoverview Helper browser proxy for getting metrics and changing metrics
- * consent data.
- */
-
-/**
- * MetricsConsentState represents the current metrics state for the current
- * logged-in user. prefName is the pref that is controlling the current user's
- * metrics consent state and isConfigurable indicates whether the current user
- * may change the pref.
- *
- * The prefName is currently always constant and only the owner of the device
- * may change the consent.
- *
- * @typedef {{
- *     prefName: string,
- *     isConfigurable: boolean,
- * }}
- */
-export let MetricsConsentState;
-
-/** @interface */
-export class MetricsConsentBrowserProxy {
-  /**
-   * Returns the metrics consent state to render.
-   *
-   * @return {!Promise<MetricsConsentState>}
-   */
-  getMetricsConsentState() {}
-
-  /**
-   * Returns the new metrics consent after the update.
-   *
-   * @param {boolean} consent Consent to change metrics consent to.
-   * @return {!Promise<boolean>}
-   */
-  updateMetricsConsent(consent) {}
-}
-
-/** @implements {MetricsConsentBrowserProxy} */
-export class MetricsConsentBrowserProxyImpl {
-  /** @override */
-  getMetricsConsentState() {
-    return sendWithPromise('getMetricsConsentState');
-  }
-
-  /** @override */
-  updateMetricsConsent(consent) {
-    return sendWithPromise('updateMetricsConsent', {consent: consent});
-  }
-}
-
-// The singleton instance_ is replaced with a test version of this wrapper
-// during testing.
-addSingletonGetter(MetricsConsentBrowserProxyImpl);
diff --git a/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.html b/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.html
index ce4b1de..f479969 100644
--- a/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.html
@@ -42,13 +42,10 @@
 <if expr="_google_chrome">
     <settings-toggle-button
         id="enable-logging"
-        pref="[[metricsConsentPref_]]"
+        pref="{{prefs.cros.metrics.reportingEnabled}}"
         label="$i18n{enableLogging}"
         sub-label="$i18n{enableLoggingDesc}"
-        deep-link-focus-id$="[[Setting.kUsageStatsAndCrashReports]]"
-        disabled$="[[!isMetricsConsentConfigurable_]]"
-        on-settings-boolean-control-change="onMetricsConsentChange_"
-        no-set-pref>
+        deep-link-focus-id$="[[Setting.kUsageStatsAndCrashReports]]">
     </settings-toggle-button>
     <div class="hr"></div>
 </if>
diff --git a/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.js b/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.js
index 410b0b1..4f76728 100644
--- a/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_privacy_page/os_privacy_page.js
@@ -31,7 +31,6 @@
 import {PrefsBehavior} from '../prefs_behavior.js';
 import {RouteObserverBehavior} from '../route_observer_behavior.js';
 
-import {MetricsConsentBrowserProxy, MetricsConsentBrowserProxyImpl, MetricsConsentState} from './metrics_consent_browser_proxy.js';
 import {DataAccessPolicyState, PeripheralDataAccessBrowserProxy, PeripheralDataAccessBrowserProxyImpl} from './peripheral_data_access_browser_proxy.js';
 
 Polymer({
@@ -196,35 +195,11 @@
         return loadTimeData.getBoolean('showSecureDnsSetting');
       },
     },
-
-    // <if expr="_google_chrome">
-    /**
-     * The preference controlling the current user's metrics consent. This will
-     * be loaded from |this.prefs| based on the response from
-     * |this.metricsConsentBrowserProxy_.getMetricsConsentState()|.
-     *
-     * @private
-     * @type {?chrome.settingsPrivate.PrefObject}
-     */
-    metricsConsentPref_: {
-      type: Object,
-      value: null,
-    },
-
-    /** @private */
-    isMetricsConsentConfigurable_: {
-      type: Boolean,
-      value: false,
-    },
-    // </if>
   },
 
   /** @private {?PeripheralDataAccessBrowserProxy} */
   browserProxy_: null,
 
-  /** @private {?MetricsConsentBrowserProxy} */
-  metricsConsentBrowserProxy_: null,
-
   observers: ['onDataAccessFlagsSet_(isThunderboltSupported_.*)'],
 
   /** @override */
@@ -238,17 +213,6 @@
             chromeos.settings.mojom.Setting.kPeripheralDataAccessProtection);
       }
     });
-
-    // <if expr="_google_chrome">
-    this.metricsConsentBrowserProxy_ =
-        MetricsConsentBrowserProxyImpl.getInstance();
-    this.metricsConsentBrowserProxy_.getMetricsConsentState().then(state => {
-      const pref = /** @type {chrome.settingsPrivate.PrefObject} */ (
-          this.get(state.prefName, this.prefs));
-      this.metricsConsentPref_ = pref;
-      this.isMetricsConsentConfigurable_ = state.isConfigurable;
-    });
-    // </if>
   },
 
   /**
@@ -453,30 +417,6 @@
     this.setPrefValue(this.dataAccessProtectionPrefName_, false);
   },
 
-  // <if expr="_google_chrome">
-  /** @private */
-  onMetricsConsentChange_() {
-    this.metricsConsentBrowserProxy_
-        .updateMetricsConsent(this.getMetricsToggle_().checked)
-        .then(consent => {
-          if (consent === this.getMetricsToggle_().checked) {
-            this.getMetricsToggle_().sendPrefChange();
-          } else {
-            this.getMetricsToggle_().resetToPrefValue();
-          }
-        });
-  },
-
-  /**
-   * @private
-   * @return {SettingsToggleButtonElement}
-   */
-  getMetricsToggle_() {
-    return /** @type {SettingsToggleButtonElement} */ (
-        this.$$('#enable-logging'));
-  },
-  // </if>
-
   /**
    * This is used to add a keydown listener event for handling keyboard
    * navigation inputs. We have to wait until either
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.gni b/chrome/browser/resources/settings/chromeos/os_settings.gni
index cebf194..09e205e5 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.gni
+++ b/chrome/browser/resources/settings/chromeos/os_settings.gni
@@ -112,8 +112,6 @@
                                    "settings.pageVisibility|pageVisibility",
                                    "settings.PeripheralDataAccessBrowserProxy|PeripheralDataAccessBrowserProxy",
                                    "settings.DataAccessPolicyState|DataAccessPolicyState",
-                                   "settings.MetricsConsentBrowserProxy|MetricsConsentBrowserProxy",
-                                   "settings.MetricsConsentState|MetricsConsentState",
                                    "settings.PhoneHubNotificationAccessStatus|PhoneHubNotificationAccessStatus",
                                    "settings.PowerSource|PowerSource",
                                    "settings.PowerManagementSettings|PowerManagementSettings",
diff --git a/chrome/browser/resources/settings/chromeos/os_settings.js b/chrome/browser/resources/settings/chromeos/os_settings.js
index 88a761c..6b33c21 100644
--- a/chrome/browser/resources/settings/chromeos/os_settings.js
+++ b/chrome/browser/resources/settings/chromeos/os_settings.js
@@ -144,7 +144,6 @@
 export {FingerprintBrowserProxyImpl, FingerprintResultType} from './os_people_page/fingerprint_browser_proxy.m.js';
 export {OsSyncBrowserProxyImpl} from './os_people_page/os_sync_browser_proxy.m.js';
 export {FingerprintLocation, FingerprintSetupStep} from './os_people_page/setup_fingerprint_dialog.m.js';
-export {MetricsConsentBrowserProxy, MetricsConsentBrowserProxyImpl, MetricsConsentState} from './os_privacy_page/metrics_consent_browser_proxy.js';
 export {DataAccessPolicyState, PeripheralDataAccessBrowserProxy, PeripheralDataAccessBrowserProxyImpl} from './os_privacy_page/peripheral_data_access_browser_proxy.js';
 export {OsResetBrowserProxyImpl} from './os_reset_page/os_reset_browser_proxy.js';
 export {routes} from './os_route.m.js';
diff --git a/chrome/browser/resources/settings/lazy_load.ts b/chrome/browser/resources/settings/lazy_load.ts
index d10877dc..04535a17 100644
--- a/chrome/browser/resources/settings/lazy_load.ts
+++ b/chrome/browser/resources/settings/lazy_load.ts
@@ -133,10 +133,12 @@
 export {LocalDataBrowserProxy, LocalDataBrowserProxyImpl, LocalDataItem} from './site_settings/local_data_browser_proxy.js';
 export {AppHandlerEntry, AppProtocolEntry, HandlerEntry, ProtocolEntry, ProtocolHandlersElement} from './site_settings/protocol_handlers.js';
 export {SettingsCategoryDefaultRadioGroupElement} from './site_settings/settings_category_default_radio_group.js';
+export {SiteDetailsElement} from './site_settings/site_details.js';
+export {SiteDetailsPermissionElement} from './site_settings/site_details_permission.js';
 export {SiteListElement} from './site_settings/site_list.js';
 export {SiteListEntryElement} from './site_settings/site_list_entry.js';
 export {ContentSettingProvider, CookiePrimarySetting, DefaultContentSetting, OriginInfo, RawChooserException, RawSiteException, RecentSitePermissions, SiteException, SiteGroup, SiteSettingsPrefsBrowserProxy, SiteSettingsPrefsBrowserProxyImpl, ZoomLevelEntry} from './site_settings/site_settings_prefs_browser_proxy.js';
-export {WebsiteUsageBrowserProxyImpl} from './site_settings/website_usage_browser_proxy.js';
+export {WebsiteUsageBrowserProxy, WebsiteUsageBrowserProxyImpl} from './site_settings/website_usage_browser_proxy.js';
 export {ZoomLevelsElement} from './site_settings/zoom_levels.js';
 export {SettingsRecentSitePermissionsElement} from './site_settings_page/recent_site_permissions.js';
 export {defaultSettingLabel} from './site_settings_page/site_settings_list.js';
diff --git a/chrome/browser/resources/settings/site_settings/site_details.ts b/chrome/browser/resources/settings/site_settings/site_details.ts
index ccd1714..afd2ace 100644
--- a/chrome/browser/resources/settings/site_settings/site_details.ts
+++ b/chrome/browser/resources/settings/site_settings/site_details.ts
@@ -40,10 +40,13 @@
 import {SiteSettingsMixin, SiteSettingsMixinInterface} from './site_settings_mixin.js';
 import {WebsiteUsageBrowserProxy, WebsiteUsageBrowserProxyImpl} from './website_usage_browser_proxy.js';
 
-interface SiteDetailsElement {
+export interface SiteDetailsElement {
   $: {
     confirmClearStorage: CrDialogElement,
     confirmResetSettings: CrDialogElement,
+    noStorage: HTMLElement,
+    storage: HTMLElement,
+    usage: HTMLElement,
   };
 }
 
@@ -55,7 +58,7 @@
       SiteSettingsMixinInterface & RouteObserverMixinInterface
     };
 
-class SiteDetailsElement extends SiteDetailsElementBase {
+export class SiteDetailsElement extends SiteDetailsElementBase {
   static get is() {
     return 'site-details';
   }
@@ -324,4 +327,10 @@
   }
 }
 
+declare global {
+  interface HTMLElementTagNameMap {
+    'site-details': SiteDetailsElement;
+  }
+}
+
 customElements.define(SiteDetailsElement.is, SiteDetailsElement);
diff --git a/chrome/browser/resources/settings/site_settings/site_details_permission.ts b/chrome/browser/resources/settings/site_settings/site_details_permission.ts
index 83c94363..a781721d 100644
--- a/chrome/browser/resources/settings/site_settings/site_details_permission.ts
+++ b/chrome/browser/resources/settings/site_settings/site_details_permission.ts
@@ -27,7 +27,10 @@
 
 export interface SiteDetailsPermissionElement {
   $: {
+    details: HTMLElement,
     permission: HTMLSelectElement,
+    permissionItem: HTMLElement,
+    permissionSecondary: HTMLElement,
   };
 }
 
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetCoordinator.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetCoordinator.java
index d0e4b51..4fae0de 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetCoordinator.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/share_sheet/ShareSheetCoordinator.java
@@ -578,7 +578,9 @@
 
     @Override
     public void onActivityPaused() {
-        if (mBottomSheet != null) {
+        boolean persistOnPause =
+                ChromeFeatureList.isEnabled(ChromeFeatureList.PERSIST_SHARE_HUB_ON_APP_SWITCH);
+        if (mBottomSheet != null && !persistOnPause) {
             mBottomSheetController.hideContent(mBottomSheet, true);
         }
     }
diff --git a/chrome/browser/share/share_features.cc b/chrome/browser/share/share_features.cc
index 1f083ea..d0f1f2ba 100644
--- a/chrome/browser/share/share_features.cc
+++ b/chrome/browser/share/share_features.cc
@@ -6,6 +6,8 @@
 
 namespace share {
 
+const base::Feature kPersistShareHubOnAppSwitch{
+    "PersistShareHubOnAppSwitch", base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kSharingDesktopScreenshotsEdit{
     "SharingDesktopScreenshotsEdit", base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kSwapAndroidShareHubRows{"SwapAndroidShareHubRows",
diff --git a/chrome/browser/share/share_features.h b/chrome/browser/share/share_features.h
index 64fffc2..614f978e 100644
--- a/chrome/browser/share/share_features.h
+++ b/chrome/browser/share/share_features.h
@@ -9,6 +9,7 @@
 
 namespace share {
 
+extern const base::Feature kPersistShareHubOnAppSwitch;
 extern const base::Feature kSharingDesktopScreenshotsEdit;
 extern const base::Feature kSwapAndroidShareHubRows;
 extern const base::Feature kUpcomingSharingFeatures;
diff --git a/chrome/browser/sharesheet/share_action/share_action_unittest.cc b/chrome/browser/sharesheet/share_action/share_action_unittest.cc
index 0fff8738..9ec04c3 100644
--- a/chrome/browser/sharesheet/share_action/share_action_unittest.cc
+++ b/chrome/browser/sharesheet/share_action/share_action_unittest.cc
@@ -16,16 +16,10 @@
 #include "chrome/common/chrome_features.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/test/base/testing_profile.h"
-#include "components/services/app_service/public/cpp/intent_util.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "ui/base/clipboard/clipboard.h"
-#include "ui/base/clipboard/file_info.h"
-#include "ui/base/clipboard/test/clipboard_test_util.h"
-#include "ui/base/clipboard/test/test_clipboard.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/chromeos/strings/grit/ui_chromeos_strings.h"
-#include "url/gurl.h"
 
 namespace sharesheet {
 
@@ -38,17 +32,15 @@
 
   // Test:
   void SetUp() override {
-    profile_ = std::make_unique<TestingProfile>();
-    share_action_cache_ = std::make_unique<ShareActionCache>(profile_.get());
+    auto profile = std::make_unique<TestingProfile>();
+    share_action_cache_ = std::make_unique<ShareActionCache>(profile.get());
   }
 
-  Profile* profile() { return profile_.get(); }
   ShareActionCache* share_action_cache() { return share_action_cache_.get(); }
 
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
   content::BrowserTaskEnvironment task_environment_;
-  std::unique_ptr<TestingProfile> profile_;
 
   std::unique_ptr<ShareActionCache> share_action_cache_;
 };
@@ -92,72 +84,4 @@
       CreateDriveIntent(), /*contains_hosted_document=*/true));
 }
 
-TEST_F(ShareActionTest, CopyToClipboardText) {
-  auto* copy_action =
-      share_action_cache()->GetActionFromName(l10n_util::GetStringUTF16(
-          IDS_SHARESHEET_COPY_TO_CLIPBOARD_SHARE_ACTION_LABEL));
-  copy_action->LaunchAction(/*controller=*/nullptr, /*root_view=*/nullptr,
-                            CreateValidTextIntent());
-  // Check text copied correctly.
-  std::u16string clipboard_text;
-  ui::Clipboard::GetForCurrentThread()->ReadText(
-      ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
-      &clipboard_text);
-  EXPECT_EQ(::sharesheet::kTestText, base::UTF16ToUTF8(clipboard_text));
-}
-
-TEST_F(ShareActionTest, CopyToClipboardUrl) {
-  auto* copy_action =
-      share_action_cache()->GetActionFromName(l10n_util::GetStringUTF16(
-          IDS_SHARESHEET_COPY_TO_CLIPBOARD_SHARE_ACTION_LABEL));
-  copy_action->LaunchAction(/*controller=*/nullptr, /*root_view=*/nullptr,
-                            CreateValidUrlIntent());
-  // Check url copied correctly.
-  std::u16string clipboard_url;
-  ui::Clipboard::GetForCurrentThread()->ReadText(
-      ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr,
-      &clipboard_url);
-  EXPECT_EQ(::sharesheet::kTestUrl, base::UTF16ToUTF8(clipboard_url));
-}
-
-TEST_F(ShareActionTest, CopyToClipboardOneFile) {
-  auto* copy_action =
-      share_action_cache()->GetActionFromName(l10n_util::GetStringUTF16(
-          IDS_SHARESHEET_COPY_TO_CLIPBOARD_SHARE_ACTION_LABEL));
-  storage::FileSystemURL url =
-      FileInDownloads(profile(), base::FilePath(kTestTextFile));
-  copy_action->LaunchAction(
-      /*controller=*/nullptr, /*root_view=*/nullptr,
-      apps_util::CreateShareIntentFromFiles({url.ToGURL()}, {kMimeTypeText}));
-
-  // Check filenames copied correctly.
-  std::vector<ui::FileInfo> filenames;
-  ui::Clipboard::GetForCurrentThread()->ReadFilenames(
-      ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr, &filenames);
-  EXPECT_EQ(filenames.size(), 1u);
-  EXPECT_EQ(url.path(), filenames[0].path);
-}
-
-TEST_F(ShareActionTest, CopyToClipboardMultipleFiles) {
-  auto* copy_action =
-      share_action_cache()->GetActionFromName(l10n_util::GetStringUTF16(
-          IDS_SHARESHEET_COPY_TO_CLIPBOARD_SHARE_ACTION_LABEL));
-  storage::FileSystemURL url1 =
-      FileInDownloads(profile(), base::FilePath(kTestPdfFile));
-  storage::FileSystemURL url2 =
-      FileInDownloads(profile(), base::FilePath(kTestTextFile));
-  copy_action->LaunchAction(
-      /*controller=*/nullptr, /*root_view=*/nullptr,
-      apps_util::CreateShareIntentFromFiles({url1.ToGURL(), url2.ToGURL()},
-                                            {kMimeTypePdf, kMimeTypeText}));
-
-  // Check filenames copied correctly.
-  std::vector<ui::FileInfo> filenames;
-  ui::Clipboard::GetForCurrentThread()->ReadFilenames(
-      ui::ClipboardBuffer::kCopyPaste, /* data_dst = */ nullptr, &filenames);
-  EXPECT_EQ(filenames.size(), 2u);
-  EXPECT_EQ(url1.path(), filenames[0].path);
-  EXPECT_EQ(url2.path(), filenames[1].path);
-}
-
 }  // namespace sharesheet
diff --git a/chrome/browser/signin/dice_web_signin_interceptor.cc b/chrome/browser/signin/dice_web_signin_interceptor.cc
index 4361addd..ddca4551 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor.cc
+++ b/chrome/browser/signin/dice_web_signin_interceptor.cc
@@ -309,7 +309,7 @@
   account_id_ = account_id;
   is_interception_in_progress_ = true;
   new_account_interception_ = is_new_account;
-  Observe(web_contents);
+  web_contents_ = web_contents->GetWeakPtr();
 
   if (heuristic_outcome) {
     RecordSigninInterceptionHeuristicOutcome(*heuristic_outcome);
@@ -374,7 +374,7 @@
 }
 
 void DiceWebSigninInterceptor::Reset() {
-  Observe(/*web_contents=*/nullptr);
+  web_contents_ = nullptr;
   account_info_update_observation_.Reset();
   on_account_info_update_timeout_.Cancel();
   is_interception_in_progress_ = false;
@@ -574,7 +574,7 @@
       break;
   }
   interception_bubble_handle_ = delegate_->ShowSigninInterceptionBubble(
-      web_contents(), bubble_parameters, std::move(callback));
+      web_contents_.get(), bubble_parameters, std::move(callback));
 
   was_interception_ui_displayed_ = true;
 }
@@ -707,8 +707,8 @@
   // DiceWebSigninInterceptor that is attached to the new profile.
   DiceWebSigninInterceptorFactory::GetForProfile(new_profile)
       ->CreateBrowserAfterSigninInterception(
-          account_id_, web_contents(), std::move(interception_bubble_handle_),
-          is_new_profile);
+          account_id_, web_contents_.get(),
+          std::move(interception_bubble_handle_), is_new_profile);
   Reset();
 }
 
diff --git a/chrome/browser/signin/dice_web_signin_interceptor.h b/chrome/browser/signin/dice_web_signin_interceptor.h
index f4c45b8..a3ce1c65 100644
--- a/chrome/browser/signin/dice_web_signin_interceptor.h
+++ b/chrome/browser/signin/dice_web_signin_interceptor.h
@@ -16,7 +16,6 @@
 #include "chrome/browser/ui/webui/signin/enterprise_profile_welcome_ui.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
-#include "content/public/browser/web_contents_observer.h"
 #include "google_apis/gaia/core_account_id.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "third_party/skia/include/core/SkColor.h"
@@ -147,7 +146,6 @@
 //   - The interception bubble is closed by deleting the handle,
 //   - The profile customization bubble is shown.
 class DiceWebSigninInterceptor : public KeyedService,
-                                 public content::WebContentsObserver,
                                  public signin::IdentityManager::Observer {
  public:
   enum class SigninInterceptionType {
@@ -371,6 +369,7 @@
   std::unique_ptr<DiceInterceptedSessionStartupHelper> session_startup_helper_;
 
   // Members below are related to the interception in progress.
+  base::WeakPtr<content::WebContents> web_contents_;
   bool is_interception_in_progress_ = false;
   CoreAccountId account_id_;
   bool new_account_interception_ = false;
diff --git a/chrome/browser/thumbnail/cc/thumbnail_cache.cc b/chrome/browser/thumbnail/cc/thumbnail_cache.cc
index be5efc8..ec500b7 100644
--- a/chrome/browser/thumbnail/cc/thumbnail_cache.cc
+++ b/chrome/browser/thumbnail/cc/thumbnail_cache.cc
@@ -81,8 +81,9 @@
 
 template <typename T>
 bool ReadBigEndianFromFile(base::File& file, T* out) {
-  char buffer[sizeof(T)];
-  if (file.ReadAtCurrentPos(buffer, sizeof(T)) != sizeof(T))
+  uint8_t buffer[sizeof(T)];
+  if (file.ReadAtCurrentPos(reinterpret_cast<char*>(buffer), sizeof(T)) !=
+      sizeof(T))
     return false;
   base::ReadBigEndian(buffer, out);
   return true;
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index ad19bf7b..078aade2 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -2635,6 +2635,8 @@
       "webui/chromeos/projector/selfie_cam_bubble_manager.h",
       "webui/chromeos/set_time_ui.cc",
       "webui/chromeos/set_time_ui.h",
+      "webui/chromeos/shimless_rma_dialog.cc",
+      "webui/chromeos/shimless_rma_dialog.h",
       "webui/chromeos/slow_trace_ui.cc",
       "webui/chromeos/slow_trace_ui.h",
       "webui/chromeos/slow_ui.cc",
@@ -2763,8 +2765,6 @@
       "webui/settings/chromeos/languages_section.h",
       "webui/settings/chromeos/main_section.cc",
       "webui/settings/chromeos/main_section.h",
-      "webui/settings/chromeos/metrics_consent_handler.cc",
-      "webui/settings/chromeos/metrics_consent_handler.h",
       "webui/settings/chromeos/multidevice_handler.cc",
       "webui/settings/chromeos/multidevice_handler.h",
       "webui/settings/chromeos/multidevice_section.cc",
diff --git a/chrome/browser/ui/app_list/app_list_syncable_service.cc b/chrome/browser/ui/app_list/app_list_syncable_service.cc
index c17b58b..cc75ca0 100644
--- a/chrome/browser/ui/app_list/app_list_syncable_service.cc
+++ b/chrome/browser/ui/app_list/app_list_syncable_service.cc
@@ -1632,19 +1632,23 @@
   // The code below initializes the app's position when the app list sort
   // feature is enabled.
 
+  // The target position of `new_item`.
   syncer::StringOrdinal position;
-  bool order_ignored = false;
+
+  ash::AppListSortOrder order = static_cast<ash::AppListSortOrder>(
+      profile()->GetPrefs()->GetInteger(prefs::kAppListPreferredOrder));
 
   // TODO(https://crbug.com/1260877): ideally we would not have to create a
   // one-off vector of items using `GetItems()`.
-  reorder_delegate_->CalculateNewItemPosition(
-      static_cast<ash::AppListSortOrder>(
-          profile()->GetPrefs()->GetInteger(prefs::kAppListPreferredOrder)),
-      *new_item, model_updater_->GetItems(), &position, &order_ignored);
+  bool is_successful = reorder_delegate_->CalculateNewItemPosition(
+      order, *new_item, model_updater_->GetItems(), &sync_items_, &position);
 
-  // Reset the sorting order if the order is ignored when calculating
-  // `new_item`'s target position.
-  if (order_ignored) {
+  // If `new_item` cannot be placed following the specified order, `new_item`
+  // should be placed at front. Also reset the sorting order.
+  if (!is_successful) {
+    DCHECK(!position.IsValid());
+    position = reorder_delegate_->CalculateFrontPosition(sync_items_);
+
     profile()->GetPrefs()->SetInteger(
         prefs::kAppListPreferredOrder,
         static_cast<int>(ash::AppListSortOrder::kCustom));
diff --git a/chrome/browser/ui/app_list/arc/arc_app_unittest.cc b/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
index 2ebbf99..8227674a 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_unittest.cc
@@ -152,8 +152,9 @@
           representation.pixel_width() !=
               base::ClampCeil(size_in_dip_ * scale) ||
           representation.pixel_height() !=
-              base::ClampCeil(size_in_dip_ * scale))
+              base::ClampCeil(size_in_dip_ * scale)) {
         return false;
+      }
     }
 
     return true;
@@ -163,6 +164,15 @@
                          const gfx::ImageSkia& image) override {
     app_id_ = app_id;
     image_ = image;
+
+    const std::vector<ui::ResourceScaleFactor>& scale_factors =
+        ui::GetSupportedResourceScaleFactors();
+    for (auto& scale_factor : scale_factors) {
+      // Force the icon to be loaded.
+      image_.GetRepresentation(
+          ui::GetScaleForResourceScaleFactor(scale_factor));
+    }
+
     images_[app_id] = image;
     ++update_image_count_;
     if (update_image_count_ == expected_update_image_count_ &&
@@ -178,8 +188,11 @@
   }
 
   void WaitForIconUpdates(size_t expected_updates) {
+    if (update_image_count_ == expected_updates)
+      return;
+
     base::RunLoop run_loop;
-    expected_update_image_count_ = expected_updates + update_image_count_;
+    expected_update_image_count_ = expected_updates;
     icon_updated_callback_ = run_loop.QuitClosure();
     run_loop.Run();
   }
@@ -1011,8 +1024,9 @@
   void LoadIconWithIconLoader(const std::string& app_id,
                               AppServiceAppIconLoader& icon_loader,
                               FakeAppIconLoaderDelegate& delegate) {
+    int current_count = delegate.update_image_count();
     icon_loader.FetchImage(app_id);
-    delegate.WaitForIconUpdates(1);
+    delegate.WaitForIconUpdates(current_count + 1);
     content::RunAllTasksUntilIdle();
 
     // Validate loaded image.
@@ -2537,7 +2551,7 @@
   // Shortcut exists, icon is requested from shortcut.
   icon_loader.FetchImage(id_shortcut_exist);
   // Icon was sent on request and loader should be updated.
-  delegate.WaitForIconUpdates(1);
+  delegate.WaitForIconUpdates(2);
   EXPECT_EQ(id_shortcut_exist, delegate.app_id());
 
   // Validate that fetched shortcut icon for existing shortcut does not match
@@ -2558,11 +2572,8 @@
   // Fallback when shortcut is not found for shelf group id, use app id instead.
   // Remove the IconRequestRecord for |app_id| to observe the icon request for
   // |app_id| is re-sent.
-  const size_t update_image_count_before = delegate.update_image_count();
   MaybeRemoveIconRequestRecord(app_id);
   icon_loader.FetchImage(id_shortcut_absent);
-  // Expected no update.
-  EXPECT_EQ(update_image_count_before, delegate.update_image_count());
   content::RunAllTasksUntilIdle();
   EXPECT_EQ(shortcut_request_count,
             app_instance()->shortcut_icon_requests().size());
@@ -2706,9 +2717,6 @@
   ArcAppListPrefs* prefs = ArcAppListPrefs::Get(profile_.get());
   ASSERT_NE(nullptr, prefs);
 
-  const std::vector<ui::ResourceScaleFactor>& scale_factors =
-      ui::GetSupportedResourceScaleFactors();
-
   app_instance()->SendRefreshAppList(std::vector<arc::mojom::AppInfo>(
       fake_apps().begin(), fake_apps().begin() + 1));
 
@@ -2730,27 +2738,14 @@
       &delegate);
   EXPECT_EQ(0UL, delegate.update_image_count());
   icon_loader.FetchImage(app_id);
-  EXPECT_EQ(0UL, delegate.update_image_count());
-
-  AppServiceAppItem* app_item = FindArcItem(app_id);
-  for (auto& scale_factor : scale_factors) {
-    // Force the icon to be loaded.
-    app_item->icon().GetRepresentation(
-        ui::GetScaleForResourceScaleFactor(scale_factor));
-  }
 
   delegate.WaitForIconUpdates(1);
 
   // Validate loaded image.
-  EXPECT_EQ(1UL, delegate.update_image_count());
   EXPECT_EQ(app_id, delegate.app_id());
   ValidateIcon(
       delegate.image(),
       ash::SharedAppListConfig::instance().default_grid_icon_dimension());
-
-  // No more updates are expected.
-  base::RunLoop().RunUntilIdle();
-  EXPECT_EQ(1UL, delegate.update_image_count());
 }
 
 TEST_P(ArcDefaultAppTest, LoadAdaptiveIcon) {
diff --git a/chrome/browser/ui/app_list/arc/arc_app_utils.cc b/chrome/browser/ui/app_list/arc/arc_app_utils.cc
index f9122f08..360adcd 100644
--- a/chrome/browser/ui/app_list/arc/arc_app_utils.cc
+++ b/chrome/browser/ui/app_list/arc/arc_app_utils.cc
@@ -13,6 +13,7 @@
 #include "ash/components/arc/arc_prefs.h"
 #include "ash/components/arc/arc_util.h"
 #include "ash/components/arc/metrics/arc_metrics_constants.h"
+#include "ash/components/arc/metrics/arc_metrics_service.h"
 #include "base/check.h"
 #include "base/containers/contains.h"
 #include "base/json/json_writer.h"
@@ -50,7 +51,6 @@
 #include "chrome/common/pref_names.h"
 #include "components/app_restore/app_restore_utils.h"
 #include "components/arc/intent_helper/arc_intent_helper_bridge.h"
-#include "components/arc/metrics/arc_metrics_service.h"
 #include "components/arc/mojom/intent_helper.mojom.h"
 #include "components/arc/session/arc_bridge_service.h"
 #include "components/arc/session/arc_service_manager.h"
@@ -835,7 +835,7 @@
   if (!prefs->GetBoolean(arc::prefs::kArcPlayStoreLaunchMetricCanBeRecorded))
     return;
   auto time_oobe_finished = prefs->GetTime(ash::prefs::kOobeOnboardingTime);
-  if (!time_oobe_finished.is_null())
+  if (time_oobe_finished.is_null())
     return;
   bool within_a_week = base::Time::Now() - time_oobe_finished < base::Days(7);
   if (within_a_week && !launched)
diff --git a/chrome/browser/ui/app_list/reorder/app_list_reorder_delegate.cc b/chrome/browser/ui/app_list/reorder/app_list_reorder_delegate.cc
index 02da39e6..30d66970 100644
--- a/chrome/browser/ui/app_list/reorder/app_list_reorder_delegate.cc
+++ b/chrome/browser/ui/app_list/reorder/app_list_reorder_delegate.cc
@@ -296,8 +296,11 @@
     ash::AppListSortOrder order,
     const T& new_item_key_attribute,
     const std::vector<reorder::SyncItemWrapper<T>>& sorted_subsequence,
-    absl::optional<syncer::StringOrdinal>* prev,
-    absl::optional<syncer::StringOrdinal>* next) {
+    syncer::StringOrdinal* prev,
+    syncer::StringOrdinal* next) {
+  DCHECK(prev && !prev->IsValid());
+  DCHECK(next && !next->IsValid());
+
   // Find the first local item that is not a folder. Recall that
   // `sorted_subsequence` guarantees:
   // (1) Folder items are always placed in front of non-folder items.
@@ -351,30 +354,32 @@
   *next = lower_bound->item_ordinal;
 }
 
-// Returns the position for an incoming new app so that after insertion launcher
-// sort order is maintained on all sync devices.
+// Adjusts `prev` and `prev` in global scope so that the sorting order is kept
+// on all sync devices after placing `new_item` between adjusted neighbors.
 template <typename T>
-syncer::StringOrdinal CalculateNewAppPositionInGlobalScope(
+void AdjustNeighborsInGlobalScope(
     ash::AppListSortOrder order,
     const reorder::SyncItemWrapper<T>& new_item,
     const std::vector<reorder::SyncItemWrapper<T>>& global_items,
-    const absl::optional<syncer::StringOrdinal>& local_prev,
-    const absl::optional<syncer::StringOrdinal>& local_next) {
-  // `local_prev` and `local_next` are the new item's local neighbor positions
-  // (see CalculateNeighbors() for more details). Recall that different sync
-  // devices may have different sets of apps. This method checks the existing
-  // sync items whose positions are between `local_prev` and `local_next` so as
+    syncer::StringOrdinal* prev,
+    syncer::StringOrdinal* next) {
+  // Before adjustment, `prev` and `next` are the new item's local neighbor
+  // positions (see CalculateNeighbors() for more details). Recall that
+  // different sync devices may have different sets of apps. This method checks
+  // the existing sync items whose positions are between `prev` and `next` so as
   // to get the correct position in global scope.
+  DCHECK(prev);
+  DCHECK(next);
 
   // The left neighbor in the global scope.
   syncer::StringOrdinal global_prev;
-  if (local_prev)
-    global_prev = *local_prev;
+  if (prev->IsValid())
+    global_prev = *prev;
 
   // The right neighbor in the global scope.
   syncer::StringOrdinal global_next;
-  if (local_next)
-    global_next = *local_next;
+  if (next->IsValid())
+    global_next = *next;
 
   const bool is_increasing = IsIncreasingOrder(order);
   for (const auto& item : global_items) {
@@ -409,27 +414,35 @@
     }
   }
 
-  // Calculate the result based on `global_prev` and `global_next`.
+  // Store results.
+  if (global_prev.IsValid())
+    *prev = global_prev;
 
-  if (!global_prev.IsValid() && !global_next.IsValid()) {
-    // The edge case that `global_items` is empty is covered here. Not sure
-    // whether this case really exists. Handle it for satefy.
+  if (global_next.IsValid())
+    *next = global_next;
+}
+
+syncer::StringOrdinal CalculatePositionBetweenNeighbors(
+    const syncer::StringOrdinal& prev,
+    const syncer::StringOrdinal& next) {
+  if (!prev.IsValid() && !next.IsValid()) {
+    // Not sure whether this case really exists. Handle it for satefy.
     return syncer::StringOrdinal().CreateInitialOrdinal();
   }
 
-  if (global_prev.IsValid() && global_next.IsValid()) {
-    // Both left neighbor and right neighbor are valid. Insert the new app
-    // between `global_prev` and `global_next`.
-    return global_prev.CreateBetween(global_next);
+  if (prev.IsValid() && next.IsValid()) {
+    // Both left neighbor and right neighbor are valid. Return a position that
+    // is between `prev` and `next`.
+    return prev.CreateBetween(next);
   }
 
-  if (global_prev.IsValid()) {
-    // Only `global_prev` is valid. Insert the new app after `global_prev`.
-    return global_prev.CreateAfter();
+  if (prev.IsValid()) {
+    // Only `prev` is valid. Return a position that is after `prev`.
+    return prev.CreateAfter();
   }
 
-  // Only `global_next` is valid. Insert the new app before `global_next`.
-  return global_next.CreateBefore();
+  // Only `next` is valid. Return a position that is before `next`.
+  return next.CreateBefore();
 }
 
 }  // namespace
@@ -491,7 +504,7 @@
 std::vector<reorder::ReorderParam>
 AppListReorderDelegate::GenerateReorderParamsForAppListItems(
     ash::AppListSortOrder order,
-    const std::vector<const ChromeAppListItem*>& app_list_items) {
+    const std::vector<const ChromeAppListItem*>& app_list_items) const {
   DCHECK_GT(app_list_items.size(), 1);
   switch (order) {
     case ash::AppListSortOrder::kNameAlphabetical:
@@ -506,12 +519,12 @@
   }
 }
 
-void AppListReorderDelegate::CalculateNewItemPosition(
+bool AppListReorderDelegate::CalculateNewItemPosition(
     ash::AppListSortOrder order,
     const ChromeAppListItem& new_item,
     const std::vector<const ChromeAppListItem*>& local_items,
-    syncer::StringOrdinal* target_position,
-    bool* order_ignored) const {
+    const AppListSyncableService::SyncItemMap* global_items,
+    syncer::StringOrdinal* target_position) const {
   // TODO(https://crbug.com/1260875): handle the case that `new_item` is a
   // folder.
   DCHECK(!new_item.is_folder());
@@ -519,20 +532,18 @@
   switch (order) {
     case ash::AppListSortOrder::kCustom:
       // Insert `item` at the front when the sort order is kCustom.
-      *target_position = CalculateFrontPosition();
-      *order_ignored = false;
-      break;
+      DCHECK(global_items);
+      *target_position = CalculateFrontPosition(*global_items);
+      return true;
     case ash::AppListSortOrder::kNameAlphabetical:
     case ash::AppListSortOrder::kNameReverseAlphabetical:
-      CalculatePositionInNameOrder(order, new_item, local_items,
-                                   target_position, order_ignored);
-      break;
+      return CalculatePositionInNameOrder(order, new_item, local_items,
+                                          global_items, target_position);
   }
 }
 
-syncer::StringOrdinal AppListReorderDelegate::CalculateFrontPosition() const {
-  const AppListSyncableService::SyncItemMap& sync_item_map =
-      app_list_syncable_service_->sync_items();
+syncer::StringOrdinal AppListReorderDelegate::CalculateFrontPosition(
+    const AppListSyncableService::SyncItemMap& sync_item_map) const {
   syncer::StringOrdinal minimum_valid_ordinal;
   for (auto iter = sync_item_map.cbegin(); iter != sync_item_map.cend();
        ++iter) {
@@ -554,28 +565,21 @@
   return syncer::StringOrdinal::CreateInitialOrdinal();
 }
 
-void AppListReorderDelegate::CalculatePositionInNameOrder(
+bool AppListReorderDelegate::CalculatePositionInNameOrder(
     ash::AppListSortOrder order,
     const ChromeAppListItem& new_item,
     const std::vector<const ChromeAppListItem*>& local_items,
-    syncer::StringOrdinal* target_position,
-    bool* order_ignored) const {
+    const AppListSyncableService::SyncItemMap* global_items,
+    syncer::StringOrdinal* target_position) const {
   DCHECK(order == ash::AppListSortOrder::kNameAlphabetical ||
          order == ash::AppListSortOrder::kNameReverseAlphabetical);
 
-  // Set the default value to be false.
-  *order_ignored = false;
-
   std::vector<reorder::SyncItemWrapper<std::string>> local_item_wrappers =
       reorder::GenerateStringWrappersFromAppListItems(local_items);
 
   if (local_item_wrappers.empty()) {
-    *target_position = CalculateNewAppPositionInGlobalScope(
-        order, reorder::ConvertAppListItemToStringWrapper(new_item),
-        reorder::GenerateStringWrappersFromSyncItems(
-            app_list_syncable_service_->sync_items()),
-        /*local_prev=*/absl::nullopt, /*local_next=*/absl::nullopt);
-    return;
+    *target_position = syncer::StringOrdinal::CreateInitialOrdinal();
+    return true;
   }
 
   std::vector<reorder::SyncItemWrapper<std::string>> sorted_subsequence;
@@ -584,26 +588,25 @@
                                           &sorted_subsequence);
 
   if (entropy > reorder::kOrderResetThreshold) {
-    // Ignore `order` and place `new_item` at front if entropy is too high.
-    *target_position = CalculateFrontPosition();
-    *order_ignored = true;
-    return;
+    // Do not set `target_position` if entropy is too high.
+    return false;
   }
 
-  absl::optional<syncer::StringOrdinal> prev_neighbor;
-  absl::optional<syncer::StringOrdinal> next_neighbor;
+  syncer::StringOrdinal prev_neighbor;
+  syncer::StringOrdinal next_neighbor;
   CalculateNeighbors(order, new_item.name(), sorted_subsequence, &prev_neighbor,
                      &next_neighbor);
 
-  *target_position = CalculateNewAppPositionInGlobalScope(
-      order, reorder::ConvertAppListItemToStringWrapper(new_item),
-      reorder::GenerateStringWrappersFromSyncItems(
-          app_list_syncable_service_->sync_items()),
-      prev_neighbor, next_neighbor);
-}
+  if (global_items) {
+    AdjustNeighborsInGlobalScope(
+        order, reorder::ConvertAppListItemToStringWrapper(new_item),
+        reorder::GenerateStringWrappersFromSyncItems(*global_items),
+        &prev_neighbor, &next_neighbor);
+  }
 
-PrefService* AppListReorderDelegate::GetPrefService() {
-  return app_list_syncable_service_->profile()->GetPrefs();
+  *target_position =
+      CalculatePositionBetweenNeighbors(prev_neighbor, next_neighbor);
+  return true;
 }
 
 }  // namespace app_list
diff --git a/chrome/browser/ui/app_list/reorder/app_list_reorder_delegate.h b/chrome/browser/ui/app_list/reorder/app_list_reorder_delegate.h
index ae88112..426eedf 100644
--- a/chrome/browser/ui/app_list/reorder/app_list_reorder_delegate.h
+++ b/chrome/browser/ui/app_list/reorder/app_list_reorder_delegate.h
@@ -14,7 +14,6 @@
 #include "components/sync/model/string_ordinal.h"
 
 class ChromeAppListItem;
-class PrefService;
 
 namespace app_list {
 namespace reorder {
@@ -60,42 +59,39 @@
   // `ChromeAppListItem` pointers as the parameter.
   std::vector<reorder::ReorderParam> GenerateReorderParamsForAppListItems(
       ash::AppListSortOrder order,
-      const std::vector<const ChromeAppListItem*>& app_list_items);
+      const std::vector<const ChromeAppListItem*>& app_list_items) const;
 
   // Calculates the position for an incoming item based on the specified sorting
-  // order and pass results through parameters. `local_items` indicates the
-  // elements in the active app list model before adding the new item. Note that
-  // different devices may have different sets of app list items. It is why the
-  // parameter is named `local_items`. `order_ignored` is true if it is not
-  // appropriate to place `new_item` following `order`. It is because the
-  // arrangement of `local_items` does not follow `order`. In this case, the new
-  // item should be placed at the front.
-  void CalculateNewItemPosition(
+  // order and pass the target position through the parameter. Returns whether
+  // `target_position` is set. The return value is false if it is not propriate
+  // to place `new_item` following `order`. In this case, `target_position` is
+  // not set.
+  // `local_items` indicates the elements in the active app list model before
+  // adding the new item. Note that different devices may have different sets of
+  // app list items. It is why the parameter is named `local_items`.
+  // `global_items` is the sync data of the items across synced devices. If
+  // `global_items` is null, position is calculated only based on `local_items`.
+  bool CalculateNewItemPosition(
       ash::AppListSortOrder order,
       const ChromeAppListItem& new_item,
       const std::vector<const ChromeAppListItem*>& local_items,
-      syncer::StringOrdinal* target_position,
-      bool* order_ignored) const;
+      const AppListSyncableService::SyncItemMap* global_items,
+      syncer::StringOrdinal* target_position) const;
+
+  // Returns the foremost item position across syncable devices.
+  syncer::StringOrdinal CalculateFrontPosition(
+      const AppListSyncableService::SyncItemMap& sync_item_map) const;
 
  private:
-  // Returns the foremost item position across syncable devices.
-  syncer::StringOrdinal CalculateFrontPosition() const;
-
-  // Calculates the position for an incoming item based on the namer order that
-  // is either kNameAlphabetical or kNameReverseAlphabetical. Pass results
-  // through parameters. Read the comment of `CalculateNewItemPosition()` for
-  // the remaining parameters' meaning.
-  // TODO(https://crbug.com/1261899): the function name is misleading. Actually
-  // this function is not const because the sort order saved in prefs could be
-  // reset. Replace with a better name.
-  void CalculatePositionInNameOrder(
+  // Similar to `CalculateNewItemPosition()` but `order` is either
+  // kNameAlphabetical or kNameReverseAlphabetical. Read the comment of
+  // `CalculateNewItemPosition()` for parameters' meanings.
+  bool CalculatePositionInNameOrder(
       ash::AppListSortOrder order,
       const ChromeAppListItem& new_item,
       const std::vector<const ChromeAppListItem*>& local_items,
-      syncer::StringOrdinal* target_position,
-      bool* order_ignored) const;
-
-  PrefService* GetPrefService();
+      const AppListSyncableService::SyncItemMap* global_items,
+      syncer::StringOrdinal* target_position) const;
 
   AppListSyncableService* const app_list_syncable_service_;
 };
diff --git a/chrome/browser/ui/ash/chrome_desks_templates_delegate.cc b/chrome/browser/ui/ash/chrome_desks_templates_delegate.cc
index a897110..02411aa 100644
--- a/chrome/browser/ui/ash/chrome_desks_templates_delegate.cc
+++ b/chrome/browser/ui/ash/chrome_desks_templates_delegate.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/ash/chrome_desks_templates_delegate.h"
 
 #include "ash/constants/app_types.h"
+#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/desk_template.h"
 #include "base/bind.h"
 #include "base/check.h"
@@ -20,7 +21,6 @@
 #include "chrome/common/chrome_features.h"
 #include "components/app_restore/app_launch_info.h"
 #include "components/app_restore/app_restore_data.h"
-#include "components/app_restore/features.h"
 #include "components/app_restore/full_restore_save_handler.h"
 #include "components/app_restore/full_restore_utils.h"
 #include "components/app_restore/restore_data.h"
@@ -194,7 +194,7 @@
     case ash::AppType::LACROS:
       return false;
     case ash::AppType::ARC_APP:
-      if (!app_restore::features::IsArcAppsForDesksTemplatesEnabled())
+      if (!ash::features::AreDesksTemplatesEnabled())
         return false;
       break;
     case ash::AppType::BROWSER:
diff --git a/chrome/browser/ui/ash/desk_template_app_launch_handler.cc b/chrome/browser/ui/ash/desk_template_app_launch_handler.cc
index 2618c64..0a5ecbae 100644
--- a/chrome/browser/ui/ash/desk_template_app_launch_handler.cc
+++ b/chrome/browser/ui/ash/desk_template_app_launch_handler.cc
@@ -6,6 +6,7 @@
 
 #include <string>
 
+#include "ash/constants/ash_features.h"
 #include "ash/wm/desks/desks_controller.h"
 #include "base/notreached.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -23,7 +24,6 @@
 #include "chrome/browser/web_applications/system_web_apps/system_web_app_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
 #include "components/app_restore/desk_template_read_handler.h"
-#include "components/app_restore/features.h"
 #include "components/app_restore/restore_data.h"
 #include "components/app_restore/window_info.h"
 #include "extensions/common/extension.h"
@@ -203,7 +203,7 @@
 }
 
 void DeskTemplateAppLaunchHandler::MaybeLaunchArcApps() {
-  if (!app_restore::features::IsArcAppsForDesksTemplatesEnabled())
+  if (!ash::features::AreDesksTemplatesEnabled())
     return;
 
   auto* arc_task_handler =
diff --git a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
index 1492d29..165263aa 100644
--- a/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
+++ b/chrome/browser/ui/ash/shelf/chrome_shelf_controller_unittest.cc
@@ -178,6 +178,7 @@
 // Dummy app id is used to put at least one pin record to prevent initializing
 // pin model with preinstalled apps that can affect some tests.
 constexpr char kDummyAppId[] = "dummyappid_dummyappid_dummyappid";
+constexpr int kSizeInDip = extension_misc::EXTENSION_ICON_MEDIUM;
 
 std::unique_ptr<KeyedService> BuildTestSyncService(
     content::BrowserContext* context) {
@@ -195,6 +196,28 @@
   return apps;
 }
 
+bool ValidateImageIsFullyLoaded(const gfx::ImageSkia& image) {
+  if (kSizeInDip != image.width() || kSizeInDip != image.height())
+    return false;
+
+  const std::vector<ui::ResourceScaleFactor>& scale_factors =
+      ui::GetSupportedResourceScaleFactors();
+  for (auto& scale_factor : scale_factors) {
+    const float scale = ui::GetScaleForResourceScaleFactor(scale_factor);
+    if (!image.HasRepresentation(scale))
+      return false;
+
+    const gfx::ImageSkiaRep& representation = image.GetRepresentation(scale);
+    if (representation.is_null() ||
+        representation.pixel_width() != base::ClampCeil(kSizeInDip * scale) ||
+        representation.pixel_height() != base::ClampCeil(kSizeInDip * scale)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
 // Test implementation of AppIconLoader.
 class TestAppIconLoaderImpl : public AppIconLoader {
  public:
@@ -4351,10 +4374,6 @@
   EXPECT_TRUE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
   // Initially, a default icon is set for the shelf item.
   EXPECT_FALSE(item_delegate->image_set_by_controller());
-  auto get_icon = [=]() {
-    return *shelf_controller_->GetItem(shelf_id)->image.bitmap();
-  };
-  const SkBitmap default_icon = get_icon();
 
   std::string window_app_id("org.chromium.arc.1");
   CreateArcWindow(window_app_id);
@@ -4366,11 +4385,18 @@
   ASSERT_TRUE(item_delegate);
   EXPECT_FALSE(shelf_controller_->GetShelfSpinnerController()->HasApp(app_id));
   EXPECT_FALSE(item_delegate->image_set_by_controller());
-  EXPECT_TRUE(gfx::test::AreBitmapsEqual(default_icon, get_icon()));
 
   // Wait for the real app icon image to be decoded and set for the shelf item.
   base::RunLoop().RunUntilIdle();
-  EXPECT_FALSE(gfx::test::AreBitmapsEqual(default_icon, get_icon()));
+  const std::vector<ui::ResourceScaleFactor>& scale_factors =
+      ui::GetSupportedResourceScaleFactors();
+  for (auto& scale_factor : scale_factors) {
+    // Force the icon to be loaded.
+    shelf_controller_->GetItem(shelf_id)->image.GetRepresentation(
+        ui::GetScaleForResourceScaleFactor(scale_factor));
+  }
+  EXPECT_TRUE(
+      ValidateImageIsFullyLoaded(shelf_controller_->GetItem(shelf_id)->image));
 }
 
 TEST_F(ChromeShelfControllerArcDefaultAppsTest, PlayStoreDeferredLaunch) {
diff --git a/chrome/browser/ui/global_media_controls/cast_media_notification_item_unittest.cc b/chrome/browser/ui/global_media_controls/cast_media_notification_item_unittest.cc
index 9d96b73..ad62eb0 100644
--- a/chrome/browser/ui/global_media_controls/cast_media_notification_item_unittest.cc
+++ b/chrome/browser/ui/global_media_controls/cast_media_notification_item_unittest.cc
@@ -9,9 +9,11 @@
 #include <utility>
 
 #include "chrome/browser/bitmap_fetcher/bitmap_fetcher.h"
+#include "chrome/browser/media/router/chrome_media_router_factory.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/global_media_controls/public/test/mock_media_item_manager.h"
 #include "components/media_message_center/mock_media_notification_view.h"
+#include "components/media_router/browser/test/mock_media_router.h"
 #include "components/media_router/common/media_route.h"
 #include "components/vector_icons/vector_icons.h"
 #include "content/public/test/browser_task_environment.h"
@@ -70,10 +72,13 @@
       mojo::Remote<media_router::mojom::MediaController> remote)
       : CastMediaSessionController(std::move(remote)) {}
 
-  MOCK_METHOD1(Send, void(media_session::mojom::MediaSessionAction));
-  MOCK_METHOD1(OnMediaStatusUpdated, void(media_router::mojom::MediaStatusPtr));
-  MOCK_METHOD1(SetVolume, void(float));
-  MOCK_METHOD1(SetMute, void(bool));
+  MOCK_METHOD(void, Send, (media_session::mojom::MediaSessionAction));
+  MOCK_METHOD(void,
+              OnMediaStatusUpdated,
+              (media_router::mojom::MediaStatusPtr));
+  MOCK_METHOD(void, SeekTo, (base::TimeDelta));
+  MOCK_METHOD(void, SetVolume, (float));
+  MOCK_METHOD(void, SetMute, (bool));
 };
 
 }  // namespace
@@ -261,6 +266,12 @@
   item_->SetMute(muted);
 }
 
+TEST_F(CastMediaNotificationItemTest, SendSeekCommandToController) {
+  auto seek_time = base::Seconds(2);
+  EXPECT_CALL(*session_controller_, SeekTo(seek_time));
+  item_->SeekTo(seek_time);
+}
+
 TEST_F(CastMediaNotificationItemTest, DownloadImage) {
   SetView();
   GURL image_url("https://example.com/image.png");
@@ -355,3 +366,15 @@
     item_->OnMediaStatusUpdated(std::move(status));
   }
 }
+
+TEST_F(CastMediaNotificationItemTest, StopCasting) {
+  media_router::ChromeMediaRouterFactory::GetInstance()->SetTestingFactory(
+      &profile_, base::BindRepeating(&media_router::MockMediaRouter::Create));
+  auto* mock_router = static_cast<media_router::MockMediaRouter*>(
+      media_router::MediaRouterFactory::GetApiForBrowserContext(&profile_));
+
+  EXPECT_CALL(*mock_router, TerminateRoute(item_->route_id()));
+  EXPECT_CALL(item_manager_, FocusDialog());
+  item_->StopCasting(
+      global_media_controls::GlobalMediaControlsEntryPoint::kPresentation);
+}
diff --git a/chrome/browser/ui/global_media_controls/cast_media_session_controller.cc b/chrome/browser/ui/global_media_controls/cast_media_session_controller.cc
index 29454309..125d42e 100644
--- a/chrome/browser/ui/global_media_controls/cast_media_session_controller.cc
+++ b/chrome/browser/ui/global_media_controls/cast_media_session_controller.cc
@@ -108,6 +108,11 @@
   route_controller_.FlushForTesting();
 }
 
+media_router::mojom::MediaStatusPtr
+CastMediaSessionController::GetMediaStatusForTesting() {
+  return media_status_.Clone();
+}
+
 base::TimeDelta CastMediaSessionController::PutWithinBounds(
     const base::TimeDelta& time) {
   if (time.is_negative() || !media_status_)
diff --git a/chrome/browser/ui/global_media_controls/cast_media_session_controller.h b/chrome/browser/ui/global_media_controls/cast_media_session_controller.h
index a939675..5624753 100644
--- a/chrome/browser/ui/global_media_controls/cast_media_session_controller.h
+++ b/chrome/browser/ui/global_media_controls/cast_media_session_controller.h
@@ -39,6 +39,7 @@
   virtual void SetVolume(float volume);
 
   void FlushForTesting();
+  media_router::mojom::MediaStatusPtr GetMediaStatusForTesting();
 
  private:
   base::TimeDelta PutWithinBounds(const base::TimeDelta& time);
diff --git a/chrome/browser/ui/global_media_controls/cast_media_session_controller_unittest.cc b/chrome/browser/ui/global_media_controls/cast_media_session_controller_unittest.cc
index 7fe80d05..da78f2d 100644
--- a/chrome/browser/ui/global_media_controls/cast_media_session_controller_unittest.cc
+++ b/chrome/browser/ui/global_media_controls/cast_media_session_controller_unittest.cc
@@ -9,6 +9,8 @@
 
 #include "base/time/time.h"
 #include "components/media_router/common/mojom/media_status.mojom.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
 #include "content/public/test/browser_task_environment.h"
 #include "services/media_session/public/mojom/constants.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -53,6 +55,13 @@
     controller_->FlushForTesting();
   }
 
+  void WaitForOneSecond() {
+    base::RunLoop run_loop;
+    content::GetUIThreadTaskRunner({})->PostDelayedTask(
+        FROM_HERE, run_loop.QuitClosure(), base::Seconds(1));
+    run_loop.Run();
+  }
+
  protected:
   content::BrowserTaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
@@ -133,3 +142,44 @@
   EXPECT_CALL(mock_controller_, Pause());
   SendToController(MediaSessionAction::kStop);
 }
+
+TEST_F(CastMediaSessionControllerTest, SeekTo) {
+  auto seek_time = base::Seconds(2);
+  EXPECT_CALL(mock_controller_, Seek(seek_time));
+  controller_->SeekTo(seek_time);
+  controller_->FlushForTesting();
+}
+
+TEST_F(CastMediaSessionControllerTest, SetMute) {
+  bool mute = false;
+  EXPECT_CALL(mock_controller_, SetMute(mute));
+  controller_->SetMute(mute);
+  controller_->FlushForTesting();
+}
+
+TEST_F(CastMediaSessionControllerTest, SetVolume) {
+  float volume = 1.1f;
+  EXPECT_CALL(mock_controller_, SetVolume(testing::FloatEq(volume)));
+  controller_->SetVolume(volume);
+  controller_->FlushForTesting();
+}
+
+TEST_F(CastMediaSessionControllerTest, IncrementCurrentTime) {
+  //  Update controller with paused media status should not increment current
+  //  time.
+  media_status_->play_state =
+      media_router::mojom::MediaStatus::PlayState::PAUSED;
+  controller_->OnMediaStatusUpdated(media_status_.Clone());
+  WaitForOneSecond();
+  EXPECT_EQ(media_status_->current_time,
+            controller_->GetMediaStatusForTesting()->current_time);
+
+  // Update controller with playing media status should increment current time.
+  media_status_->play_state =
+      media_router::mojom::MediaStatus::PlayState::PLAYING;
+  controller_->OnMediaStatusUpdated(media_status_.Clone());
+
+  WaitForOneSecond();
+  EXPECT_NE(media_status_->current_time,
+            controller_->GetMediaStatusForTesting()->current_time);
+}
diff --git a/chrome/browser/ui/views/location_bar/permission_chip.cc b/chrome/browser/ui/views/location_bar/permission_chip.cc
index 93165c3..fc69dcd 100644
--- a/chrome/browser/ui/views/location_bar/permission_chip.cc
+++ b/chrome/browser/ui/views/location_bar/permission_chip.cc
@@ -34,23 +34,16 @@
       : views::ButtonController(button, std::move(delegate)),
         bubble_owner_(bubble_owner) {}
 
-  bool OnMousePressed(const ui::MouseEvent& event) override {
-    bubble_owner_->RecordOnMousePressed();
-    suppress_button_release_ = bubble_owner_->IsBubbleShowing();
-    return views::ButtonController::OnMousePressed(event);
-  }
+  // TODO(crbug.com/1270699): Add keyboard support.
+  void OnMouseEntered(const ui::MouseEvent& event) override {
+    if (bubble_owner_->IsBubbleShowing() || bubble_owner_->IsAnimating()) {
+      return;
+    }
 
-  bool IsTriggerableEvent(const ui::Event& event) override {
-    // TODO(olesiamarukhno): There is the same logic in IconLabelBubbleView,
-    // this class should be reused in the future to avoid duplication.
-    if (event.IsMouseEvent())
-      return !bubble_owner_->IsBubbleShowing() && !suppress_button_release_;
-
-    return views::ButtonController::IsTriggerableEvent(event);
+    bubble_owner_->RestartTimersOnMouseHover();
   }
 
  private:
-  bool suppress_button_release_ = false;
   BubbleOwnerDelegate* bubble_owner_ = nullptr;
 };
 
@@ -86,8 +79,7 @@
     bubble_widget->Close();
   }
   CHECK(!IsInObserverList());
-  collapse_timer_.AbandonAndStop();
-  dismiss_timer_.AbandonAndStop();
+  ResetTimers();
 }
 
 void PermissionChip::OpenBubble() {
@@ -111,11 +103,12 @@
 }
 
 void PermissionChip::Collapse(bool allow_restart) {
-  if (allow_restart && (IsMouseHovered() || IsBubbleShowing())) {
+  if (allow_restart && IsMouseHovered()) {
     StartCollapseTimer();
   } else {
     chip_button_->AnimateCollapse();
     StartDismissTimer();
+    ShowBlockedIcon();
   }
 }
 
@@ -123,11 +116,6 @@
   chip_button_->SetShowBlockedIcon(true);
 }
 
-void PermissionChip::OnMouseEntered(const ui::MouseEvent& event) {
-  if (!chip_button_->is_animating())
-    RestartTimersOnInteraction();
-}
-
 void PermissionChip::AddedToWidget() {
   views::AccessiblePaneView::AddedToWidget();
 
@@ -152,17 +140,22 @@
   // If permission request is still active after the prompt was closed,
   // collapse the chip.
   Collapse(/*allow_restart=*/false);
+  ShowBlockedIcon();
 }
 
 bool PermissionChip::IsBubbleShowing() const {
   return prompt_bubble_tracker_.view() != nullptr;
 }
 
-void PermissionChip::RecordOnMousePressed() {
-  if (IsBubbleShowing() && ShouldCloseBubbleOnLostFocus()) {
-    // If the permission prompt bubble is closed because the user clicked on the
-    // chip, record this as Dismissed.
-    OnPromptBubbleDismissed();
+bool PermissionChip::IsAnimating() const {
+  return chip_button_->is_animating();
+}
+
+void PermissionChip::RestartTimersOnMouseHover() {
+  if (is_fully_collapsed()) {
+    StartDismissTimer();
+  } else {
+    StartCollapseTimer();
   }
 }
 
@@ -185,10 +178,6 @@
   delegate_->SetDecisionTime();
 }
 
-bool PermissionChip::ShouldCloseBubbleOnLostFocus() const {
-  return false;
-}
-
 void PermissionChip::Show(bool always_open_bubble) {
   // TODO(olesiamarukhno): Add tests for animation logic.
   chip_button_->ResetAnimation();
@@ -202,22 +191,25 @@
 }
 
 void PermissionChip::ExpandAnimationEnded() {
-  StartCollapseTimer();
-  if (should_start_open_ && !IsBubbleShowing())
+  if (IsBubbleShowing())
+    return;
+
+  if (should_start_open_) {
     OpenBubble();
+  } else {
+    StartCollapseTimer();
+  }
 }
 
 void PermissionChip::ChipButtonPressed() {
-  if (!IsBubbleShowing())
-    OpenBubble();
-  RestartTimersOnInteraction();
-}
-
-void PermissionChip::RestartTimersOnInteraction() {
-  if (is_fully_collapsed()) {
-    StartDismissTimer();
+  if (IsBubbleShowing()) {
+    // A mouse click on chip while a permission prompt is open should dismiss
+    // the prompt and collapse the chip
+    prompt_bubble_tracker_.view()->GetWidget()->CloseWithReason(
+        views::Widget::ClosedReason::kCloseButtonClicked);
   } else {
-    StartCollapseTimer();
+    ResetTimers();
+    OpenBubble();
   }
 }
 
diff --git a/chrome/browser/ui/views/location_bar/permission_chip.h b/chrome/browser/ui/views/location_bar/permission_chip.h
index ee5fb3b..d2dadb3 100644
--- a/chrome/browser/ui/views/location_bar/permission_chip.h
+++ b/chrome/browser/ui/views/location_bar/permission_chip.h
@@ -17,7 +17,8 @@
 class BubbleOwnerDelegate {
  public:
   virtual bool IsBubbleShowing() const = 0;
-  virtual void RecordOnMousePressed() = 0;
+  virtual bool IsAnimating() const = 0;
+  virtual void RestartTimersOnMouseHover() = 0;
 };
 
 // A class for an interface for chip view that is shown in the location bar to
@@ -55,7 +56,6 @@
   bool is_fully_collapsed() const { return chip_button_->is_fully_collapsed(); }
 
   // views::View:
-  void OnMouseEntered(const ui::MouseEvent& event) override;
   void AddedToWidget() override;
   void VisibilityChanged(views::View* starting_from, bool is_visible) override;
 
@@ -64,6 +64,8 @@
 
   // BubbleOwnerDelegate:
   bool IsBubbleShowing() const override;
+  bool IsAnimating() const override;
+  void RestartTimersOnMouseHover() override;
 
   views::Widget* GetPromptBubbleWidgetForTesting();
 
@@ -75,6 +77,19 @@
   bool should_expand_for_testing() { return should_expand_; }
   OmniboxChipButton* get_chip_button_for_testing() { return chip_button_; }
 
+  void stop_animation_for_test() {
+    chip_button_->animation_for_testing()->Stop();
+    ExpandAnimationEnded();
+  }
+
+  bool is_collapse_timer_running_for_testing() {
+    return collapse_timer_.IsRunning();
+  }
+
+  bool is_dismiss_timer_running_for_testing() {
+    return dismiss_timer_.IsRunning();
+  }
+
  protected:
   // Returns a newly-created permission prompt bubble.
   virtual views::View* CreateBubble() WARN_UNUSED_RESULT = 0;
@@ -90,20 +105,19 @@
 
   virtual void OnPromptBubbleDismissed();
 
-  virtual bool ShouldCloseBubbleOnLostFocus() const;
-
  private:
-  // BubbleOwnerDelegate:
-  void RecordOnMousePressed() override;
-
   void Show(bool always_open_bubble);
   void ExpandAnimationEnded();
   void ChipButtonPressed();
-  void RestartTimersOnInteraction();
   void StartCollapseTimer();
   void StartDismissTimer();
   void Finalize();
 
+  void ResetTimers() {
+    collapse_timer_.AbandonAndStop();
+    dismiss_timer_.AbandonAndStop();
+  }
+
   void AnimateCollapse();
   void AnimateExpand();
 
diff --git a/chrome/browser/ui/views/location_bar/permission_chip_unittest.cc b/chrome/browser/ui/views/location_bar/permission_chip_unittest.cc
new file mode 100644
index 0000000..cce75ca
--- /dev/null
+++ b/chrome/browser/ui/views/location_bar/permission_chip_unittest.cc
@@ -0,0 +1,623 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/frame/test_with_browser_view.h"
+#include "chrome/browser/ui/views/location_bar/permission_quiet_chip.h"
+#include "chrome/browser/ui/views/location_bar/permission_request_chip.h"
+#include "components/permissions/test/mock_permission_request.h"
+#include "ui/events/base_event_utils.h"
+#include "ui/views/test/ax_event_counter.h"
+#include "ui/views/test/button_test_api.h"
+
+namespace {
+
+class TestDelegate : public permissions::PermissionPrompt::Delegate {
+ public:
+  explicit TestDelegate(
+      const GURL& origin,
+      const std::vector<permissions::RequestType> request_types) {
+    std::transform(
+        request_types.begin(), request_types.end(),
+        std::back_inserter(requests_), [&](auto& request_type) {
+          return std::make_unique<permissions::MockPermissionRequest>(
+              origin, request_type);
+        });
+    std::transform(requests_.begin(), requests_.end(),
+                   std::back_inserter(raw_requests_),
+                   [](auto& req) { return req.get(); });
+  }
+
+  const std::vector<permissions::PermissionRequest*>& Requests() override {
+    return raw_requests_;
+  }
+
+  GURL GetRequestingOrigin() const override {
+    return raw_requests_.front()->requesting_origin();
+  }
+
+  GURL GetEmbeddingOrigin() const override {
+    return GURL("https://embedder.example.com");
+  }
+
+  void Accept() override { requests_.clear(); }
+  void AcceptThisTime() override { requests_.clear(); }
+  void Deny() override { requests_.clear(); }
+  void Dismiss() override { requests_.clear(); }
+  void Ignore() override { requests_.clear(); }
+
+  bool WasCurrentRequestAlreadyDisplayed() override {
+    return was_current_request_already_displayed_;
+  }
+  bool ShouldDropCurrentRequestIfCannotShowQuietly() const override {
+    return false;
+  }
+  bool ShouldCurrentRequestUseQuietUI() const override { return false; }
+  absl::optional<permissions::PermissionUiSelector::QuietUiReason>
+  ReasonForUsingQuietUi() const override {
+    return absl::nullopt;
+  }
+  void SetDismissOnTabClose() override {}
+  void SetBubbleShown() override {}
+  void SetDecisionTime() override {}
+
+  bool IsRequestInProgress() { return !requests_.empty(); }
+
+  void SetAlreadyDisplayed() { was_current_request_already_displayed_ = true; }
+
+ private:
+  std::vector<std::unique_ptr<permissions::PermissionRequest>> requests_;
+  std::vector<permissions::PermissionRequest*> raw_requests_;
+  bool was_current_request_already_displayed_ = false;
+};
+}  // namespace
+
+class PermissionChipUnitTest : public TestWithBrowserView {
+ public:
+  PermissionChipUnitTest()
+      : TestWithBrowserView(
+            base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
+
+  PermissionChipUnitTest(const PermissionChipUnitTest&) = delete;
+  PermissionChipUnitTest& operator=(const PermissionChipUnitTest&) = delete;
+
+  void SetUp() override {
+    TestWithBrowserView::SetUp();
+
+    AddTab(browser(), GURL("http://a.com"));
+    web_contents_ = browser()->tab_strip_model()->GetWebContentsAt(0);
+  }
+
+  void ClickOnChip(PermissionChip& chip) {
+    views::test::ButtonTestApi(chip.button())
+        .NotifyClick(ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(),
+                                    gfx::Point(), ui::EventTimeForNow(),
+                                    ui::EF_LEFT_MOUSE_BUTTON, 0));
+    base::RunLoop().RunUntilIdle();
+  }
+
+  content::WebContents* web_contents_;
+
+  base::TimeDelta kChipCollapseDuration = base::Seconds(12);
+  base::TimeDelta kNormalChipDismissDuration = base::Seconds(6);
+  base::TimeDelta kQuietChipDismissDuration = base::Seconds(18);
+  base::TimeDelta kLongerThanAllTimersDuration = base::Seconds(50);
+};
+
+TEST_F(PermissionChipUnitTest, DisplayChipNoAutoPopupTest) {
+  TestDelegate delegate(GURL("https://test.origin"),
+                        {permissions::RequestType::kNotifications});
+  PermissionRequestChip chip(browser(), &delegate, false);
+
+  EXPECT_FALSE(chip.IsBubbleShowing());
+
+  // Due to animation issue, the collapse timer will not be started.
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  // Animation does not work. Most probably it is unit tests limitations.
+  // `chip.is_fully_collapsed()` will not work as well.
+  EXPECT_TRUE(chip.get_chip_button_for_testing()->is_animating());
+
+  // TODO(crbug.com/1271093): Fix animation callback for unit tests.
+  chip.stop_animation_for_test();
+  EXPECT_FALSE(chip.get_chip_button_for_testing()->is_animating());
+
+  EXPECT_TRUE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  // The chip collapse timer is 12 seconds. After 11 seconds the permission
+  // request should still be there.
+  task_environment()->AdvanceClock(kChipCollapseDuration - base::Seconds(1));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_TRUE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  // The collapse timer has 1 more second to go. Wait 2 seconds for the dismiss
+  // timer to start.
+  task_environment()->AdvanceClock(base::Seconds(2));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  // The collapse timer is fired and the dismiss timer is started.
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_TRUE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kNormalChipDismissDuration);
+  base::RunLoop().RunUntilIdle();
+  ASSERT_FALSE(delegate.IsRequestInProgress());
+}
+
+TEST_F(PermissionChipUnitTest, AlreadyDisplayedRequestTest) {
+  TestDelegate delegate(GURL("https://test.origin"),
+                        {permissions::RequestType::kNotifications});
+  delegate.SetAlreadyDisplayed();
+
+  EXPECT_TRUE(delegate.WasCurrentRequestAlreadyDisplayed());
+
+  PermissionRequestChip chip(browser(), &delegate, false);
+
+  EXPECT_FALSE(chip.IsBubbleShowing());
+
+  // The permission request was already displayed, hence the dismiss timer will
+  // be triggered directly after the chip is displayed.
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_TRUE(chip.is_dismiss_timer_running_for_testing());
+
+  // The default dismiss timer is 6 seconds. The chip should be still displayed
+  // after 5 seconds.
+  task_environment()->AdvanceClock(kNormalChipDismissDuration -
+                                   base::Seconds(1));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  // Wait 2 more seconds for the dismiss timer to finish.
+  task_environment()->AdvanceClock(base::Seconds(2));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_FALSE(delegate.IsRequestInProgress());
+}
+
+TEST_F(PermissionChipUnitTest, MultiClickOnChipNoAutoPopupTest) {
+  TestDelegate delegate(GURL("https://test.origin"),
+                        {permissions::RequestType::kNotifications});
+  PermissionRequestChip chip(browser(), &delegate, false);
+
+  EXPECT_FALSE(chip.IsBubbleShowing());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  // Animation does not work. Most probably it is unit tests limitations.
+  // `chip.is_fully_collapsed()` will not work as well.
+  EXPECT_TRUE(chip.get_chip_button_for_testing()->is_animating());
+  chip.stop_animation_for_test();
+  EXPECT_FALSE(chip.get_chip_button_for_testing()->is_animating());
+
+  EXPECT_TRUE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kChipCollapseDuration - base::Seconds(1));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_TRUE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  ClickOnChip(chip);
+  EXPECT_TRUE(chip.IsBubbleShowing());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  // After a very long time the permissin prompt popup bubble should still be
+  // visible.
+  task_environment()->AdvanceClock(kLongerThanAllTimersDuration);
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+  EXPECT_TRUE(chip.IsBubbleShowing());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  // The seconds click on the chip hides the popup bubble.
+  ClickOnChip(chip);
+  EXPECT_FALSE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  // After the second click, only dismiss timer should be active.
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_TRUE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kNormalChipDismissDuration -
+                                   base::Seconds(1));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  // The third click on the chip opens the popup bubble again.
+  ClickOnChip(chip);
+  EXPECT_TRUE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kLongerThanAllTimersDuration);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  ClickOnChip(chip);
+  EXPECT_FALSE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_TRUE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kNormalChipDismissDuration +
+                                   base::Seconds(1));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_FALSE(delegate.IsRequestInProgress());
+}
+
+TEST_F(PermissionChipUnitTest, DisplayChipAutoPopupTest) {
+  TestDelegate delegate(GURL("https://test.origin"),
+                        {permissions::RequestType::kNotifications});
+  PermissionRequestChip chip(browser(), &delegate, true);
+
+  // Due to animation issue, the collapse timer will not be started.
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  // Animation does not work. Most probably it is unit tests limitations.
+  // `chip.is_fully_collapsed()` will not work as well.
+  EXPECT_TRUE(chip.get_chip_button_for_testing()->is_animating());
+  chip.stop_animation_for_test();
+  EXPECT_FALSE(chip.get_chip_button_for_testing()->is_animating());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kLongerThanAllTimersDuration);
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+  // Bubble is showing automatically.
+  EXPECT_TRUE(chip.IsBubbleShowing());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  // The seconds click on the chip hides the popup bubble.
+  ClickOnChip(chip);
+  EXPECT_FALSE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  // After the second click, only dismiss timer should be active.
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_TRUE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kNormalChipDismissDuration +
+                                   base::Seconds(1));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_FALSE(delegate.IsRequestInProgress());
+}
+
+TEST_F(PermissionChipUnitTest, MultiClickOnChipAutoPopupTest) {
+  TestDelegate delegate(GURL("https://test.origin"),
+                        {permissions::RequestType::kNotifications});
+  PermissionRequestChip chip(browser(), &delegate, true);
+
+  EXPECT_FALSE(chip.IsBubbleShowing());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  // Animation does not work. Most probably it is unit tests limitations.
+  // `chip.is_fully_collapsed()` will not work as well.
+  EXPECT_TRUE(chip.get_chip_button_for_testing()->is_animating());
+  chip.stop_animation_for_test();
+  EXPECT_FALSE(chip.get_chip_button_for_testing()->is_animating());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  // The permission prompt bubble is open automatically
+  EXPECT_TRUE(chip.IsBubbleShowing());
+
+  task_environment()->AdvanceClock(kLongerThanAllTimersDuration);
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+  EXPECT_TRUE(chip.IsBubbleShowing());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  ClickOnChip(chip);
+
+  // The permission prompt bubble is open automatically, hence the first click
+  // on the chip should close the bubble.
+  EXPECT_FALSE(chip.IsBubbleShowing());
+  EXPECT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_TRUE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kNormalChipDismissDuration -
+                                   base::Seconds(1));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  ClickOnChip(chip);
+  EXPECT_TRUE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kLongerThanAllTimersDuration);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  ClickOnChip(chip);
+  EXPECT_FALSE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_TRUE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kNormalChipDismissDuration +
+                                   base::Seconds(1));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_FALSE(delegate.IsRequestInProgress());
+}
+
+TEST_F(PermissionChipUnitTest, DisplayQuietChipNoAbusiveTest) {
+  TestDelegate delegate(GURL("https://test.origin"),
+                        {permissions::RequestType::kNotifications});
+  PermissionQuietChip chip(browser(), &delegate, true);
+
+  EXPECT_FALSE(chip.IsBubbleShowing());
+
+  // Due to animation issue, the collapse timer will not be started.
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  // Animation does not work. Most probably it is unit tests limitations.
+  // `chip.is_fully_collapsed()` will not work as well.
+  EXPECT_TRUE(chip.get_chip_button_for_testing()->is_animating());
+  chip.stop_animation_for_test();
+  EXPECT_FALSE(chip.get_chip_button_for_testing()->is_animating());
+
+  EXPECT_TRUE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kChipCollapseDuration - base::Seconds(1));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_TRUE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  // Wait 2 more seconds for the collapse timer to finish.
+  task_environment()->AdvanceClock(base::Seconds(2));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  // The collapse timer is fired and the dismiss timer is started.
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_TRUE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kNormalChipDismissDuration +
+                                   base::Seconds(1));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_FALSE(delegate.IsRequestInProgress());
+}
+
+TEST_F(PermissionChipUnitTest, MultiClickOnQuietChipNoAbusiveTest) {
+  TestDelegate delegate(GURL("https://test.origin"),
+                        {permissions::RequestType::kNotifications});
+  PermissionQuietChip chip(browser(), &delegate, true);
+
+  EXPECT_FALSE(chip.IsBubbleShowing());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  // Animation does not work. Most probably it is unit tests limitations.
+  // `chip.is_fully_collapsed()` will not work as well.
+  EXPECT_TRUE(chip.get_chip_button_for_testing()->is_animating());
+  chip.stop_animation_for_test();
+  EXPECT_FALSE(chip.get_chip_button_for_testing()->is_animating());
+
+  EXPECT_TRUE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+  EXPECT_FALSE(chip.IsBubbleShowing());
+
+  // The chip collapse timer is 12 seconds.
+  task_environment()->AdvanceClock(kChipCollapseDuration - base::Seconds(1));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_TRUE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  ClickOnChip(chip);
+  EXPECT_TRUE(chip.IsBubbleShowing());
+
+  // Collapse timer was restarted.
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  // After 30 seconds the permissin prompt popup bubble should still be visible.
+  task_environment()->AdvanceClock(kLongerThanAllTimersDuration);
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+  EXPECT_TRUE(chip.IsBubbleShowing());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  // The seconds click on the chip hides the popup bubble.
+  ClickOnChip(chip);
+  EXPECT_FALSE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  // After the second click, only dismiss timer should be active.
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_TRUE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kNormalChipDismissDuration -
+                                   base::Seconds(1));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  // The third click on the chip opens the popup bubble again.
+  ClickOnChip(chip);
+  EXPECT_TRUE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kLongerThanAllTimersDuration);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  ClickOnChip(chip);
+  EXPECT_FALSE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_TRUE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kNormalChipDismissDuration +
+                                   base::Seconds(1));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_FALSE(delegate.IsRequestInProgress());
+}
+
+TEST_F(PermissionChipUnitTest, DisplayQuietChipAbusiveTest) {
+  TestDelegate delegate(GURL("https://test.origin"),
+                        {permissions::RequestType::kNotifications});
+  PermissionQuietChip chip(browser(), &delegate, false);
+
+  EXPECT_FALSE(chip.IsBubbleShowing());
+
+  // The quiet abusive chip does not have animation and will start the dismiss
+  // timer immediately after displaying.
+  EXPECT_FALSE(chip.get_chip_button_for_testing()->is_animating());
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_TRUE(chip.is_dismiss_timer_running_for_testing());
+
+  // The dismiss timer is 18 seconds by default. After 17 seconds, the chip
+  // should be there.
+  task_environment()->AdvanceClock(kQuietChipDismissDuration -
+                                   base::Seconds(1));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_TRUE(chip.is_dismiss_timer_running_for_testing());
+
+  // Wait 2 more seconds for the dismiss timer to finish.
+  task_environment()->AdvanceClock(base::Seconds(2));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_FALSE(delegate.IsRequestInProgress());
+}
+
+TEST_F(PermissionChipUnitTest, MultiClickOnQuietChipAbusiveTest) {
+  TestDelegate delegate(GURL("https://test.origin"),
+                        {permissions::RequestType::kNotifications});
+  PermissionQuietChip chip(browser(), &delegate, false);
+
+  EXPECT_FALSE(chip.IsBubbleShowing());
+
+  // The quiet abusive chip does not have animation and will start the dismiss
+  // timer immediately after displaying.
+  EXPECT_FALSE(chip.get_chip_button_for_testing()->is_animating());
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_TRUE(chip.is_dismiss_timer_running_for_testing());
+
+  // The dismiss timer is 18 seconds by default. After 17 seconds, the chip
+  // should be there.
+  task_environment()->AdvanceClock(kQuietChipDismissDuration -
+                                   base::Seconds(1));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_TRUE(chip.is_dismiss_timer_running_for_testing());
+
+  ClickOnChip(chip);
+  EXPECT_TRUE(chip.IsBubbleShowing());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kLongerThanAllTimersDuration);
+  base::RunLoop().RunUntilIdle();
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+  EXPECT_TRUE(chip.IsBubbleShowing());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  // The seconds click on the chip hides the popup bubble.
+  ClickOnChip(chip);
+  EXPECT_FALSE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  // After the second click, only dismiss timer should be active.
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_TRUE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kQuietChipDismissDuration -
+                                   base::Seconds(1));
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  // The third click on the chip opens the popup bubble again.
+  ClickOnChip(chip);
+  EXPECT_TRUE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kLongerThanAllTimersDuration);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_FALSE(chip.is_dismiss_timer_running_for_testing());
+
+  ClickOnChip(chip);
+  EXPECT_FALSE(chip.IsBubbleShowing());
+  ASSERT_TRUE(delegate.IsRequestInProgress());
+
+  EXPECT_FALSE(chip.is_collapse_timer_running_for_testing());
+  EXPECT_TRUE(chip.is_dismiss_timer_running_for_testing());
+
+  task_environment()->AdvanceClock(kQuietChipDismissDuration +
+                                   base::Seconds(1));
+  base::RunLoop().RunUntilIdle();
+  ASSERT_FALSE(delegate.IsRequestInProgress());
+}
diff --git a/chrome/browser/ui/views/location_bar/permission_quiet_chip.cc b/chrome/browser/ui/views/location_bar/permission_quiet_chip.cc
index b36c509..e67c77f 100644
--- a/chrome/browser/ui/views/location_bar/permission_quiet_chip.cc
+++ b/chrome/browser/ui/views/location_bar/permission_quiet_chip.cc
@@ -87,16 +87,14 @@
     bubble_widget->AddObserver(this);
     bubble_widget->Show();
 
+    quiet_request_bubble->set_close_on_deactivate(false);
+
     return quiet_request_bubble;
   }
 
   return nullptr;
 }
 
-bool PermissionQuietChip::ShouldCloseBubbleOnLostFocus() const {
-  return true;
-}
-
 void PermissionQuietChip::RecordChipButtonPressed() {
   base::UmaHistogramMediumTimes("Permissions.QuietChip.TimeToInteraction",
                                 base::TimeTicks::Now() - chip_shown_time_);
diff --git a/chrome/browser/ui/views/location_bar/permission_quiet_chip.h b/chrome/browser/ui/views/location_bar/permission_quiet_chip.h
index e84cd3ee..7a5a3277 100644
--- a/chrome/browser/ui/views/location_bar/permission_quiet_chip.h
+++ b/chrome/browser/ui/views/location_bar/permission_quiet_chip.h
@@ -27,7 +27,6 @@
  private:
   // PermissionChip:
   views::View* CreateBubble() override;
-  bool ShouldCloseBubbleOnLostFocus() const override;
 
   void RecordChipButtonPressed();
   LocationBarView* GetLocationBarView();
diff --git a/chrome/browser/ui/views/location_bar/permission_request_chip.cc b/chrome/browser/ui/views/location_bar/permission_request_chip.cc
index 3d22a3c..04948ef 100644
--- a/chrome/browser/ui/views/location_bar/permission_request_chip.cc
+++ b/chrome/browser/ui/views/location_bar/permission_request_chip.cc
@@ -122,18 +122,6 @@
   return prompt_bubble;
 }
 
-void PermissionRequestChip::Collapse(bool allow_restart) {
-  PermissionChip::Collapse(allow_restart);
-  if (!IsBubbleShowing()) {
-    ShowBlockedIcon();
-  }
-}
-
-void PermissionRequestChip::OnPromptBubbleDismissed() {
-  PermissionChip::OnPromptBubbleDismissed();
-  ShowBlockedIcon();
-}
-
 void PermissionRequestChip::RecordChipButtonPressed() {
   base::UmaHistogramMediumTimes("Permissions.Chip.TimeToInteraction",
                                 base::TimeTicks::Now() - chip_shown_time_);
diff --git a/chrome/browser/ui/views/location_bar/permission_request_chip.h b/chrome/browser/ui/views/location_bar/permission_request_chip.h
index b076ebf..3867e39 100644
--- a/chrome/browser/ui/views/location_bar/permission_request_chip.h
+++ b/chrome/browser/ui/views/location_bar/permission_request_chip.h
@@ -29,8 +29,6 @@
  private:
   // PermissionChip:
   views::View* CreateBubble() override;
-  void Collapse(bool allow_restart) override;
-  void OnPromptBubbleDismissed() override;
 
   void RecordChipButtonPressed();
 
diff --git a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view.cc b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view.cc
index d1bd8d6a..4e46d3c 100644
--- a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view.cc
+++ b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view.cc
@@ -27,7 +27,8 @@
 #include "ui/views/controls/button/md_text_button.h"
 #include "ui/views/controls/label.h"
 #include "ui/views/controls/styled_label.h"
-#include "ui/views/layout/grid_layout.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/layout/box_layout_view.h"
 #include "ui/views/style/typography.h"
 #include "ui/views/widget/widget.h"
 #include "url/gurl.h"
@@ -97,21 +98,16 @@
   new_title->SetText(title_text);
   new_title->AddStyleRange(gfx::Range(0, title_text.length()), name_style);
   GetBubbleFrameView()->SetTitleView(std::move(new_title));
-
-  ChromeLayoutProvider* layout_provider = ChromeLayoutProvider::Get();
-
-  gfx::Insets insets = layout_provider->GetDialogInsetsForContentType(
-      views::DialogContentType::kText, views::DialogContentType::kText);
-  set_margins(gfx::Insets(0, 0, insets.bottom(), 0));
+  set_margins(gfx::Insets(0, 0, margins().bottom(), 0));
 
   // Configure layout.
-  views::GridLayout* bubble_layout =
-      SetLayoutManager(std::make_unique<views::GridLayout>());
-  constexpr int kColumnId = 0;
-  views::ColumnSet* bubble_col_set = bubble_layout->AddColumnSet(kColumnId);
-  bubble_col_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL,
-                            1.0, views::GridLayout::ColumnSize::kUsePreferred,
-                            0, 0);
+  ChromeLayoutProvider* layout_provider = ChromeLayoutProvider::Get();
+  gfx::Insets insets = layout_provider->GetDialogInsetsForContentType(
+      views::DialogContentType::kText, views::DialogContentType::kText);
+  SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kVertical,
+      gfx::Insets(insets.top(), insets.left(), 0, insets.right()),
+      insets.bottom()));
 
   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
   const bool use_dark =
@@ -122,43 +118,50 @@
   auto image_view = std::make_unique<NonAccessibleImageView>();
   image_view->SetImage(*image);
   views::BubbleFrameView* frame_view = GetBubbleFrameView();
-  CHECK(frame_view);
   frame_view->SetHeaderView(std::move(image_view));
 
-  auto bottom_view = std::make_unique<views::View>();
-  views::GridLayout* bottom_layout =
-      bottom_view->SetLayoutManager(std::make_unique<views::GridLayout>());
-  views::ColumnSet* bottom_column_set = bottom_layout->AddColumnSet(0);
-  bottom_column_set->AddPaddingColumn(views::GridLayout::kFixedSize,
-                                      insets.left());
-  bottom_column_set->AddColumn(
-      views::GridLayout::LEADING, views::GridLayout::FILL, 1.0,
-      views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
-  bottom_column_set->AddPaddingColumn(views::GridLayout::kFixedSize,
-                                      insets.right());
-
   // Add text description.
-  const int spacing =
-      layout_provider->GetDistanceMetric(DISTANCE_CONTROL_LIST_VERTICAL);
-  bottom_layout->StartRowWithPadding(views::GridLayout::kFixedSize, kColumnId,
-                                     views::GridLayout::kFixedSize, spacing);
-  auto text_label = std::make_unique<views::Label>(
-      GetSafetyTipDescription(safety_tip_status, suggested_url_));
+  auto* text_label = AddChildView(std::make_unique<views::Label>(
+      GetSafetyTipDescription(safety_tip_status, suggested_url_)));
   text_label->SetMultiLine(true);
   text_label->SetLineHeight(20);
   text_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
   text_label->SizeToFit(layout_provider->GetDistanceMetric(
                             views::DISTANCE_BUBBLE_PREFERRED_WIDTH) -
                         insets.left() - insets.right());
-  bottom_layout->AddView(std::move(text_label));
 
-  MaybeAddButtons(safety_tip_status, bottom_layout, spacing, kColumnId, insets);
+  // Suspicious site safety tips don't have a call to action, as they are used
+  // for drawing users' attention to the omnibox to see if they leave the site
+  // on their own once they notice the omnibox. (https://crbug.com/1146471)
+  if (safety_tip_status != security_state::SafetyTipStatus::kBadReputation) {
+    auto* button_view = AddChildView(std::make_unique<views::BoxLayoutView>());
+    button_view->SetCrossAxisAlignment(
+        views::BoxLayout::CrossAxisAlignment::kCenter);
 
-  bubble_layout->StartRow(views::GridLayout::kFixedSize, kColumnId);
-  bubble_layout->AddView(std::move(bottom_view));
+    // Learn more link.
+    info_link_ = button_view->AddChildView(std::make_unique<views::Link>(
+        l10n_util::GetStringUTF16(IDS_PAGE_INFO_SAFETY_TIP_MORE_INFO_LINK)));
+    info_link_->SetCallback(base::BindRepeating(
+        &SafetyTipPageInfoBubbleView::OpenHelpCenter, base::Unretained(this)));
 
-  Layout();
-  SizeToContents();
+    auto* flex_view =
+        button_view->AddChildView(std::make_unique<views::View>());
+    button_view->SetFlexForView(flex_view, 1);
+
+    // Leave site button.
+    leave_button_ =
+        button_view->AddChildView(std::make_unique<views::MdTextButton>(
+            base::BindRepeating(
+                [](SafetyTipPageInfoBubbleView* view) {
+                  view->ExecuteLeaveCommand();
+                },
+                this),
+            l10n_util::GetStringUTF16(
+                GetSafetyTipLeaveButtonId(safety_tip_status))));
+    leave_button_->SetProminent(true);
+    leave_button_->SetID(
+        PageInfoViewFactory::VIEW_ID_PAGE_INFO_BUTTON_LEAVE_SITE);
+  }
 }
 
 SafetyTipPageInfoBubbleView::~SafetyTipPageInfoBubbleView() {}
@@ -252,69 +255,6 @@
   // Do nothing. (Base class closes the bubble.)
 }
 
-void SafetyTipPageInfoBubbleView::MaybeAddButtons(
-    security_state::SafetyTipStatus safety_tip_status,
-    views::GridLayout* bottom_layout,
-    int spacing,
-    int column_id,
-    const gfx::Insets& insets) {
-  // Suspicious site safety tips don't have a call to action, as they are used
-  // for drawing users' attention to the omnibox to see if they leave the site
-  // on their own once they notice the omnibox. (https://crbug.com/1146471)
-  if (safety_tip_status == security_state::SafetyTipStatus::kBadReputation)
-    return;
-
-  ChromeLayoutProvider* layout_provider = ChromeLayoutProvider::Get();
-  // To make the rest of the layout simpler, they live in their own grid layout.
-  auto button_view = std::make_unique<views::View>();
-  views::GridLayout* button_layout =
-      button_view->SetLayoutManager(std::make_unique<views::GridLayout>());
-  views::ColumnSet* button_column_set = button_layout->AddColumnSet(0);
-  button_column_set->AddColumn(
-      views::GridLayout::LEADING, views::GridLayout::CENTER, 0.0,
-      views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
-  button_column_set->AddPaddingColumn(1.f, 1);
-  button_column_set->AddColumn(
-      views::GridLayout::TRAILING, views::GridLayout::FILL, 0.0,
-      views::GridLayout::ColumnSize::kUsePreferred, 0, 0);
-
-  button_layout->StartRow(views::GridLayout::kFixedSize, column_id);
-
-  // More info button.
-  auto info_text =
-      l10n_util::GetStringUTF16(IDS_PAGE_INFO_SAFETY_TIP_MORE_INFO_LINK);
-  auto info_link = std::make_unique<views::StyledLabel>();
-  info_link->SetText(info_text);
-  views::StyledLabel::RangeStyleInfo link_style =
-      views::StyledLabel::RangeStyleInfo::CreateForLink(
-          base::BindRepeating(&SafetyTipPageInfoBubbleView::OpenHelpCenter,
-                              base::Unretained(this)));
-  gfx::Range details_range(0, info_text.length());
-  info_link->AddStyleRange(details_range, link_style);
-  info_link->SizeToFit(0);
-  info_button_ = button_layout->AddView(std::move(info_link));
-  // Leave site button.
-  auto leave_button = std::make_unique<views::MdTextButton>(
-      base::BindRepeating(
-          [](SafetyTipPageInfoBubbleView* view) {
-            view->ExecuteLeaveCommand();
-          },
-          this),
-      l10n_util::GetStringUTF16(GetSafetyTipLeaveButtonId(safety_tip_status)));
-  leave_button->SetProminent(true);
-  leave_button->SetID(PageInfoViewFactory::VIEW_ID_PAGE_INFO_BUTTON_LEAVE_SITE);
-  leave_button_ = button_layout->AddView(std::move(leave_button));
-
-  bottom_layout->StartRowWithPadding(views::GridLayout::kFixedSize, column_id,
-                                     views::GridLayout::kFixedSize, spacing);
-  bottom_layout->AddView(std::move(button_view), 1, 1,
-                         views::GridLayout::LEADING, views::GridLayout::LEADING,
-                         layout_provider->GetDistanceMetric(
-                             views::DISTANCE_BUBBLE_PREFERRED_WIDTH) -
-                             insets.left() - insets.right(),
-                         0);
-}
-
 void ShowSafetyTipDialog(
     content::WebContents* web_contents,
     security_state::SafetyTipStatus safety_tip_status,
diff --git a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view.h b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view.h
index efa0949..2beec54 100644
--- a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view.h
+++ b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view.h
@@ -9,7 +9,7 @@
 #include "chrome/browser/ui/views/page_info/page_info_bubble_view_base.h"
 #include "components/security_state/core/security_state.h"
 #include "content/public/browser/visibility.h"
-#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/button/md_text_button.h"
 #include "ui/views/controls/styled_label.h"
 
 namespace content {
@@ -21,7 +21,7 @@
 }  // namespace gfx
 
 namespace views {
-class GridLayout;
+class Link;
 class View;
 class Widget;
 }  // namespace views
@@ -68,21 +68,14 @@
   void PrimaryPageChanged(content::Page& page) override;
   void DidChangeVisibleSecurityState() override;
 
-  void MaybeAddButtons(security_state::SafetyTipStatus safety_tip_status,
-                       views::GridLayout* bottom_layout,
-                       int spacing,
-                       int column_id,
-                       const gfx::Insets& insets);
-
   const security_state::SafetyTipStatus safety_tip_status_;
 
   // The URL of the page the Safety Tip suggests you intended to go to, when
   // applicable (for SafetyTipStatus::kLookalike).
   const GURL suggested_url_;
 
-  views::StyledLabel* info_button_ = nullptr;
-  views::Button* ignore_button_ = nullptr;
-  views::Button* leave_button_ = nullptr;
+  views::Link* info_link_ = nullptr;
+  views::MdTextButton* leave_button_ = nullptr;
   base::OnceCallback<void(SafetyTipInteraction)> close_callback_;
   SafetyTipInteraction action_taken_ = SafetyTipInteraction::kNoAction;
 };
diff --git a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
index 00eaa33..39c180d8 100644
--- a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
@@ -18,10 +18,12 @@
 #include "chrome/browser/history/history_test_utils.h"
 #include "chrome/browser/reputation/reputation_service.h"
 #include "chrome/browser/reputation/reputation_web_contents_observer.h"
+#include "chrome/browser/reputation/safety_tip_ui.h"
 #include "chrome/browser/reputation/safety_tip_ui_helper.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_navigator.h"
+#include "chrome/browser/ui/test/test_browser_dialog.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/location_bar/location_icon_view.h"
 #include "chrome/browser/ui/views/page_info/page_info_bubble_view.h"
@@ -338,8 +340,7 @@
   void CheckNoButtons() {
     auto* bubble = static_cast<SafetyTipPageInfoBubbleView*>(
         PageInfoBubbleViewBase::GetPageInfoBubbleForTesting());
-    EXPECT_FALSE(bubble->info_button_);
-    EXPECT_FALSE(bubble->ignore_button_);
+    EXPECT_FALSE(bubble->info_link_);
     EXPECT_FALSE(bubble->leave_button_);
   }
 
@@ -1744,3 +1745,35 @@
   EXPECT_TRUE(IsUIShowing());
   histograms.ExpectTotalCount(kHistogramName, 1);
 }
+
+class SafetyTipPageInfoBubbleViewDialogTest : public DialogBrowserTest {
+ public:
+  SafetyTipPageInfoBubbleViewDialogTest() = default;
+  SafetyTipPageInfoBubbleViewDialogTest(
+      const SafetyTipPageInfoBubbleViewDialogTest&) = delete;
+  SafetyTipPageInfoBubbleViewDialogTest& operator=(
+      const SafetyTipPageInfoBubbleViewDialogTest&) = delete;
+  ~SafetyTipPageInfoBubbleViewDialogTest() override = default;
+
+  void ShowUi(const std::string& name) override {
+    auto status = security_state::SafetyTipStatus::kUnknown;
+    if (name == "BadReputation")
+      status = security_state::SafetyTipStatus::kBadReputation;
+    else if (name == "Lookalike")
+      status = security_state::SafetyTipStatus::kLookalike;
+
+    ShowSafetyTipDialog(browser()->tab_strip_model()->GetActiveWebContents(),
+                        status, GURL("https://www.google.tld"),
+                        base::DoNothing());
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewDialogTest,
+                       InvokeUi_BadReputation) {
+  ShowAndVerifyUi();
+}
+
+IN_PROC_BROWSER_TEST_F(SafetyTipPageInfoBubbleViewDialogTest,
+                       InvokeUi_Lookalike) {
+  ShowAndVerifyUi();
+}
diff --git a/chrome/browser/ui/views/payments/payment_request_show_promise_browsertest.cc b/chrome/browser/ui/views/payments/payment_request_show_promise_browsertest.cc
index 01adee07..80c107f9 100644
--- a/chrome/browser/ui/views/payments/payment_request_show_promise_browsertest.cc
+++ b/chrome/browser/ui/views/payments/payment_request_show_promise_browsertest.cc
@@ -45,10 +45,11 @@
                                  DialogEvent::SPEC_DONE_UPDATING,
                                  DialogEvent::PROCESSING_SPINNER_HIDDEN,
                                  DialogEvent::DIALOG_OPENED});
-    // The boolean "true" makes the payment method be the URL of the webpage,
+    // buyWithCurrentUrl() uses the URL of the webpage as the payment method,
     // which is necessary because service workers cannot use "basic-card"
     // payment method (the default payment method of the test page).
-    ASSERT_TRUE(content::ExecuteScript(GetActiveWebContents(), "buy(true);"));
+    ASSERT_TRUE(content::ExecuteScript(GetActiveWebContents(),
+                                       "buyWithCurrentUrlMethod();"));
     WaitForObservedEvent();
     EXPECT_TRUE(web_modal::WebContentsModalDialogManager::FromWebContents(
                     GetActiveWebContents())
diff --git a/chrome/browser/ui/views/permission_bubble/permission_chip_interactive_test.cc b/chrome/browser/ui/views/permission_bubble/permission_chip_interactive_test.cc
index cc5acb9..c79bf36 100644
--- a/chrome/browser/ui/views/permission_bubble/permission_chip_interactive_test.cc
+++ b/chrome/browser/ui/views/permission_bubble/permission_chip_interactive_test.cc
@@ -917,8 +917,7 @@
 
 // Test that the quiet prompt disposition differs when permission is considered
 // abusive (currently only applicable for Notifications) vs. when permission is
-// not considered abusive. For `QuietUiReason::kTriggeredDueToAbusiveContent`
-// reputation we show a static UI icon.
+// not considered abusive.
 IN_PROC_BROWSER_TEST_F(QuietChipPermissionPromptBubbleViewInteractiveTest,
                        DispositionAbusiveContentTest) {
   SetCannedUiDecision(QuietUiReason::kTriggeredDueToAbusiveContent,
@@ -962,8 +961,6 @@
           LOCATION_BAR_LEFT_QUIET_ABUSIVE_CHIP);
 }
 
-// For `QuietUiReason::kEnabledInPrefs` reputation we show an animated quiet UI
-// icon.
 IN_PROC_BROWSER_TEST_F(QuietChipPermissionPromptBubbleViewInteractiveTest,
                        DispositionEnabledInPrefsTest) {
   SetCannedUiDecision(QuietUiReason::kEnabledInPrefs, absl::nullopt);
@@ -984,8 +981,6 @@
       permissions::PermissionPromptDisposition::LOCATION_BAR_LEFT_QUIET_CHIP);
 }
 
-// For `QuietUiReason::kPredictedVeryUnlikelyGrant` reputation we show an
-// animated quiet UI icon.
 IN_PROC_BROWSER_TEST_F(QuietChipPermissionPromptBubbleViewInteractiveTest,
                        DispositionPredictedVeryUnlikelyGrantTest) {
   SetCannedUiDecision(QuietUiReason::kPredictedVeryUnlikelyGrant,
@@ -1007,8 +1002,6 @@
       permissions::PermissionPromptDisposition::LOCATION_BAR_LEFT_QUIET_CHIP);
 }
 
-// For `QuietUiReason::kTriggeredDueToAbusiveRequests` reputation we show a
-// static quiet UI icon.
 IN_PROC_BROWSER_TEST_F(QuietChipPermissionPromptBubbleViewInteractiveTest,
                        DispositionAbusiveRequestsTest) {
   SetCannedUiDecision(QuietUiReason::kTriggeredDueToAbusiveRequests,
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc b/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
index e18659e..215e110 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_controller.cc
@@ -294,7 +294,7 @@
                                            const Tab* intended_tab) {
   // Make sure the hover card isn't accidentally shown if it's already visible
   // or if the anchor is gone or changed.
-  if (hover_card_ || !target_tab_ || target_tab_ != intended_tab)
+  if (hover_card_ || !TargetTabIsValid() || target_tab_ != intended_tab)
     return;
 
   CreateHoverCard(target_tab_);
@@ -508,9 +508,17 @@
   return hover_card_->GetAnchorView();
 }
 
+bool TabHoverCardController::TargetTabIsValid() const {
+  return target_tab_ && tab_strip_->GetModelIndexOf(target_tab_) >= 0 &&
+         !target_tab_->closing();
+}
+
 void TabHoverCardController::OnCardFullyVisible() {
-  const bool has_preview = ArePreviewsEnabled() && !target_tab_->IsActive() &&
-                           !waiting_for_preview();
+  // We have to do a bunch of validity checks here because this happens on a
+  // callback and so the tab may no longer be valid (or part of the original
+  // tabstrip).
+  const bool has_preview = ArePreviewsEnabled() && TargetTabIsValid() &&
+                           !target_tab_->IsActive() && !waiting_for_preview();
   metrics_->CardFullyVisibleOnTab(target_tab_, has_preview);
 }
 
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_controller.h b/chrome/browser/ui/views/tabs/tab_hover_card_controller.h
index 4e08021..a502be9 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_controller.h
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_controller.h
@@ -87,6 +87,10 @@
 
   const views::View* GetTargetAnchorView() const;
 
+  // Determines if `target_tab_` is still valid. Call this when entering
+  // TabHoverCardController from an asynchronous callback.
+  bool TargetTabIsValid() const;
+
   // Helper for recording metrics when a card becomes fully visible to the user.
   void OnCardFullyVisible();
 
diff --git a/chrome/browser/ui/views/tabs/tab_hover_card_controller_unittest.cc b/chrome/browser/ui/views/tabs/tab_hover_card_controller_unittest.cc
index 981c99f..0a6f714 100644
--- a/chrome/browser/ui/views/tabs/tab_hover_card_controller_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_hover_card_controller_unittest.cc
@@ -4,12 +4,16 @@
 
 #include "chrome/browser/ui/views/tabs/tab_hover_card_controller.h"
 
-#include "testing/gtest/include/gtest/gtest.h"
+#include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/frame/test_with_browser_view.h"
 
 // These are regression tests for possible crashes.
 
-TEST(TabHoverCardControllerTest, ShowWrongTabDoesntCrash) {
-  auto controller = std::make_unique<TabHoverCardController>(nullptr);
+class TabHoverCardControllerTest : public TestWithBrowserView {};
+
+TEST_F(TabHoverCardControllerTest, ShowWrongTabDoesntCrash) {
+  auto controller =
+      std::make_unique<TabHoverCardController>(browser_view()->tabstrip());
   // Create some completely invalid pointer values (these should never be
   // dereferenced).
   Tab* const tab1 = reinterpret_cast<Tab*>(3);
@@ -20,8 +24,9 @@
   controller->ShowHoverCard(false, tab2);
 }
 
-TEST(TabHoverCardControllerTest, SetPreviewWithNoHoverCardDoesntCrash) {
-  auto controller = std::make_unique<TabHoverCardController>(nullptr);
+TEST_F(TabHoverCardControllerTest, SetPreviewWithNoHoverCardDoesntCrash) {
+  auto controller =
+      std::make_unique<TabHoverCardController>(browser_view()->tabstrip());
   // If the safeguard is not in place, this could crash in either metrics
   // collection *or* in trying to set the actual thumbnail image on the card.
   controller->OnPreviewImageAvaialble(controller->thumbnail_observer_.get(),
diff --git a/chrome/browser/ui/webui/chromeos/shimless_rma_dialog.cc b/chrome/browser/ui/webui/chromeos/shimless_rma_dialog.cc
new file mode 100644
index 0000000..3b422d9
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/shimless_rma_dialog.cc
@@ -0,0 +1,71 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/webui/chromeos/shimless_rma_dialog.h"
+
+#include <string>
+
+#include "ash/webui/shimless_rma/url_constants.h"
+#include "ui/aura/window.h"
+#include "ui/display/display.h"
+#include "ui/display/screen.h"
+#include "ui/views/widget/widget.h"
+
+namespace chromeos {
+// static
+void ShimlessRmaDialog::ShowDialog() {
+  ShimlessRmaDialog* dialog = new ShimlessRmaDialog();
+  dialog->ShowSystemDialog(nullptr);
+}
+
+ShimlessRmaDialog::ShimlessRmaDialog()
+    : SystemWebDialogDelegate(GURL(ash::kChromeUIShimlessRMAUrl),
+                              /*title=*/std::u16string()) {
+  // MODAL_TYPE_SYSTEM renders over OOBE/login screens, but does not support
+  // ui::SHOW_STATE_FULLSCREEN correctly.
+  // This dialog uses DisplayObserver::OnDisplayMetricsChanged to update the
+  // window size as screen size changes.
+  set_modal_type(ui::ModalType::MODAL_TYPE_SYSTEM);
+  set_can_minimize(false);
+}
+
+ShimlessRmaDialog::~ShimlessRmaDialog() = default;
+
+const std::string& ShimlessRmaDialog::Id() {
+  return id_;
+}
+
+void ShimlessRmaDialog::AdjustWidgetInitParams(
+    views::Widget::InitParams* params) {
+  params->type = views::Widget::InitParams::Type::TYPE_WINDOW_FRAMELESS;
+  params->visible_on_all_workspaces = true;
+  params->corner_radius = 0;
+  params->show_state = ui::SHOW_STATE_FULLSCREEN;
+  params->remove_standard_frame = true;
+  params->opacity = views::Widget::InitParams::WindowOpacity::kOpaque;
+}
+
+void ShimlessRmaDialog::GetDialogSize(gfx::Size* size) const {
+  *size = display::Screen::GetScreen()->GetPrimaryDisplay().size();
+}
+
+bool ShimlessRmaDialog::ShouldShowCloseButton() const {
+  return false;
+}
+
+bool ShimlessRmaDialog::ShouldCloseDialogOnEscape() const {
+  return false;
+}
+
+bool ShimlessRmaDialog::CanMaximizeDialog() const {
+  return false;
+}
+
+void ShimlessRmaDialog::OnDisplayMetricsChanged(const display::Display& display,
+                                                uint32_t changed_metrics) {
+  dialog_window()->SetBounds(
+      display::Screen::GetScreen()->GetPrimaryDisplay().bounds());
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/shimless_rma_dialog.h b/chrome/browser/ui/webui/chromeos/shimless_rma_dialog.h
new file mode 100644
index 0000000..a951760
--- /dev/null
+++ b/chrome/browser/ui/webui/chromeos/shimless_rma_dialog.h
@@ -0,0 +1,46 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_SHIMLESS_RMA_DIALOG_H_
+#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_SHIMLESS_RMA_DIALOG_H_
+
+#include "chrome/browser/ui/webui/chromeos/system_web_dialog_delegate.h"
+#include "ui/display/display_observer.h"
+#include "ui/gfx/native_widget_types.h"
+#include "ui/views/widget/widget.h"
+
+namespace chromeos {
+
+class ShimlessRmaDialog : public SystemWebDialogDelegate,
+                          public display::DisplayObserver {
+ public:
+  static void ShowDialog();
+
+ protected:
+  ShimlessRmaDialog();
+  ~ShimlessRmaDialog() override;
+
+  ShimlessRmaDialog(const ShimlessRmaDialog&) = delete;
+  ShimlessRmaDialog& operator=(const ShimlessRmaDialog&) = delete;
+
+  // SystemWebDialogDelegate
+  const std::string& Id() override;
+  void AdjustWidgetInitParams(views::Widget::InitParams* params) override;
+
+  // ui::WebDialogDelegate
+  void GetDialogSize(gfx::Size* size) const override;
+  bool ShouldShowCloseButton() const override;
+  bool ShouldCloseDialogOnEscape() const override;
+  bool CanMaximizeDialog() const override;
+
+ private:
+  void OnDisplayMetricsChanged(const display::Display& display,
+                               uint32_t changed_metrics) override;
+
+  const std::string id_ = "shimless-rma-dialog";
+  display::ScopedDisplayObserver display_observer_{this};
+};
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_UI_WEBUI_CHROMEOS_SHIMLESS_RMA_DIALOG_H_
diff --git a/chrome/browser/ui/webui/settings/chromeos/metrics_consent_handler.cc b/chrome/browser/ui/webui/settings/chromeos/metrics_consent_handler.cc
deleted file mode 100644
index be9ebaa0..0000000
--- a/chrome/browser/ui/webui/settings/chromeos/metrics_consent_handler.cc
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/webui/settings/chromeos/metrics_consent_handler.h"
-
-#include "ash/components/settings/cros_settings_names.h"
-#include "base/check.h"
-#include "chrome/browser/ash/settings/stats_reporting_controller.h"
-#include "chrome/browser/browser_process.h"
-#include "components/user_manager/user_manager.h"
-
-namespace chromeos {
-namespace settings {
-
-const char MetricsConsentHandler::kGetMetricsConsentState[] =
-    "getMetricsConsentState";
-const char MetricsConsentHandler::kUpdateMetricsConsent[] =
-    "updateMetricsConsent";
-
-MetricsConsentHandler::MetricsConsentHandler(
-    Profile* profile,
-    user_manager::UserManager* user_manager)
-    : profile_(profile), user_manager_(user_manager) {
-  DCHECK(profile_);
-  DCHECK(user_manager_);
-}
-
-MetricsConsentHandler::~MetricsConsentHandler() = default;
-
-void MetricsConsentHandler::RegisterMessages() {
-  web_ui()->RegisterMessageCallback(
-      kUpdateMetricsConsent,
-      base::BindRepeating(&MetricsConsentHandler::HandleUpdateMetricsConsent,
-                          weak_ptr_factory_.GetWeakPtr()));
-
-  web_ui()->RegisterMessageCallback(
-      kGetMetricsConsentState,
-      base::BindRepeating(&MetricsConsentHandler::HandleGetMetricsConsentState,
-                          weak_ptr_factory_.GetWeakPtr()));
-}
-
-void MetricsConsentHandler::OnJavascriptAllowed() {}
-
-void MetricsConsentHandler::OnJavascriptDisallowed() {}
-
-void MetricsConsentHandler::HandleGetMetricsConsentState(
-    base::Value::ConstListView args) {
-  AllowJavascript();
-  CHECK_EQ(1U, args.size());
-
-  const base::Value& callback_id = args[0];
-
-  base::Value response(base::Value::Type::DICTIONARY);
-  response.SetKey("prefName", base::Value(::ash::kStatsReportingPref));
-  response.SetKey("isConfigurable",
-                  base::Value(IsMetricsConsentConfigurable()));
-
-  ResolveJavascriptCallback(callback_id, response);
-}
-
-void MetricsConsentHandler::HandleUpdateMetricsConsent(
-    base::Value::ConstListView args) {
-  AllowJavascript();
-  CHECK_EQ(2U, args.size());
-
-  const base::Value& callback_id = args[0];
-  const bool metrics_consent = args[1].GetBool();
-
-  auto* stats_reporting_controller = ash::StatsReportingController::Get();
-
-  stats_reporting_controller->SetEnabled(profile_, metrics_consent);
-
-  // Re-read from |stats_reporting_controller|. If |profile_| is not owner, then
-  // the consent should not have changed to |metrics_consent|.
-  ResolveJavascriptCallback(
-      callback_id, base::Value(stats_reporting_controller->IsEnabled()));
-}
-
-bool MetricsConsentHandler::IsMetricsConsentConfigurable() const {
-  return user_manager_->IsCurrentUserOwner();
-}
-
-}  // namespace settings
-}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/settings/chromeos/metrics_consent_handler.h b/chrome/browser/ui/webui/settings/chromeos/metrics_consent_handler.h
deleted file mode 100644
index 8d4e41c..0000000
--- a/chrome/browser/ui/webui/settings/chromeos/metrics_consent_handler.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_METRICS_CONSENT_HANDLER_H_
-#define CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_METRICS_CONSENT_HANDLER_H_
-
-#include "base/callback_list.h"
-#include "base/memory/weak_ptr.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/webui/settings/settings_page_ui_handler.h"
-#include "components/user_manager/user_manager.h"
-
-namespace chromeos {
-namespace settings {
-
-class TestMetricsConsentHandler;
-
-// Handler for fetching and updating metrics consent.
-class MetricsConsentHandler : public ::settings::SettingsPageUIHandler {
- public:
-  // Message names sent to WebUI for handling metric consent.
-  static const char kGetMetricsConsentState[];
-  static const char kUpdateMetricsConsent[];
-
-  MetricsConsentHandler(Profile* profile,
-                        user_manager::UserManager* user_manager);
-
-  MetricsConsentHandler(const MetricsConsentHandler&) = delete;
-  MetricsConsentHandler& operator=(const MetricsConsentHandler&) = delete;
-
-  ~MetricsConsentHandler() override;
-
-  // SettingsPageUIHandler:
-  void RegisterMessages() override;
-  void OnJavascriptAllowed() override;
-  void OnJavascriptDisallowed() override;
-
- private:
-  friend class TestMetricsConsentHandler;
-
-  // Handles updating metrics consent for the user.
-  void HandleUpdateMetricsConsent(base::Value::ConstListView args);
-
-  // Handles fetching metrics consent state. The callback will return two
-  // values: a string pref name and a boolean indicating whether the current
-  // user may change that pref.
-  void HandleGetMetricsConsentState(base::Value::ConstListView args);
-
-  // Returns true if user with |profile_| has permissions to change the metrics
-  // consent pref.
-  bool IsMetricsConsentConfigurable() const;
-
-  Profile* const profile_;
-  user_manager::UserManager* const user_manager_;
-
-  // Used for callbacks.
-  base::WeakPtrFactory<MetricsConsentHandler> weak_ptr_factory_{this};
-};
-
-}  // namespace settings
-}  // namespace chromeos
-
-#endif  // CHROME_BROWSER_UI_WEBUI_SETTINGS_CHROMEOS_METRICS_CONSENT_HANDLER_H_
diff --git a/chrome/browser/ui/webui/settings/chromeos/metrics_consent_handler_unittest.cc b/chrome/browser/ui/webui/settings/chromeos/metrics_consent_handler_unittest.cc
deleted file mode 100644
index 8faeda6..0000000
--- a/chrome/browser/ui/webui/settings/chromeos/metrics_consent_handler_unittest.cc
+++ /dev/null
@@ -1,277 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/webui/settings/chromeos/metrics_consent_handler.h"
-
-#include "ash/components/settings/cros_settings_names.h"
-#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
-#include "chrome/browser/ash/ownership/owner_settings_service_ash.h"
-#include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
-#include "chrome/browser/ash/policy/core/device_policy_builder.h"
-#include "chrome/browser/ash/settings/cros_settings.h"
-#include "chrome/browser/ash/settings/device_settings_cache.h"
-#include "chrome/browser/ash/settings/device_settings_service.h"
-#include "chrome/browser/ash/settings/stats_reporting_controller.h"
-#include "chrome/browser/prefs/browser_prefs.h"
-#include "chrome/test/base/testing_profile.h"
-#include "chromeos/dbus/session_manager/fake_session_manager_client.h"
-#include "components/ownership/mock_owner_key_util.h"
-#include "components/pref_registry/pref_registry_syncable.h"
-#include "components/prefs/testing_pref_service.h"
-#include "components/sync_preferences/pref_service_mock_factory.h"
-#include "components/sync_preferences/pref_service_syncable.h"
-#include "content/public/browser/web_ui.h"
-#include "content/public/test/browser_task_environment.h"
-#include "content/public/test/test_utils.h"
-#include "content/public/test/test_web_ui.h"
-#include "testing/gmock/include/gmock/gmock.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace chromeos {
-namespace settings {
-
-namespace {
-
-using ::testing::Eq;
-
-TestingPrefServiceSimple* RegisterPrefs(TestingPrefServiceSimple* local_state) {
-  StatsReportingController::RegisterLocalStatePrefs(local_state->registry());
-  ash::device_settings_cache::RegisterPrefs(local_state->registry());
-  return local_state;
-}
-
-}  // namespace
-
-class TestMetricsConsentHandler : public MetricsConsentHandler {
- public:
-  TestMetricsConsentHandler(Profile* profile,
-                            user_manager::UserManager* user_manager,
-                            content::WebUI* web_ui)
-      : MetricsConsentHandler(profile, user_manager) {
-    set_web_ui(web_ui);
-  }
-  ~TestMetricsConsentHandler() override = default;
-
-  void GetMetricsConsentState() {
-    base::ListValue args;
-    args.Append(base::Value("callback-id"));
-    HandleGetMetricsConsentState(args.GetList());
-  }
-
-  void UpdateMetricsConsent(bool metrics_consent) {
-    base::ListValue args;
-    args.Append(base::Value("callback-id"));
-    args.Append(base::Value(metrics_consent));
-    HandleUpdateMetricsConsent(args.GetList());
-  }
-};
-
-class MetricsConsentHandlerTest : public testing::Test {
- public:
-  MetricsConsentHandlerTest() = default;
-  MetricsConsentHandlerTest(const MetricsConsentHandlerTest&) = delete;
-  MetricsConsentHandlerTest& operator=(const MetricsConsentHandlerTest&) =
-      delete;
-  ~MetricsConsentHandlerTest() override = default;
-
-  std::unique_ptr<TestingProfile> RegisterOwner(const AccountId& account_id) {
-    DeviceSettingsService::Get()->SetSessionManager(
-        &fake_session_manager_client_, owner_keys);
-    std::unique_ptr<TestingProfile> owner = CreateUser(owner_keys);
-    test_user_manager_->AddUserWithAffiliationAndTypeAndProfile(
-        account_id, false, user_manager::USER_TYPE_REGULAR, owner.get());
-    test_user_manager_->SetOwnerId(account_id);
-
-    EXPECT_THAT(ash::DeviceSettingsService::Get()->GetOwnershipStatus(),
-                Eq(ash::DeviceSettingsService::OWNERSHIP_TAKEN));
-
-    return owner;
-  }
-
-  void InitializeTestHandler(Profile* profile) {
-    // Create the handler with given profile.
-    handler_ = std::make_unique<TestMetricsConsentHandler>(
-        profile, test_user_manager_.get(), web_ui_.get());
-
-    // Enable javascript.
-    handler_->AllowJavascriptForTesting();
-  }
-
-  std::unique_ptr<TestingProfile> CreateUser(
-      scoped_refptr<ownership::MockOwnerKeyUtil> keys) {
-    OwnerSettingsServiceAshFactory::GetInstance()->SetOwnerKeyUtilForTesting(
-        keys);
-    std::unique_ptr<TestingProfile> user = std::make_unique<TestingProfile>();
-    OwnerSettingsServiceAshFactory::GetForBrowserContext(user.get())
-        ->OnTPMTokenReady();
-    content::RunAllTasksUntilIdle();
-    return user;
-  }
-
-  void LoginUser(const AccountId& account_id) {
-    test_user_manager_->LoginUser(account_id);
-    test_user_manager_->SwitchActiveUser(account_id);
-    test_user_manager_->SimulateUserProfileLoad(account_id);
-  }
-
- protected:
-  void SetUp() override {
-    // Load device policy with owner.
-    device_policy_.Build();
-    fake_session_manager_client_.set_device_policy(device_policy_.GetBlob());
-
-    // Keys to be used for testing.
-    non_owner_keys->SetPublicKeyFromPrivateKey(*device_policy_.GetSigningKey());
-    owner_keys->SetPublicKeyFromPrivateKey(*device_policy_.GetSigningKey());
-    owner_keys->SetPrivateKey(device_policy_.GetSigningKey());
-
-    content::RunAllTasksUntilIdle();
-
-    ash::StatsReportingController::Initialize(&pref_service_);
-
-    test_user_manager_ = std::make_unique<ash::FakeChromeUserManager>();
-    web_ui_ = std::make_unique<content::TestWebUI>();
-  }
-
-  void TearDown() override {
-    handler_->DisallowJavascript();
-    ash::StatsReportingController::Shutdown();
-  }
-
-  bool GetMetricsConsentStateMessage(std::string* pref_name,
-                                     bool* is_configurable) {
-    for (auto it = web_ui_->call_data().rbegin();
-         it != web_ui_->call_data().rend(); ++it) {
-      const content::TestWebUI::CallData* data = it->get();
-      const std::string* name = data->arg1()->GetIfString();
-
-      if (data->function_name() != "cr.webUIResponse" || !name ||
-          *name != "callback-id") {
-        continue;
-      }
-
-      if (!data->arg3() ||
-          data->arg3()->type() != base::Value::Type::DICTIONARY) {
-        return false;
-      }
-
-      const base::Value* metrics_consent_state = data->arg3();
-      *pref_name = metrics_consent_state->FindKey("prefName")->GetString();
-      *is_configurable =
-          metrics_consent_state->FindKey("isConfigurable")->GetBool();
-
-      return true;
-    }
-    return false;
-  }
-
-  bool UpdateMetricsConsentMessage(bool* current_consent) {
-    for (auto it = web_ui_->call_data().rbegin();
-         it != web_ui_->call_data().rend(); ++it) {
-      const content::TestWebUI::CallData* data = it->get();
-      const std::string* name = data->arg1()->GetIfString();
-
-      if (data->function_name() != "cr.webUIResponse" || !name ||
-          *name != "callback-id") {
-        continue;
-      }
-
-      if (!data->arg3() || data->arg3()->type() != base::Value::Type::BOOLEAN) {
-        return false;
-      }
-
-      *current_consent = data->arg3()->GetBool();
-      return true;
-    }
-    return false;
-  }
-
-  // Profiles must be created in browser threads.
-  content::BrowserTaskEnvironment task_environment_;
-  TestingPrefServiceSimple pref_service_;
-
-  std::unique_ptr<TestMetricsConsentHandler> handler_;
-  std::unique_ptr<ash::FakeChromeUserManager> test_user_manager_;
-  std::unique_ptr<content::TestWebUI> web_ui_;
-
-  // Set up stubs for StatsReportingController.
-  chromeos::ScopedStubInstallAttributes scoped_install_attributes_;
-  ash::FakeSessionManagerClient fake_session_manager_client_;
-  ash::ScopedTestDeviceSettingsService scoped_device_settings_;
-  ash::ScopedTestCrosSettings scoped_cros_settings_{
-      RegisterPrefs(&pref_service_)};
-  policy::DevicePolicyBuilder device_policy_;
-
-  scoped_refptr<ownership::MockOwnerKeyUtil> owner_keys{
-      base::MakeRefCounted<ownership::MockOwnerKeyUtil>()};
-  scoped_refptr<ownership::MockOwnerKeyUtil> non_owner_keys{
-      base::MakeRefCounted<ownership::MockOwnerKeyUtil>()};
-};
-
-TEST_F(MetricsConsentHandlerTest, OwnerCanToggle) {
-  auto owner_id = AccountId::FromUserEmailGaiaId("owner@example.com", "2");
-  std::unique_ptr<TestingProfile> owner = RegisterOwner(owner_id);
-
-  LoginUser(owner_id);
-  EXPECT_TRUE(test_user_manager_->IsCurrentUserOwner());
-
-  InitializeTestHandler(owner.get());
-  handler_->GetMetricsConsentState();
-
-  // Owner should be able to toggle the device stats reporting pref.
-  std::string pref_name;
-  bool is_configurable = false;
-
-  // Non-owner user should not be able to toggle the device stats reporting
-  // pref.
-  EXPECT_TRUE(GetMetricsConsentStateMessage(&pref_name, &is_configurable));
-  EXPECT_THAT(::ash::kStatsReportingPref, Eq(pref_name));
-  EXPECT_TRUE(is_configurable);
-
-  // Toggle true. Consent change should go through.
-  handler_->UpdateMetricsConsent(true);
-
-  bool current_consent = false;
-  EXPECT_TRUE(UpdateMetricsConsentMessage(&current_consent));
-
-  // Consent should change for owner.
-  EXPECT_TRUE(current_consent);
-}
-
-TEST_F(MetricsConsentHandlerTest, NonOwnerCannotToggle) {
-  auto owner_id = AccountId::FromUserEmailGaiaId("owner@example.com", "2");
-  std::unique_ptr<TestingProfile> owner = RegisterOwner(owner_id);
-
-  auto account_id = AccountId::FromUserEmailGaiaId("test@example.com", "1");
-  std::unique_ptr<TestingProfile> non_owner = CreateUser(non_owner_keys);
-  test_user_manager_->AddUserWithAffiliationAndTypeAndProfile(
-      account_id, false, user_manager::USER_TYPE_REGULAR, non_owner.get());
-
-  LoginUser(account_id);
-  EXPECT_FALSE(test_user_manager_->IsCurrentUserOwner());
-
-  InitializeTestHandler(non_owner.get());
-  handler_->GetMetricsConsentState();
-
-  std::string pref_name;
-  bool is_configurable = false;
-
-  // Non-owner user should not be able to toggle the device stats reporting
-  // pref.
-  EXPECT_TRUE(GetMetricsConsentStateMessage(&pref_name, &is_configurable));
-  EXPECT_THAT(::ash::kStatsReportingPref, Eq(pref_name));
-  EXPECT_FALSE(is_configurable);
-
-  // Toggle true.
-  handler_->UpdateMetricsConsent(true);
-
-  bool current_consent = false;
-  EXPECT_TRUE(UpdateMetricsConsentMessage(&current_consent));
-
-  // Consent should not change.
-  EXPECT_FALSE(current_consent);
-}
-
-}  // namespace settings
-}  // namespace chromeos
diff --git a/chrome/browser/ui/webui/settings/chromeos/privacy_section.cc b/chrome/browser/ui/webui/settings/chromeos/privacy_section.cc
index a1c2819..6abfc21b 100644
--- a/chrome/browser/ui/webui/settings/chromeos/privacy_section.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/privacy_section.cc
@@ -12,8 +12,6 @@
 #include "build/branding_buildflags.h"
 #include "build/chromeos_buildflags.h"
 #include "chrome/browser/ash/login/quick_unlock/quick_unlock_utils.h"
-#include "chrome/browser/ash/profiles/profile_helper.h"
-#include "chrome/browser/ui/webui/settings/chromeos/metrics_consent_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/os_settings_features_util.h"
 #include "chrome/browser/ui/webui/settings/chromeos/peripheral_data_access_handler.h"
 #include "chrome/browser/ui/webui/settings/chromeos/search/search_tag_registry.h"
@@ -228,10 +226,6 @@
   web_ui->AddMessageHandler(
       std::make_unique<chromeos::settings::PeripheralDataAccessHandler>());
 
-  web_ui->AddMessageHandler(
-      std::make_unique<chromeos::settings::MetricsConsentHandler>(
-          profile(), user_manager::UserManager::Get()));
-
   if (IsSecureDnsAvailable())
     web_ui->AddMessageHandler(std::make_unique<::settings::SecureDnsHandler>());
 }
diff --git a/chrome/browser/ui/webui/signin/sync_confirmation_ui.cc b/chrome/browser/ui/webui/signin/sync_confirmation_ui.cc
index 0251e598..f93d9d6 100644
--- a/chrome/browser/ui/webui/signin/sync_confirmation_ui.cc
+++ b/chrome/browser/ui/webui/signin/sync_confirmation_ui.cc
@@ -6,6 +6,8 @@
 
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "build/buildflag.h"
+#include "build/chromeos_buildflags.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
@@ -120,17 +122,30 @@
     DesignVersion design,
     bool is_modal_dialog) {
   AddStringResource(source, "syncConfirmationTitle",
-                    IDS_SYNC_CONFIRMATION_TITLE);
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+                    IDS_SYNC_CONFIRMATION_TITLE_LACROS
+#else
+                    IDS_SYNC_CONFIRMATION_TITLE
+#endif
+  );
   AddStringResource(source, "syncConfirmationSyncInfoTitle",
-                    IDS_SYNC_CONFIRMATION_SYNC_INFO_TITLE);
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+                    IDS_SYNC_CONFIRMATION_SYNC_INFO_TITLE_LACROS
+#else
+                    IDS_SYNC_CONFIRMATION_SYNC_INFO_TITLE
+#endif
+  );
+  AddStringResource(source, "syncConfirmationConfirmLabel",
+#if BUILDFLAG(IS_CHROMEOS_LACROS)
+                    IDS_DONE
+#else
+                    IDS_SYNC_CONFIRMATION_CONFIRM_BUTTON_LABEL
+#endif
+  );
   AddStringResource(source, "syncConfirmationSyncInfoDesc",
                     IDS_SYNC_CONFIRMATION_SYNC_INFO_DESC);
   AddStringResource(source, "syncConfirmationSettingsInfo",
                     IDS_SYNC_CONFIRMATION_SETTINGS_INFO);
-  AddStringResource(source, "syncConfirmationSettingsLabel",
-                    IDS_SYNC_CONFIRMATION_SETTINGS_BUTTON_LABEL);
-  AddStringResource(source, "syncConfirmationConfirmLabel",
-                    IDS_SYNC_CONFIRMATION_CONFIRM_BUTTON_LABEL);
 
   source->AddResourcePath(
       "sync_confirmation_app.js",
@@ -147,6 +162,8 @@
       source->AddString("accountPictureUrl",
                         profiles::GetPlaceholderAvatarIconUrl());
       AddStringResource(source, "syncConfirmationUndoLabel", IDS_CANCEL);
+      AddStringResource(source, "syncConfirmationSettingsLabel",
+                        IDS_SYNC_CONFIRMATION_SETTINGS_BUTTON_LABEL);
 
       source->AddResourcePath(
           "images/sync_confirmation_illustration.svg",
@@ -170,6 +187,8 @@
       source->AddString("accountPictureUrl", avatar_picture_url);
 
       AddStringResource(source, "syncConfirmationUndoLabel", IDS_NO_THANKS);
+      AddStringResource(source, "syncConfirmationSettingsLabel",
+                        IDS_SYNC_CONFIRMATION_REFRESHED_SETTINGS_BUTTON_LABEL);
       source->AddString("highlightColor", color_utils::SkColorToRgbaString(
                                               colors.profile_highlight_color));
 
diff --git a/chrome/browser/user_agent/user_agent_browsertest.cc b/chrome/browser/user_agent/user_agent_browsertest.cc
new file mode 100644
index 0000000..918ed7ae
--- /dev/null
+++ b/chrome/browser/user_agent/user_agent_browsertest.cc
@@ -0,0 +1,88 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chrome_content_browser_client.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/scoped_testing_local_state.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/embedder_support/user_agent_utils.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/test/browser_test.h"
+#include "net/http/http_request_headers.h"
+#include "third_party/blink/public/common/features.h"
+
+namespace policy {
+
+using EnterprisePolicyState =
+    ChromeContentBrowserClient::UserAgentReductionEnterprisePolicyState;
+
+class UserAgentBrowserTest : public InProcessBrowserTest,
+                             public testing::WithParamInterface<bool> {
+ public:
+  UserAgentBrowserTest() {
+    embedded_test_server()->RegisterRequestMonitor(base::BindRepeating(
+        &UserAgentBrowserTest::MonitorUserAgent, base::Unretained(this)));
+    if (GetParam()) {
+      scoped_feature_list_.InitAndEnableFeature(
+          blink::features::kReduceUserAgent);
+    }
+  }
+
+  void ExpectUserAgent(std::string expected_user_agent) {
+    EXPECT_EQ(expected_user_agent, observered_user_agent_);
+  }
+
+  void set_user_agent_reduction_policy(int policy) {
+    browser()->profile()->GetPrefs()->SetInteger(prefs::kUserAgentReduction,
+                                                 policy);
+  }
+
+  int user_agent_reduction_policy() {
+    return browser()->profile()->GetPrefs()->GetInteger(
+        prefs::kUserAgentReduction);
+  }
+
+ private:
+  void MonitorUserAgent(const net::test_server::HttpRequest& request) {
+    observered_user_agent_ =
+        request.headers.find(net::HttpRequestHeaders::kUserAgent)->second;
+  }
+
+  std::string observered_user_agent_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_P(UserAgentBrowserTest, EnterprisePolicyState) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL url = embedded_test_server()->GetURL("/empty.html");
+
+  // Check that default is set correctly
+  EXPECT_EQ(EnterprisePolicyState::kDefault, user_agent_reduction_policy());
+
+  set_user_agent_reduction_policy(EnterprisePolicyState::kForceDisabled);
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+  ExpectUserAgent(embedder_support::GetFullUserAgent());
+
+  set_user_agent_reduction_policy(EnterprisePolicyState::kForceEnabled);
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+  ExpectUserAgent(embedder_support::GetReducedUserAgent());
+
+  set_user_agent_reduction_policy(EnterprisePolicyState::kDefault);
+  ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
+  ExpectUserAgent(embedder_support::GetUserAgent());
+}
+
+INSTANTIATE_TEST_SUITE_P(ReduceUserAgentFeature,
+                         UserAgentBrowserTest,
+                         ::testing::Bool());
+
+}  // namespace policy
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index 29e72a7..33c7710 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1637149934-4ec5a6f789a83ea724d2bda8a97e9efd4a0b15c6.profdata
+chrome-linux-main-1637171708-555a54e81cb15ab58d705bc3a46722a3241eaca0.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 160fcfae..520181aa 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1637149934-130828ad108b811b1d0077e4db70c0c96f5a1e4d.profdata
+chrome-mac-main-1637171708-f185520a910fbb88bb9d719820d80ff84a921076.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 98d7fa6..290806a 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1637161043-976d2c6ea09f2f56f78158b992ac46514f48e9f1.profdata
+chrome-win32-main-1637171708-17003bcd4a05091296b27b58a7ceb85a6be4fefa.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 6a9ce51..5741148 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1637161043-c18ea641740adb1c065e0cff8307521911b42b41.profdata
+chrome-win64-main-1637171708-f202ade7a961975de9a8741bdd840dd770f9cdd2.profdata
diff --git a/chrome/chrome_repack_locales.gni b/chrome/chrome_repack_locales.gni
index 3425f1fef..7e67226 100644
--- a/chrome/chrome_repack_locales.gni
+++ b/chrome/chrome_repack_locales.gni
@@ -35,6 +35,7 @@
       "${root_gen_dir}/services/strings/services_strings_",
       "${root_gen_dir}/third_party/libaddressinput/address_input_strings_",
       "${root_gen_dir}/ui/strings/app_locale_settings_",
+      "${root_gen_dir}/ui/strings/ax_strings_",
       "${root_gen_dir}/ui/strings/ui_strings_",
     ]
     if (!defined(deps)) {
@@ -52,6 +53,7 @@
       "//third_party/blink/public/strings",
       "//third_party/libaddressinput:strings",
       "//ui/strings:app_locale_settings",
+      "//ui/strings:ax_strings",
       "//ui/strings:ui_strings",
     ]
     if (defined(invoker.deps)) {
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 6469784..c861034 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -1648,6 +1648,10 @@
 const char kSuppressDifferentOriginSubframeJSDialogs[] =
     "suppress_different_origin_subframe_js_dialogs";
 
+// Enum indicating if the user agent reduction feature should be forced enabled
+// or disabled. Defaults to blink::features::kReduceUserAgent field trial.
+const char kUserAgentReduction[] = "user_agent_reduction";
+
 // *************** LOCAL STATE ***************
 // These are attached to the machine/installation
 
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index f4344370..9f9df29 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -1185,6 +1185,8 @@
 
 extern const char kSuppressDifferentOriginSubframeJSDialogs[];
 
+extern const char kUserAgentReduction[];
+
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 extern const char kPdfAnnotationsEnabled[];
 #endif
diff --git a/chrome/services/sharing/DIR_METADATA b/chrome/services/sharing/DIR_METADATA
index 135a13c..3d76e46 100644
--- a/chrome/services/sharing/DIR_METADATA
+++ b/chrome/services/sharing/DIR_METADATA
@@ -1,3 +1,3 @@
 monorail: {
-  component: "UI>Browser>Sharing>Nearby"
+  component: "OS>Systems>Multidevice>Nearby"
 }
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 1cefea0..c97d31b 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -2058,6 +2058,7 @@
       "../browser/ukm_worker_browsertest.cc",
       "../browser/unload_browsertest.cc",
       "../browser/usb/usb_browsertest.cc",
+      "../browser/user_agent/user_agent_browsertest.cc",
       "../browser/wake_lock/wake_lock_browsertest.cc",
       "../browser/webauthn/chrome_webauthn_browsertest.cc",
       "../browser/window_placement/window_placement_browsertest.cc",
@@ -5122,6 +5123,7 @@
       "../browser/ui/sync/sync_promo_ui_unittest.cc",
       "../browser/ui/toolbar/app_menu_icon_controller_unittest.cc",
       "../browser/ui/views/file_system_access/file_system_access_ui_helpers_unittest.cc",
+      "../browser/ui/views/location_bar/permission_chip_unittest.cc",
       "../browser/ui/webui/constrained_web_dialog_ui_unittest.cc",
       "../browser/ui/webui/devtools_ui_data_source_unittest.cc",
       "../browser/ui/webui/discards/graph_dump_impl_unittest.cc",
diff --git a/chrome/test/base/chrome_unit_test_suite.cc b/chrome/test/base/chrome_unit_test_suite.cc
index 7aaff47..9fe7a54 100644
--- a/chrome/test/base/chrome_unit_test_suite.cc
+++ b/chrome/test/base/chrome_unit_test_suite.cc
@@ -43,6 +43,7 @@
 #if BUILDFLAG(IS_CHROMEOS_ASH)
 #include "ash/constants/ash_paths.h"
 #include "chrome/browser/ash/arc/arc_util.h"
+#include "crypto/nss_util_internal.h"
 #endif
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
@@ -114,6 +115,7 @@
            "AXPlatformNode::ResetAxModeForTesting() at the end of your test.";
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     arc::ClearArcAllowedCheckForTesting();
+    crypto::ResetTokenManagerForTesting();
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
   }
 
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 2c650cec..4314d93 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -17350,5 +17350,53 @@
         }
       }
     ]
+  },
+  "UserAgentReduction": {
+    "os": [
+      "android",
+      "chromeos_ash",
+      "chromeos_lacros",
+      "linux",
+      "mac",
+      "win"
+    ],
+    "policy_pref_mapping_tests": [
+      {
+        "policies": {},
+        "prefs": {
+          "user_agent_reduction": {
+            "location": "user_profile",
+            "default_value": 0
+          }
+        }
+      },
+      {
+        "policies": {"UserAgentReduction": 0},
+        "prefs": {
+          "user_agent_reduction": {
+            "location": "user_profile",
+            "value": 0
+          }
+        }
+      },
+      {
+        "policies": {"UserAgentReduction": 1},
+        "prefs": {
+          "user_agent_reduction": {
+            "location": "user_profile",
+            "value": 1
+          }
+        }
+      },
+      {
+        "policies": {"UserAgentReduction": 2},
+        "prefs": {
+          "user_agent_reduction": {
+            "location": "user_profile",
+            "value": 2
+          }
+        }
+      }
+    ]
   }
 }
diff --git a/chrome/test/data/webui/chromeos/personalization_app/google_photos_element_test.js b/chrome/test/data/webui/chromeos/personalization_app/google_photos_element_test.js
index 29e2819b..d2206d2 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/google_photos_element_test.js
+++ b/chrome/test/data/webui/chromeos/personalization_app/google_photos_element_test.js
@@ -31,6 +31,8 @@
       'googlePhotosLabel': 'Google Photos',
       'googlePhotosAlbumsTabLabel': 'Albums',
       'googlePhotosPhotosTabLabel': 'Photos',
+      'googlePhotosZeroStateMessage':
+          'No image available. To add photos, go to $1',
     });
 
     const mocks = baseSetup();
@@ -45,11 +47,16 @@
   test('displays only photos content', async () => {
     // Tabs and albums content are not displayed if albums are absent.
     personalizationStore.data.googlePhotos.albums = null;
+    personalizationStore.data.googlePhotos.photos = Array.from({length: 1});
     personalizationStore.data.loading.googlePhotos.albums = false;
+    personalizationStore.data.loading.googlePhotos.photos = false;
 
     googlePhotosElement = initElement(GooglePhotos.is, {hidden: false});
     await waitAfterNextRender(googlePhotosElement);
 
+    // Zero state should be absent.
+    assertEquals(querySelector('#zeroState'), null);
+
     // Tabs should be absent.
     assertEquals(querySelector('.tabStrip'), null);
 
@@ -65,11 +72,16 @@
   test('displays tabs and content for photos and albums', async () => {
     // Tabs and albums content are only displayed if albums are present.
     personalizationStore.data.googlePhotos.albums = Array.from({length: 1});
+    personalizationStore.data.googlePhotos.photos = Array.from({length: 1});
     personalizationStore.data.loading.googlePhotos.albums = false;
+    personalizationStore.data.loading.googlePhotos.photos = false;
 
     googlePhotosElement = initElement(GooglePhotos.is, {hidden: false});
     await waitAfterNextRender(googlePhotosElement);
 
+    // Zero state should be absent.
+    assertEquals(querySelector('#zeroState'), null);
+
     // Photos tab should be present, visible, and pressed.
     const photosTab = querySelector('#photosTab');
     assertTrue(!!photosTab);
@@ -118,4 +130,39 @@
     assertEquals(albumsTab.getAttribute('aria-pressed'), 'false');
     assertTrue(albumsContent.hidden);
   });
+
+  test('displays zero state when there is no content', async () => {
+    personalizationStore.data.googlePhotos.albums = [];
+    personalizationStore.data.googlePhotos.photos = [];
+    personalizationStore.data.loading.googlePhotos.albums = false;
+    personalizationStore.data.loading.googlePhotos.photos = false;
+
+    googlePhotosElement = initElement(GooglePhotos.is, {hidden: false});
+    await waitAfterNextRender(googlePhotosElement);
+
+    // Photos tab should be absent.
+    assertEquals(querySelector('#photosTab'), null);
+
+    // Photos content should be absent.
+    assertEquals(querySelector('#photosContent'), null);
+
+    // Albums tab should be absent.
+    assertEquals(querySelector('#albumsTab'), null);
+
+    // Albums content should be absent.
+    assertEquals(querySelector('#albumsContent'), null);
+
+    // Zero state should be present and visible.
+    const zeroState = querySelector('#zeroState');
+    assertTrue(!!zeroState);
+    assertFalse(zeroState.hidden);
+
+    const message =
+        googlePhotosElement.i18nAdvanced('googlePhotosZeroStateMessage', {
+          substitutions: [
+            '<a target="_blank" href="https://photos.google.com">photos.google.com</a>'
+          ]
+        });
+    assertEquals(querySelector('#zeroStateText').innerHTML, message);
+  });
 }
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/onboarding_network_page_test.js b/chrome/test/data/webui/chromeos/shimless_rma/onboarding_network_page_test.js
index 1a7fd24..5b0fed6c 100644
--- a/chrome/test/data/webui/chromeos/shimless_rma/onboarding_network_page_test.js
+++ b/chrome/test/data/webui/chromeos/shimless_rma/onboarding_network_page_test.js
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {OncMojo} from 'chrome://resources/cr_components/chromeos/network/onc_mojo.m.js';
 import {fakeNetworks} from 'chrome://shimless-rma/fake_data.js';
 import {FakeShimlessRmaService} from 'chrome://shimless-rma/fake_shimless_rma_service.js';
 import {setNetworkConfigServiceForTesting, setShimlessRmaServiceForTesting} from 'chrome://shimless-rma/mojo_interface_provider.js';
@@ -140,4 +141,29 @@
 
     assertFalse(dialog.open);
   });
+
+  test('SetSkipButtonWhenNotConnected', async () => {
+    networkConfigService.addNetworksForTest(fakeNetworks);
+
+    component = /** @type {!OnboardingNetworkPage} */ (
+        document.createElement('onboarding-network-page'));
+    let buttonLabelKey;
+    component.addEventListener('set-next-button-label', (e) => {
+      buttonLabelKey = e.detail;
+    });
+
+    document.body.appendChild(component);
+    await flushTasks();
+    assertEquals('skipButtonLabel', buttonLabelKey);
+
+    const ethernetConnected = OncMojo.getDefaultNetworkState(
+        chromeos.networkConfig.mojom.NetworkType.kEthernet, 'ethernet');
+    ethernetConnected.connectionState =
+        chromeos.networkConfig.mojom.ConnectionStateType.kOnline;
+    networkConfigService.addNetworksForTest([ethernetConnected]);
+
+    component.refreshNetworks();
+    await flushTasks();
+    assertEquals('nextButtonLabel', buttonLabelKey);
+  });
 }
diff --git a/chrome/test/data/webui/nearby_share/DIR_METADATA b/chrome/test/data/webui/nearby_share/DIR_METADATA
index 7ed7d6b..76bc6b2f 100644
--- a/chrome/test/data/webui/nearby_share/DIR_METADATA
+++ b/chrome/test/data/webui/nearby_share/DIR_METADATA
@@ -1,3 +1,3 @@
 monorail {
-  component: "UI>Browser>Sharing>Nearby"
+  component: "OS>Systems>Multidevice>Nearby"
 }
diff --git a/chrome/test/data/webui/read_later/side_panel/bookmark_folder_test.js b/chrome/test/data/webui/read_later/side_panel/bookmark_folder_test.js
index 628cf32..ccb0e1359 100644
--- a/chrome/test/data/webui/read_later/side_panel/bookmark_folder_test.js
+++ b/chrome/test/data/webui/read_later/side_panel/bookmark_folder_test.js
@@ -10,7 +10,7 @@
 import {BookmarksApiProxy} from 'chrome://read-later.top-chrome/side_panel/bookmarks_api_proxy.js';
 import {getFaviconForPageURL} from 'chrome://resources/js/icon.js';
 
-import {assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
+import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
 import {eventToPromise, flushTasks, waitAfterNextRender} from '../../test_util.js';
 
 import {TestBookmarksApiProxy} from './test_bookmarks_api_proxy.js';
@@ -30,7 +30,13 @@
       {
         id: '1',
         title: 'Shopping list',
-        children: [],
+        children: [
+          {
+            id: '4',
+            title: 'New shoes',
+            url: 'http://shoes/',
+          },
+        ],
       },
       {
         id: '2',
@@ -295,4 +301,36 @@
         modifiedClick.altKey && modifiedClick.ctrlKey &&
         modifiedClick.metaKey && modifiedClick.shiftKey);
   });
+
+  test('GetsFocusableElements', async () => {
+    let focusableElement = bookmarkFolder.getFocusableElement([folder]);
+    assertEquals('folder', focusableElement.id);
+
+    const childBookmark = folder.children[1];
+    focusableElement = bookmarkFolder.getFocusableElement([childBookmark]);
+    assertTrue(focusableElement.classList.contains('bookmark'));
+    assertEquals(childBookmark, focusableElement.dataBookmark);
+
+    const childFolder = folder.children[0];
+    focusableElement = bookmarkFolder.getFocusableElement([childFolder]);
+    assertEquals('folder', focusableElement.id);
+    assertEquals(childFolder.id, focusableElement.dataBookmark.id);
+
+    // Grandchild bookmark is in a closed folder, so the focusable element
+    // should still be the child folder.
+    const grandchildBookmark = childFolder.children[0];
+    focusableElement =
+        bookmarkFolder.getFocusableElement([childFolder, grandchildBookmark]);
+    assertEquals('folder', focusableElement.id);
+    assertEquals(childFolder.id, focusableElement.dataBookmark.id);
+
+    // Once the child folder is opened, the grandchild bookmark element should
+    // be focusable.
+    bookmarkFolder.openFolders = ['0', '1'];
+    await waitAfterNextRender();
+    focusableElement =
+        bookmarkFolder.getFocusableElement([childFolder, grandchildBookmark]);
+    assertTrue(focusableElement.classList.contains('bookmark'));
+    assertEquals(grandchildBookmark, focusableElement.dataBookmark);
+  });
 });
diff --git a/chrome/test/data/webui/settings/BUILD.gn b/chrome/test/data/webui/settings/BUILD.gn
index 577571b..88371827 100644
--- a/chrome/test/data/webui/settings/BUILD.gn
+++ b/chrome/test/data/webui/settings/BUILD.gn
@@ -102,8 +102,8 @@
   "settings_ui_tests.js",
   "site_data_details_subpage_tests.js",
   "site_data_test.js",
-  "site_details_permission_tests.js",
-  "site_details_tests.js",
+  "site_details_permission_tests.ts",
+  "site_details_tests.ts",
   "site_entry_tests.js",
   "site_favicon_test.js",
   "site_list_tests.ts",
diff --git a/chrome/test/data/webui/settings/chromeos/os_privacy_page_test.js b/chrome/test/data/webui/settings/chromeos/os_privacy_page_test.js
index 5cc9352..15144f487 100644
--- a/chrome/test/data/webui/settings/chromeos/os_privacy_page_test.js
+++ b/chrome/test/data/webui/settings/chromeos/os_privacy_page_test.js
@@ -11,7 +11,7 @@
 // #import {assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
 // #import {assert} from 'chrome://resources/js/assert.m.js';
 // #import {getDeepActiveElement} from 'chrome://resources/js/util.m.js';
-// #import {SecureDnsMode, SecureDnsUiManagementMode, Router, routes, PeripheralDataAccessBrowserProxyImpl, DataAccessPolicyState, MetricsConsentBrowserProxyImpl, MetricsConsentState} from 'chrome://os-settings/chromeos/os_settings.js';
+// #import {SecureDnsMode, SecureDnsUiManagementMode, Router, routes, PeripheralDataAccessBrowserProxyImpl, DataAccessPolicyState} from 'chrome://os-settings/chromeos/os_settings.js';
 // #import {FakeQuickUnlockPrivate} from './fake_quick_unlock_private.m.js';
 // #import {waitAfterNextRender} from 'chrome://test/test_util.js';
 // clang-format on
@@ -19,7 +19,6 @@
 const crosSettingPrefName = 'cros.device.peripheral_data_access_enabled';
 const localStatePrefName =
     'settings.local_state_device_pci_data_access_enabled';
-const deviceMetricsConsentPrefName = 'cros.metrics.reportingEnabled';
 
 /**
  * @implements {settings.PeripheralDataAccessBrowserProxy}
@@ -60,45 +59,6 @@
   }
 }
 
-/**
- * @implements {settings.MetricsConsentBrowserProxy}
- */
-class TestMetricsConsentBrowserProxy extends TestBrowserProxy {
-  constructor() {
-    super([
-      'getMetricsConsentState',
-      'updateMetricsConsent',
-    ]);
-
-    /** @type {MetricsConsentState} */
-    this.state_ = {
-      prefName: deviceMetricsConsentPrefName,
-      isConfigurable: false
-    };
-  }
-
-  /** @override */
-  getMetricsConsentState() {
-    this.methodCalled('getMetricsConsentState');
-    return Promise.resolve(this.state_);
-  }
-
-  /** @override */
-  updateMetricsConsent(consent) {
-    this.methodCalled('updateMetricsConsent');
-    return Promise.resolve(consent);
-  }
-
-  /**
-   * @param {String} prefName
-   * @param {Boolean} isConfigurable
-   */
-  setMetricsConsentState(prefName, isConfigurable) {
-    this.state_.prefName = prefName;
-    this.state_.isConfigurable = isConfigurable;
-  }
-}
-
 suite('PrivacyPageTests', function() {
   /** @type {SettingsPrivacyPageElement} */
   let privacyPage = null;
@@ -110,7 +70,7 @@
           value: true,
         }
       }
-    }
+    },
   };
 
   /** @type {?TestPeripheralDataAccessBrowserProxy} */
@@ -335,49 +295,27 @@
         'peripheral_data_access_enabled': {
           value: true,
         }
-      },
-      'metrics': {
-        'reportingEnabled': {
-          value: true,
-        }
       }
     },
-  };
+   };
 
   /** @type {?TestPeripheralDataAccessBrowserProxy} */
   let browserProxy = null;
 
-  /** @type {?TestMetricsConsentBrowserProxy} */
-  let metricsConsentBrowserProxy = null;
-
   setup(async () => {
-    privacyPage = document.createElement('os-settings-privacy-page');
     browserProxy = new TestPeripheralDataAccessBrowserProxy();
-    PolymerTest.clearBody();
-
     settings.PeripheralDataAccessBrowserProxyImpl.instance_ = browserProxy;
-
-    metricsConsentBrowserProxy = new TestMetricsConsentBrowserProxy();
-    settings.MetricsConsentBrowserProxyImpl.instance_ =
-        metricsConsentBrowserProxy;
-
     loadTimeData.overrideValues({
       pciguardUiEnabled: false,
     });
-  });
 
-  async function setUpPage(prefName, isConfigurable) {
-    metricsConsentBrowserProxy.setMetricsConsentState(prefName, isConfigurable);
-
+    PolymerTest.clearBody();
     privacyPage = document.createElement('os-settings-privacy-page');
-    privacyPage.prefs = Object.assign({}, prefs_);
     document.body.appendChild(privacyPage);
     Polymer.dom.flush();
 
-    await metricsConsentBrowserProxy.whenCalled('getMetricsConsentState');
-    await test_util.waitAfterNextRender();
-    Polymer.dom.flush();
-  }
+    await browserProxy.whenCalled('isThunderboltSupported');
+  });
 
   teardown(function() {
     privacyPage.remove();
@@ -385,8 +323,6 @@
   });
 
   test('Deep link to send usage stats', async () => {
-    await setUpPage(deviceMetricsConsentPrefName, /*isConfigurable=*/ true);
-
     const params = new URLSearchParams;
     params.append('settingId', '1103');
     settings.Router.getInstance().navigateTo(
@@ -401,41 +337,6 @@
         deepLinkElement, getDeepActiveElement(),
         'Send usage stats toggle should be focused for settingId=1103.');
   });
-
-  test('Toggle disabled if metrics consent is not configurable', async () => {
-    await setUpPage(deviceMetricsConsentPrefName, /*isConfigurable=*/ false);
-
-    const toggle =
-        privacyPage.$$('#enable-logging').shadowRoot.querySelector('cr-toggle');
-    await test_util.waitAfterNextRender(toggle);
-
-    // The pref is true, so the toggle should be on.
-    assertTrue(toggle.checked);
-
-    // Not configurable, so toggle should be disabled.
-    assertTrue(toggle.disabled);
-  });
-
-  test('Toggle enabled if metrics consent is configurable', async () => {
-    await setUpPage(deviceMetricsConsentPrefName, /*is_configurable=*/ true);
-
-    const toggle =
-        privacyPage.$$('#enable-logging').shadowRoot.querySelector('cr-toggle');
-    await test_util.waitAfterNextRender(toggle);
-
-    // The pref is true, so the toggle should be on.
-    assertTrue(toggle.checked);
-
-    // Configurable, so toggle should be enabled.
-    assertFalse(toggle.disabled);
-
-    // Toggle.
-    toggle.click();
-    await metricsConsentBrowserProxy.whenCalled('updateMetricsConsent');
-
-    // Pref should be off now.
-    assertFalse(toggle.checked);
-  });
 });
 
 suite('PeripheralDataAccessTest', function() {
@@ -452,9 +353,15 @@
       }
     },
     'settings': {'local_state_device_pci_data_access_enabled': {value: false}},
-    'dns_over_https':
-        {'mode': {value: SecureDnsMode.AUTOMATIC}, 'templates': {value: ''}},
-  };
+    'dns_over_https': {
+      'mode': {
+        value: SecureDnsMode.AUTOMATIC
+      },
+      'templates': {
+        value: ''
+      }
+     },
+   };
 
   /** @type {?TestPeripheralDataAccessBrowserProxy} */
   let browserProxy = null;
diff --git a/chrome/test/data/webui/settings/site_details_permission_tests.js b/chrome/test/data/webui/settings/site_details_permission_tests.ts
similarity index 77%
rename from chrome/test/data/webui/settings/site_details_permission_tests.js
rename to chrome/test/data/webui/settings/site_details_permission_tests.ts
index e5ceca3..cbc6c8f 100644
--- a/chrome/test/data/webui/settings/site_details_permission_tests.js
+++ b/chrome/test/data/webui/settings/site_details_permission_tests.ts
@@ -3,31 +3,29 @@
 // found in the LICENSE file.
 
 // clang-format off
-import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {ContentSetting,ContentSettingsTypes,SiteSettingSource,SiteSettingsPrefsBrowserProxyImpl} from 'chrome://settings/lazy_load.js';
+import {ContentSetting, ContentSettingsTypes, SiteDetailsPermissionElement, SiteSettingSource, SiteSettingsPrefsBrowserProxyImpl} from 'chrome://settings/lazy_load.js';
+import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
+
 import {TestSiteSettingsPrefsBrowserProxy} from './test_site_settings_prefs_browser_proxy.js';
-import {createContentSettingTypeToValuePair,createDefaultContentSetting,createRawSiteException,createSiteSettingsPrefs} from './test_util.js';
+import {createContentSettingTypeToValuePair, createDefaultContentSetting, createRawSiteException, createSiteSettingsPrefs, SiteSettingsPref} from './test_util.js';
 // clang-format on
 
 /** @fileoverview Suite of tests for site-details. */
 suite('SiteDetailsPermission', function() {
   /**
    * A site list element created before each test.
-   * @type {SiteDetailsPermission}
    */
-  let testElement;
+  let testElement: SiteDetailsPermissionElement;
 
   /**
    * The mock proxy object to use during test.
-   * @type {TestSiteSettingsPrefsBrowserProxy}
    */
-  let browserProxy;
+  let browserProxy: TestSiteSettingsPrefsBrowserProxy;
 
   /**
    * An example pref with only camera allowed.
-   * @type {SiteSettingsPref}
    */
-  let prefs;
+  let prefs: SiteSettingsPref;
 
   // Initialize a site-details-permission before each test.
   setup(function() {
@@ -42,12 +40,13 @@
 
     browserProxy = new TestSiteSettingsPrefsBrowserProxy();
     SiteSettingsPrefsBrowserProxyImpl.setInstance(browserProxy);
-    PolymerTest.clearBody();
+    document.body.innerHTML = '';
     testElement = document.createElement('site-details-permission');
     document.body.appendChild(testElement);
   });
 
-  function validatePermissionFlipWorks(origin, expectedContentSetting) {
+  function validatePermissionFlipWorks(
+      origin: string, expectedContentSetting: ContentSetting) {
     browserProxy.resetResolver('setOriginPermissions');
 
     // Simulate permission change initiated by the user.
@@ -66,17 +65,18 @@
     browserProxy.setPrefs(prefs);
     testElement.category = ContentSettingsTypes.CAMERA;
     testElement.label = 'Camera';
-    testElement.site = {
+    testElement.site = createRawSiteException(origin, {
       origin: origin,
       embeddingOrigin: '',
       source: SiteSettingSource.PREFERENCE,
-    };
+    });
 
     assertFalse(testElement.$.details.hidden);
 
-    const header = testElement.$.details.querySelector('#permissionHeader');
+    const header =
+        testElement.$.details.querySelector<HTMLElement>('#permissionHeader')!;
     assertEquals(
-        'Camera', header.innerText.trim(),
+        'Camera', header.innerText!.trim(),
         'Widget should be labelled correctly');
 
     // Flip the permission and validate that prefs stay in sync.
@@ -97,12 +97,12 @@
     browserProxy.setPrefs(prefs);
     testElement.category = ContentSettingsTypes.CAMERA;
     testElement.label = 'Camera';
-    testElement.site = {
+    testElement.site = createRawSiteException(origin, {
       origin: origin,
       embeddingOrigin: '',
       setting: ContentSetting.ALLOW,
       source: SiteSettingSource.PREFERENCE,
-    };
+    });
 
     return browserProxy.whenCalled('getDefaultValueForContentType')
         .then((args) => {
@@ -111,7 +111,7 @@
 
           // The default option will always be the first in the menu.
           assertEquals(
-              'Allow (default)', testElement.$.permission.options[0].text,
+              'Allow (default)', testElement.$.permission.options[0]!.text,
               'Default setting string should match prefs');
           browserProxy.resetResolver('getDefaultValueForContentType');
           const defaultPrefs = createSiteSettingsPrefs(
@@ -126,7 +126,7 @@
         .then((args) => {
           assertEquals(ContentSettingsTypes.CAMERA, args);
           assertEquals(
-              'Block (default)', testElement.$.permission.options[0].text,
+              'Block (default)', testElement.$.permission.options[0]!.text,
               'Default setting string should match prefs');
           browserProxy.resetResolver('getDefaultValueForContentType');
           const defaultPrefs = createSiteSettingsPrefs(
@@ -139,7 +139,7 @@
         .then((args) => {
           assertEquals(ContentSettingsTypes.CAMERA, args);
           assertEquals(
-              'Ask (default)', testElement.$.permission.options[0].text,
+              'Ask (default)', testElement.$.permission.options[0]!.text,
               'Default setting string should match prefs');
         });
   });
@@ -150,36 +150,36 @@
 
     // Strings that should be shown for the permission sources that don't depend
     // on the ContentSetting value.
-    const permissionSourcesNoSetting = {};
-    permissionSourcesNoSetting[SiteSettingSource.DEFAULT] = '';
-    permissionSourcesNoSetting[SiteSettingSource.PREFERENCE] = '';
-    permissionSourcesNoSetting[SiteSettingSource.EMBARGO] =
-        'Automatically blocked';
-    permissionSourcesNoSetting[SiteSettingSource.INSECURE_ORIGIN] =
-        'Blocked to protect your privacy';
-    permissionSourcesNoSetting[SiteSettingSource.KILL_SWITCH] =
-        'Temporarily blocked to protect your security';
+    const permissionSourcesNoSetting: Map<SiteSettingSource, string> = new Map([
+      [SiteSettingSource.DEFAULT, ''],
+      [SiteSettingSource.EMBARGO, 'Automatically blocked'],
+      [SiteSettingSource.INSECURE_ORIGIN, 'Blocked to protect your privacy'],
+      [
+        SiteSettingSource.KILL_SWITCH,
+        'Temporarily blocked to protect your security'
+      ],
+      [SiteSettingSource.PREFERENCE, ''],
+    ]);
 
-    for (const testSource in permissionSourcesNoSetting) {
-      testElement.site = {
+    for (const [source, str] of permissionSourcesNoSetting) {
+      testElement.site = createRawSiteException(origin, {
         origin: origin,
         embeddingOrigin: origin,
         setting: ContentSetting.BLOCK,
-        source: testSource,
-      };
+        source,
+      });
       assertEquals(
-          permissionSourcesNoSetting[testSource] +
-              (permissionSourcesNoSetting[testSource].length === 0 ?
-                   'Block (default)\nAllow\nBlock\nAsk' :
-                   '\nBlock (default)\nAllow\nBlock\nAsk'),
+          str +
+              (str.length === 0 ? 'Block (default)\nAllow\nBlock\nAsk' :
+                                  '\nBlock (default)\nAllow\nBlock\nAsk'),
           testElement.$.permissionItem.innerText.trim());
       assertEquals(
-          permissionSourcesNoSetting[testSource] !== '',
+          str !== '',
           testElement.$.permissionItem.classList.contains('two-line'));
 
-      if (testSource !== SiteSettingSource.DEFAULT &&
-          testSource !== SiteSettingSource.PREFERENCE &&
-          testSource !== SiteSettingSource.EMBARGO) {
+      if (source !== SiteSettingSource.DEFAULT &&
+          source !== SiteSettingSource.PREFERENCE &&
+          source !== SiteSettingSource.EMBARGO) {
         assertTrue(testElement.$.permission.disabled);
       } else {
         assertFalse(testElement.$.permission.disabled);
@@ -187,59 +187,57 @@
     }
 
     // Permissions that have been set by extensions.
-    const extensionSourceStrings = {};
-    extensionSourceStrings[ContentSetting.ALLOW] = 'Allowed by an extension';
-    extensionSourceStrings[ContentSetting.BLOCK] = 'Blocked by an extension';
-    extensionSourceStrings[ContentSetting.ASK] =
-        'Setting controlled by an extension';
+    const extensionSourceStrings: Map<ContentSetting, string> = new Map([
+      [ContentSetting.ALLOW, 'Allowed by an extension'],
+      [ContentSetting.BLOCK, 'Blocked by an extension'],
+      [ContentSetting.ASK, 'Setting controlled by an extension'],
+    ]);
 
-    for (const testSetting in extensionSourceStrings) {
-      testElement.site = {
+    for (const [setting, str] of extensionSourceStrings) {
+      testElement.site = createRawSiteException(origin, {
         origin: origin,
         embeddingOrigin: origin,
-        setting: testSetting,
+        setting,
         source: SiteSettingSource.EXTENSION,
-      };
+      });
       assertEquals(
-          extensionSourceStrings[testSetting] +
-              '\nBlock (default)\nAllow\nBlock\nAsk',
+          str + '\nBlock (default)\nAllow\nBlock\nAsk',
           testElement.$.permissionItem.innerText.trim());
       assertTrue(testElement.$.permissionItem.classList.contains('two-line'));
       assertTrue(testElement.$.permission.disabled);
-      assertEquals(testSetting, testElement.$.permission.value);
+      assertEquals(setting, testElement.$.permission.value);
     }
 
     // Permissions that have been set by enterprise policy.
-    const policySourceStrings = {};
-    policySourceStrings[ContentSetting.ALLOW] = 'Allowed by your administrator';
-    policySourceStrings[ContentSetting.BLOCK] = 'Blocked by your administrator';
-    policySourceStrings[ContentSetting.ASK] =
-        'Setting controlled by your administrator';
+    const policySourceStrings: Map<ContentSetting, string> = new Map([
+      [ContentSetting.ALLOW, 'Allowed by your administrator'],
+      [ContentSetting.ASK, 'Setting controlled by your administrator'],
+      [ContentSetting.BLOCK, 'Blocked by your administrator'],
+    ]);
 
-    for (const testSetting in policySourceStrings) {
-      testElement.site = {
+    for (const [setting, str] of policySourceStrings) {
+      testElement.site = createRawSiteException(origin, {
         origin: origin,
         embeddingOrigin: origin,
-        setting: testSetting,
+        setting,
         source: SiteSettingSource.POLICY,
-      };
+      });
       assertEquals(
-          policySourceStrings[testSetting] +
-              '\nBlock (default)\nAllow\nBlock\nAsk',
+          str + '\nBlock (default)\nAllow\nBlock\nAsk',
           testElement.$.permissionItem.innerText.trim());
       assertTrue(testElement.$.permissionItem.classList.contains('two-line'));
       assertTrue(testElement.$.permission.disabled);
-      assertEquals(testSetting, testElement.$.permission.value);
+      assertEquals(setting, testElement.$.permission.value);
     }
 
     // Finally, check if changing the source from a non-user-controlled setting
     // (policy) back to a user-controlled one re-enables the control.
-    testElement.site = {
+    testElement.site = createRawSiteException(origin, {
       origin: origin,
       embeddingOrigin: origin,
       setting: ContentSetting.ASK,
       source: SiteSettingSource.DEFAULT,
-    };
+    });
     assertEquals(
         'Ask (default)\nAllow\nBlock\nAsk',
         testElement.$.permissionItem.innerText.trim());
@@ -250,12 +248,12 @@
   test('info string correct for ads', function() {
     const origin = 'https://www.example.com';
     testElement.category = ContentSettingsTypes.ADS;
-    testElement.site = {
+    testElement.site = createRawSiteException(origin, {
       origin: origin,
       embeddingOrigin: origin,
       setting: ContentSetting.BLOCK,
       source: SiteSettingSource.ADS_FILTER_BLACKLIST,
-    };
+    });
     assertEquals(
         'Site shows intrusive or misleading ads' +
             '\nAllow\nBlock\nAsk',
@@ -264,12 +262,12 @@
     assertFalse(testElement.$.permission.disabled);
 
     // Check the string that shows when ads is blocked but not blacklisted.
-    testElement.site = {
+    testElement.site = createRawSiteException(origin, {
       origin: origin,
       embeddingOrigin: origin,
       setting: ContentSetting.BLOCK,
       source: SiteSettingSource.PREFERENCE,
-    };
+    });
     assertEquals(
         'Block if site shows intrusive or misleading ads' +
             '\nAllow\nBlock\nAsk',
@@ -278,12 +276,12 @@
     assertFalse(testElement.$.permission.disabled);
 
     // Ditto for default block settings.
-    testElement.site = {
+    testElement.site = createRawSiteException(origin, {
       origin: origin,
       embeddingOrigin: origin,
       setting: ContentSetting.BLOCK,
       source: SiteSettingSource.DEFAULT,
-    };
+    });
     assertEquals(
         'Block if site shows intrusive or misleading ads' +
             '\nBlock (default)\nAllow\nBlock\nAsk',
@@ -292,12 +290,12 @@
     assertFalse(testElement.$.permission.disabled);
 
     // Allowing ads for unblacklisted sites shows nothing.
-    testElement.site = {
+    testElement.site = createRawSiteException(origin, {
       origin: origin,
       embeddingOrigin: origin,
       setting: ContentSetting.ALLOW,
       source: SiteSettingSource.PREFERENCE,
-    };
+    });
     assertEquals(
         'Block (default)\nAllow\nBlock\nAsk',
         testElement.$.permissionItem.innerText.trim());
@@ -309,12 +307,12 @@
     const origin = 'chrome://test';
     testElement.category = ContentSettingsTypes.NOTIFICATIONS;
     testElement.$.details.hidden = false;
-    testElement.site = {
+    testElement.site = createRawSiteException(origin, {
       origin: origin,
       embeddingOrigin: origin,
       setting: ContentSetting.ALLOW,
       source: SiteSettingSource.ALLOWLIST,
-    };
+    });
     assertEquals(
         'Allowlisted internally\nAllow\nBlock\nAsk',
         testElement.$.permissionItem.innerText.trim());
@@ -327,12 +325,12 @@
     browserProxy.setPrefs(prefs);
     testElement.category = ContentSettingsTypes.SOUND;
     testElement.label = 'Sound';
-    testElement.site = {
+    testElement.site = createRawSiteException(origin, {
       origin: origin,
       embeddingOrigin: '',
       setting: ContentSetting.ALLOW,
       source: SiteSettingSource.PREFERENCE,
-    };
+    });
 
     return browserProxy.whenCalled('getDefaultValueForContentType')
         .then((args) => {
@@ -341,7 +339,7 @@
 
           // The default option will always be the first in the menu.
           assertEquals(
-              'Allow (default)', testElement.$.permission.options[0].text,
+              'Allow (default)', testElement.$.permission.options[0]!.text,
               'Default setting string should match prefs');
           browserProxy.resetResolver('getDefaultValueForContentType');
           const defaultPrefs = createSiteSettingsPrefs(
@@ -356,7 +354,7 @@
         .then((args) => {
           assertEquals(ContentSettingsTypes.SOUND, args);
           assertEquals(
-              'Mute (default)', testElement.$.permission.options[0].text,
+              'Mute (default)', testElement.$.permission.options[0]!.text,
               'Default setting string should match prefs');
           browserProxy.resetResolver('getDefaultValueForContentType');
           testElement.useAutomaticLabel = true;
@@ -372,7 +370,7 @@
         .then((args) => {
           assertEquals(ContentSettingsTypes.SOUND, args);
           assertEquals(
-              'Automatic (default)', testElement.$.permission.options[0].text,
+              'Automatic (default)', testElement.$.permission.options[0]!.text,
               'Default setting string should match prefs');
         });
   });
@@ -382,12 +380,12 @@
     browserProxy.setPrefs(prefs);
     testElement.category = ContentSettingsTypes.SOUND;
     testElement.label = 'Sound';
-    testElement.site = {
+    testElement.site = createRawSiteException(origin, {
       origin: origin,
       embeddingOrigin: '',
       setting: ContentSetting.ALLOW,
       source: SiteSettingSource.PREFERENCE,
-    };
+    });
 
     return browserProxy.whenCalled('getDefaultValueForContentType')
         .then((args) => {
@@ -396,7 +394,7 @@
 
           // The block option will always be the third in the menu.
           assertEquals(
-              'Mute', testElement.$.permission.options[2].text,
+              'Mute', testElement.$.permission.options[2]!.text,
               'Block setting string should match prefs');
         });
   });
@@ -405,18 +403,19 @@
     const origin = 'https://www.example.com';
     testElement.category = ContentSettingsTypes.USB_DEVICES;
     testElement.label = 'USB';
-    testElement.site = {
+    testElement.site = createRawSiteException(origin, {
       origin: origin,
       embeddingOrigin: origin,
       setting: ContentSetting.ASK,
       source: SiteSettingSource.PREFERENCE,
-    };
+    });
 
     // In addition to the assertions below, the main goal of this test is to
     // ensure we do not hit any assertions when choosing ASK as a setting.
     assertEquals(testElement.$.permission.value, ContentSetting.ASK);
     assertFalse(testElement.$.permission.disabled);
-    assertFalse(testElement.$.permission.options.ask.hidden);
+    assertFalse(
+        testElement.$.permission.querySelector<HTMLElement>('#ask')!.hidden);
   });
 
   test(
@@ -425,31 +424,34 @@
         const origin = 'https://www.example.com';
         testElement.category = ContentSettingsTypes.BLUETOOTH_SCANNING;
         testElement.label = 'Bluetooth-scanning';
-        testElement.site = {
+        testElement.site = createRawSiteException(origin, {
           origin: origin,
           embeddingOrigin: origin,
           setting: ContentSetting.ASK,
           source: SiteSettingSource.PREFERENCE,
-        };
+        });
 
         // In addition to the assertions below, the main goal of this test is to
         // ensure we do not hit any assertions when choosing ASK as a setting.
         assertEquals(testElement.$.permission.value, ContentSetting.ASK);
         assertFalse(testElement.$.permission.disabled);
-        assertFalse(testElement.$.permission.options.ask.hidden);
+        assertFalse(testElement.$.permission.querySelector<HTMLElement>(
+                                                '#ask')!.hidden);
 
-        testElement.site = {
+        testElement.site = createRawSiteException(origin, {
           origin: origin,
           embeddingOrigin: origin,
           setting: ContentSetting.BLOCK,
           source: SiteSettingSource.PREFERENCE,
-        };
+        });
 
         // In addition to the assertions below, the main goal of this test is to
         // ensure we do not hit any assertions when choosing BLOCK as a setting.
         assertEquals(testElement.$.permission.value, ContentSetting.BLOCK);
         assertFalse(testElement.$.permission.disabled);
-        assertFalse(testElement.$.permission.options.block.hidden);
+        assertFalse(
+            testElement.$.permission.querySelector<HTMLElement>(
+                                        '#block')!.hidden);
       });
 
   test(
@@ -458,31 +460,34 @@
         const origin = 'https://www.example.com';
         testElement.category = ContentSettingsTypes.FILE_SYSTEM_WRITE;
         testElement.label = 'Save to original files';
-        testElement.site = {
+        testElement.site = createRawSiteException(origin, {
           origin: origin,
           embeddingOrigin: origin,
           setting: ContentSetting.ASK,
           source: SiteSettingSource.PREFERENCE,
-        };
+        });
 
         // In addition to the assertions below, the main goal of this test is to
         // ensure we do not hit any assertions when choosing ASK as a setting.
         assertEquals(testElement.$.permission.value, ContentSetting.ASK);
         assertFalse(testElement.$.permission.disabled);
-        assertFalse(testElement.$.permission.options.ask.hidden);
+        assertFalse(testElement.$.permission.querySelector<HTMLElement>(
+                                                '#ask')!.hidden);
 
-        testElement.site = {
+        testElement.site = createRawSiteException(origin, {
           origin: origin,
           embeddingOrigin: origin,
           setting: ContentSetting.BLOCK,
           source: SiteSettingSource.PREFERENCE,
-        };
+        });
 
         // In addition to the assertions below, the main goal of this test is to
         // ensure we do not hit any assertions when choosing BLOCK as a setting.
         assertEquals(testElement.$.permission.value, ContentSetting.BLOCK);
         assertFalse(testElement.$.permission.disabled);
-        assertFalse(testElement.$.permission.options.block.hidden);
+        assertFalse(
+            testElement.$.permission.querySelector<HTMLElement>(
+                                        '#block')!.hidden);
       });
 
   test('settingDetail string is respected', function() {
@@ -491,25 +496,25 @@
 
     testElement.category = ContentSettingsTypes.SOUND;
     testElement.label = 'Sound';
-    testElement.site = {
+    testElement.site = createRawSiteException(origin, {
       origin: origin,
       embeddingOrigin: '',
       setting: ContentSetting.ALLOW,
       source: SiteSettingSource.PREFERENCE,
-    };
+    });
 
     // Typically, the secondary text is hidden.
     assertTrue(testElement.$.permissionSecondary.hidden);
 
     testElement.category = ContentSettingsTypes.FILE_HANDLING;
     testElement.label = 'File handlers';
-    testElement.site = {
+    testElement.site = createRawSiteException(origin, {
       origin: origin,
       embeddingOrigin: '',
       setting: ContentSetting.ALLOW,
       source: SiteSettingSource.PREFERENCE,
       settingDetail: '.txt',
-    };
+    });
 
     // For file handlers with a `settingDetail`, the secondary text is shown.
     assertFalse(testElement.$.permissionSecondary.hidden);
diff --git a/chrome/test/data/webui/settings/site_details_tests.js b/chrome/test/data/webui/settings/site_details_tests.ts
similarity index 83%
rename from chrome/test/data/webui/settings/site_details_tests.js
rename to chrome/test/data/webui/settings/site_details_tests.ts
index 9f2421e..d420492 100644
--- a/chrome/test/data/webui/settings/site_details_tests.js
+++ b/chrome/test/data/webui/settings/site_details_tests.ts
@@ -3,32 +3,31 @@
 // found in the LICENSE file.
 
 // clang-format off
-import {isChromeOS, isWindows, webUIListenerCallback} from 'chrome://resources/js/cr.m.js';
-import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+import {isChromeOS, webUIListenerCallback} from 'chrome://resources/js/cr.m.js';
 import {listenOnce} from 'chrome://resources/js/util.m.js';
-import {flush,Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {ChooserType,ContentSetting,ContentSettingsTypes,SiteSettingSource,SiteSettingsPrefsBrowserProxyImpl,WebsiteUsageBrowserProxyImpl} from 'chrome://settings/lazy_load.js';
-import {MetricsBrowserProxyImpl, PrivacyElementInteractions, Route,Router,routes} from 'chrome://settings/settings.js';
+import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {ChooserType, ContentSetting, ContentSettingsTypes, SiteDetailsElement, SiteSettingSource, SiteSettingsPrefsBrowserProxyImpl, WebsiteUsageBrowserProxy, WebsiteUsageBrowserProxyImpl} from 'chrome://settings/lazy_load.js';
+import {MetricsBrowserProxyImpl, PrivacyElementInteractions, Router, routes} from 'chrome://settings/settings.js';
+import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
 import {TestBrowserProxy} from 'chrome://webui-test/test_browser_proxy.js';
 
 import {TestMetricsBrowserProxy} from './test_metrics_browser_proxy.js';
 import {TestSiteSettingsPrefsBrowserProxy} from './test_site_settings_prefs_browser_proxy.js';
-import {createContentSettingTypeToValuePair,createRawChooserException,createRawSiteException,createSiteSettingsPrefs} from './test_util.js';
+import {createContentSettingTypeToValuePair, createRawChooserException, createRawSiteException, createSiteSettingsPrefs, SiteSettingsPref} from './test_util.js';
 
 // clang-format on
 
-class TestWebsiteUsageBrowserProxy extends TestBrowserProxy {
+class TestWebsiteUsageBrowserProxy extends TestBrowserProxy implements
+    WebsiteUsageBrowserProxy {
   constructor() {
     super(['clearUsage', 'fetchUsageTotal']);
   }
 
-  /** @override */
-  fetchUsageTotal(host) {
+  fetchUsageTotal(host: string) {
     this.methodCalled('fetchUsageTotal', host);
   }
 
-  /** @override */
-  clearUsage(origin) {
+  clearUsage(origin: string) {
     this.methodCalled('clearUsage', origin);
   }
 }
@@ -37,30 +36,25 @@
 suite('SiteDetails', function() {
   /**
    * A site list element created before each test.
-   * @type {SiteDetails}
    */
-  let testElement;
+  let testElement: SiteDetailsElement;
 
   /**
    * An example pref with 1 pref in each category.
-   * @type {SiteSettingsPref}
    */
-  let prefs;
+  let prefs: SiteSettingsPref;
 
   /**
    * The mock site settings prefs proxy object to use during test.
-   * @type {TestSiteSettingsPrefsBrowserProxy}
    */
-  let browserProxy;
+  let browserProxy: TestSiteSettingsPrefsBrowserProxy;
 
-  /** @type {!TestMetricsBrowserProxy} */
-  let testMetricsBrowserProxy;
+  let testMetricsBrowserProxy: TestMetricsBrowserProxy;
 
   /**
    * The mock website usage proxy object to use during test.
-   * @type {TestWebsiteUsageBrowserProxy}
    */
-  let websiteUsageProxy;
+  let websiteUsageProxy: TestWebsiteUsageBrowserProxy;
 
   // Initialize a site-details before each test.
   setup(function() {
@@ -185,10 +179,10 @@
     websiteUsageProxy = new TestWebsiteUsageBrowserProxy();
     WebsiteUsageBrowserProxyImpl.setInstance(websiteUsageProxy);
 
-    PolymerTest.clearBody();
+    document.body.innerHTML = '';
   });
 
-  function createSiteDetails(origin) {
+  function createSiteDetails(origin: string) {
     const siteDetailsElement = document.createElement('site-details');
     document.body.appendChild(siteDetailsElement);
     Router.getInstance().navigateTo(
@@ -201,28 +195,24 @@
     browserProxy.setPrefs(prefs);
     testElement = createSiteDetails('https://foo.com:443');
     await websiteUsageProxy.whenCalled('fetchUsageTotal');
-    assertTrue(!!testElement.shadowRoot.querySelector('#usage'));
+    assertTrue(!!testElement.$.usage);
 
     // When there's no usage, there should be a string that says so.
     assertEquals(
         '',
-        testElement.shadowRoot.querySelector('#storedData').textContent.trim());
-    assertFalse(testElement.shadowRoot.querySelector('#noStorage').hidden);
-    assertTrue(testElement.shadowRoot.querySelector('#storage').hidden);
-    assertTrue(
-        testElement.shadowRoot.querySelector('#usage').innerText.indexOf(
-            'No usage data') !== -1);
+        testElement.shadowRoot!.querySelector(
+                                   '#storedData')!.textContent!.trim());
+    assertFalse(testElement.$.noStorage.hidden);
+    assertTrue(testElement.$.storage.hidden);
+    assertTrue(testElement.$.usage.textContent!.includes('No usage data'));
 
     // If there is, check the correct amount of usage is specified.
     const usage = '1 KB';
     webUIListenerCallback(
         'usage-total-changed', 'foo.com', usage, '10 cookies');
-    flush();
-    assertTrue(testElement.shadowRoot.querySelector('#noStorage').hidden);
-    assertFalse(testElement.shadowRoot.querySelector('#storage').hidden);
-    assertTrue(
-        testElement.shadowRoot.querySelector('#usage').innerText.indexOf(
-            usage) !== -1);
+    assertTrue(testElement.$.noStorage.hidden);
+    assertFalse(testElement.$.storage.hidden);
+    assertTrue(testElement.$.usage.textContent!.includes(usage));
   });
 
   test('storage gets trashed properly', function() {
@@ -232,8 +222,6 @@
 
     flush();
 
-    // Call onOriginChanged_() manually to simulate a new navigation.
-    testElement.currentRouteChanged(Route);
     return Promise
         .all([
           browserProxy.whenCalled('getOriginPermissions'),
@@ -246,14 +234,14 @@
               'usage-total-changed', hostRequested, '1 KB', '10 cookies');
           assertEquals(
               '1 KB',
-              testElement.shadowRoot.querySelector('#storedData')
-                  .textContent.trim());
-          assertTrue(testElement.shadowRoot.querySelector('#noStorage').hidden);
-          assertFalse(testElement.shadowRoot.querySelector('#storage').hidden);
+              testElement.shadowRoot!.querySelector(
+                                         '#storedData')!.textContent!.trim());
+          assertTrue(testElement.$.noStorage.hidden);
+          assertFalse(testElement.$.storage.hidden);
 
-          testElement.shadowRoot
-              .querySelector('#confirmClearStorage .action-button')
-              .click();
+          testElement.shadowRoot!
+              .querySelector<HTMLElement>(
+                  '#confirmClearStorage .action-button')!.click();
           return websiteUsageProxy.whenCalled('clearUsage');
         })
         .then(originCleared => {
@@ -266,8 +254,6 @@
     browserProxy.setPrefs(prefs);
     testElement = createSiteDetails(origin);
 
-    // Call onOriginChanged_() manually to simulate a new navigation.
-    testElement.currentRouteChanged(Route);
     return Promise
         .all([
           browserProxy.whenCalled('getOriginPermissions'),
@@ -282,14 +268,14 @@
               'usage-total-changed', hostRequested, '1 KB', '10 cookies');
           assertEquals(
               '10 cookies',
-              testElement.shadowRoot.querySelector('#numCookies')
-                  .textContent.trim());
-          assertTrue(testElement.shadowRoot.querySelector('#noStorage').hidden);
-          assertFalse(testElement.shadowRoot.querySelector('#storage').hidden);
+              testElement.shadowRoot!.querySelector(
+                                         '#numCookies')!.textContent!.trim());
+          assertTrue(testElement.$.noStorage.hidden);
+          assertFalse(testElement.$.storage.hidden);
 
-          testElement.shadowRoot
-              .querySelector('#confirmClearStorage .action-button')
-              .click();
+          testElement.shadowRoot!
+              .querySelector<HTMLElement>(
+                  '#confirmClearStorage .action-button')!.click();
           return websiteUsageProxy.whenCalled('clearUsage');
         })
         .then(originCleared => {
@@ -312,7 +298,7 @@
           return browserProxy.whenCalled('getOriginPermissions');
         })
         .then(() => {
-          testElement.root.querySelectorAll('site-details-permission')
+          testElement.shadowRoot!.querySelectorAll('site-details-permission')
               .forEach((siteDetailsPermission) => {
                 if (!isChromeOS &&
                     siteDetailsPermission.category ===
@@ -338,10 +324,10 @@
                     siteDetailsPermission.category ===
                         ContentSettingsTypes.FILE_SYSTEM_WRITE) {
                   expectedSetting =
-                      prefs.exceptions[siteDetailsPermission.category][0]
+                      prefs.exceptions[siteDetailsPermission.category][0]!
                           .setting;
                   expectedSource =
-                      prefs.exceptions[siteDetailsPermission.category][0]
+                      prefs.exceptions[siteDetailsPermission.category][0]!
                           .source;
                   expectedMenuValue =
                       (expectedSource === SiteSettingSource.DEFAULT) ?
@@ -370,7 +356,7 @@
           return browserProxy.whenCalled('getOriginPermissions');
         })
         .then(() => {
-          testElement.root.querySelectorAll('site-details-permission')
+          testElement.shadowRoot!.querySelectorAll('site-details-permission')
               .forEach((siteDetailsPermission) => {
                 const shouldBeVisible = siteDetailsPermission.category ===
                         ContentSettingsTypes.NOTIFICATIONS ||
@@ -391,12 +377,14 @@
 
     // Check both cancelling and accepting the dialog closes it.
     ['cancel-button', 'action-button'].forEach(buttonType => {
-      testElement.shadowRoot.querySelector('#resetSettingsButton').click();
+      testElement.shadowRoot!
+          .querySelector<HTMLElement>('#resetSettingsButton')!.click();
       assertTrue(testElement.$.confirmResetSettings.open);
       const actionButtonList =
-          testElement.$.confirmResetSettings.getElementsByClassName(buttonType);
+          testElement.shadowRoot!.querySelectorAll<HTMLElement>(
+              `#confirmResetSettings .${buttonType}`);
       assertEquals(1, actionButtonList.length);
-      actionButtonList[0].click();
+      actionButtonList[0]!.click();
       assertFalse(testElement.$.confirmResetSettings.open);
     });
 
@@ -415,12 +403,14 @@
 
     // Check both cancelling and accepting the dialog closes it.
     ['cancel-button', 'action-button'].forEach(buttonType => {
-      testElement.shadowRoot.querySelector('#usage cr-button').click();
+      testElement.shadowRoot!.querySelector<HTMLElement>(
+                                 '#usage cr-button')!.click();
       assertTrue(testElement.$.confirmClearStorage.open);
       const actionButtonList =
-          testElement.$.confirmClearStorage.getElementsByClassName(buttonType);
+          testElement.shadowRoot!.querySelectorAll<HTMLElement>(
+              `#confirmClearStorage .${buttonType}`);
       assertEquals(1, actionButtonList.length);
-      actionButtonList[0].click();
+      actionButtonList[0]!.click();
       assertFalse(testElement.$.confirmClearStorage.open);
     });
   });
@@ -430,9 +420,10 @@
     const origin = 'https://foo.com:443';
     testElement = createSiteDetails(origin);
 
-    const elems = testElement.root.querySelectorAll('site-details-permission');
+    const elems =
+        testElement.shadowRoot!.querySelectorAll('site-details-permission');
     const notificationPermission = Array.from(elems).find(
-        elem => elem.category === ContentSettingsTypes.NOTIFICATIONS);
+        elem => elem.category === ContentSettingsTypes.NOTIFICATIONS)!;
 
     // Wait for all the permissions to be populated initially.
     return browserProxy.whenCalled('isOriginValid')
@@ -448,12 +439,12 @@
               ContentSetting.ASK, notificationPermission.$.permission.value);
 
           // Set new prefs and make sure only that permission is updated.
-          const newException = {
+          const newException = createRawSiteException(origin, {
             embeddingOrigin: origin,
             origin: origin,
             setting: ContentSetting.BLOCK,
             source: SiteSettingSource.DEFAULT,
-          };
+          });
           browserProxy.resetResolver('getOriginPermissions');
           browserProxy.setSingleException(
               ContentSettingsTypes.NOTIFICATIONS, newException);
diff --git a/chrome/test/data/webui/signin/sync_confirmation_test.ts b/chrome/test/data/webui/signin/sync_confirmation_test.ts
index 854f17ee..c7861c09 100644
--- a/chrome/test/data/webui/signin/sync_confirmation_test.ts
+++ b/chrome/test/data/webui/signin/sync_confirmation_test.ts
@@ -20,6 +20,28 @@
 [true, false].forEach(isNewDesignEnabled => {
   const suiteSuffix = isNewDesignEnabled ? 'NewDesign' : 'OldDesign';
 
+  // <if expr="not lacros">
+  const STANDARD_TITLE = 'Turn on sync?';
+  const STANDARD_CONSENT_DESCRIPTION_TEXT = [
+    STANDARD_TITLE,
+    'Sync your bookmarks, passwords, history, and more on all your devices',
+    'Google may use your history to personalize Search and other Google ' +
+        'services',
+  ];
+  const STANDARD_CONSENT_CONFIRMATION = 'Yes, I\'m in';
+  // </if>
+  // <if expr="lacros">
+  const STANDARD_TITLE = 'Chrome browser sync is on';
+  const STANDARD_CONSENT_DESCRIPTION_TEXT = [
+    STANDARD_TITLE,
+    'Your bookmarks, passwords, history, and more are synced on all your ' +
+        'devices',
+    'Google may use your history to personalize Search and other Google ' +
+        'services',
+  ];
+  const STANDARD_CONSENT_CONFIRMATION = 'Done';
+  // </if>
+
   suite(`SigninSyncConfirmationTest${suiteSuffix}`, function() {
     let app: SyncConfirmationAppElement;
 
@@ -38,7 +60,7 @@
     // Tests that no DCHECKS are thrown during initialization of the UI.
     test('LoadPage', function() {
       assertEquals(
-          'Turn on sync?',
+          STANDARD_TITLE,
           app.shadowRoot!.querySelector(
                              '#syncConfirmationHeading')!.textContent!.trim());
 
@@ -81,13 +103,6 @@
       await browserProxy.whenCalled('requestAccountInfo');
     });
 
-    const STANDARD_CONSENT_DESCRIPTION_TEXT = [
-      'Turn on sync?',
-      'Sync your bookmarks, passwords, history, and more on all your devices',
-      'Google may use your history to personalize Search and other Google ' +
-          'services',
-    ];
-
     // Tests that the expected strings are recorded when clicking the Confirm
     // button.
     test('recordConsentOnConfirm', async function() {
@@ -97,7 +112,7 @@
       assertEquals(
           JSON.stringify(STANDARD_CONSENT_DESCRIPTION_TEXT),
           JSON.stringify(description));
-      assertEquals('Yes, I\'m in', confirmation);
+      assertEquals(STANDARD_CONSENT_CONFIRMATION, confirmation);
     });
 
     // Tests that the expected strings are recorded when clicking the Confirm
@@ -109,6 +124,8 @@
       assertEquals(
           JSON.stringify(STANDARD_CONSENT_DESCRIPTION_TEXT),
           JSON.stringify(description));
+      // 'Sync settings' is recorded for new design but this is passed from the
+      // UI class so overriding loadTimeData does not help here.
       assertEquals('Settings', confirmation);
     });
   });
diff --git a/chromecast/BUILD.gn b/chromecast/BUILD.gn
index 5379f8c..6c7722a 100644
--- a/chromecast/BUILD.gn
+++ b/chromecast/BUILD.gn
@@ -550,6 +550,7 @@
     "$root_gen_dir/ui/resources/webui_generated_resources.pak",
     "$root_gen_dir/ui/resources/webui_resources.pak",
     "$root_gen_dir/ui/strings/app_locale_settings_en-US.pak",
+    "$root_gen_dir/ui/strings/ax_strings_en-US.pak",
     "$root_gen_dir/ui/strings/ui_strings_en-US.pak",
   ]
 
diff --git a/chromecast/media/audio/capture_service/capture_service_receiver_unittest.cc b/chromecast/media/audio/capture_service/capture_service_receiver_unittest.cc
index d7f443f..fc58c03 100644
--- a/chromecast/media/audio/capture_service/capture_service_receiver_unittest.cc
+++ b/chromecast/media/audio/capture_service/capture_service_receiver_unittest.cc
@@ -5,6 +5,7 @@
 #include "chromecast/media/audio/capture_service/capture_service_receiver.h"
 
 #include <cstddef>
+#include <cstdint>
 #include <memory>
 
 #include "base/big_endian.h"
@@ -128,7 +129,7 @@
         EXPECT_EQ(buf_len, static_cast<int>(sizeof(HandshakePacket)));
         const char* data = buf->data();
         uint16_t size;
-        base::ReadBigEndian(data, &size);
+        base::ReadBigEndian(reinterpret_cast<const uint8_t*>(data), &size);
         EXPECT_EQ(size, sizeof(HandshakePacket) - sizeof(size));
         HandshakePacket packet;
         std::memcpy(&packet, data, sizeof(HandshakePacket));
diff --git a/chromecast/media/audio/net/audio_socket.cc b/chromecast/media/audio/net/audio_socket.cc
index 89bbd8de..eebc232f 100644
--- a/chromecast/media/audio/net/audio_socket.cc
+++ b/chromecast/media/audio/net/audio_socket.cc
@@ -37,7 +37,7 @@
     return false;
   }
 
-  base::ReadBigEndian(data, &padding_bytes);
+  base::ReadBigEndian(reinterpret_cast<const uint8_t*>(data), &padding_bytes);
   size -= sizeof(padding_bytes);
 
   if (padding_bytes < 0 || padding_bytes > 3) {
@@ -150,7 +150,8 @@
 bool AudioSocket::SendPreparedAudioBuffer(
     scoped_refptr<net::IOBuffer> audio_buffer) {
   uint16_t payload_size;
-  base::ReadBigEndian(audio_buffer->data(), &payload_size);
+  base::ReadBigEndian(reinterpret_cast<uint8_t*>(audio_buffer->data()),
+                      &payload_size);
   DCHECK_GE(payload_size, kAudioHeaderSize);
   return SendBuffer(0, std::move(audio_buffer),
                     sizeof(uint16_t) + payload_size);
@@ -229,7 +230,8 @@
   pending_writes_.swap(pending);
   for (auto& m : pending) {
     uint16_t message_size;
-    base::ReadBigEndian(m.second->data(), &message_size);
+    base::ReadBigEndian(reinterpret_cast<uint8_t*>(m.second->data()),
+                        &message_size);
     SendBufferToSocket(m.first, std::move(m.second),
                        sizeof(uint16_t) + message_size);
   }
diff --git a/chromecast/net/small_message_socket.cc b/chromecast/net/small_message_socket.cc
index f43dd17..f97f714 100644
--- a/chromecast/net/small_message_socket.cc
+++ b/chromecast/net/small_message_socket.cc
@@ -335,7 +335,7 @@
   }
 
   uint16_t first_size;
-  base::ReadBigEndian(ptr, &first_size);
+  base::ReadBigEndian(reinterpret_cast<uint8_t*>(ptr), &first_size);
 
   if (first_size < kMax2ByteSize) {
     data_offset = sizeof(uint16_t);
@@ -345,7 +345,8 @@
       return false;
     }
     uint32_t real_size;
-    base::ReadBigEndian(ptr + sizeof(uint16_t), &real_size);
+    base::ReadBigEndian(reinterpret_cast<uint8_t*>(ptr) + sizeof(uint16_t),
+                        &real_size);
     data_offset = sizeof(uint16_t) + sizeof(uint32_t);
     message_size = real_size;
   }
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index 08be718a..2eae79d 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -1892,6 +1892,9 @@
       <message name="IDS_PERSONALIZATION_APP_GOOGLE_PHOTOS_PHOTOS_TAB" desc="Label for the Photos tab in the Google Photos page in the wallpaper app.">
         Photos
       </message>
+      <message name="IDS_PERSONALIZATION_APP_GOOGLE_PHOTOS_ZERO_STATE_MESSAGE" desc="Message shown in the Google Photos page in the wallpaper app when the user has no photos.">
+        No image available. To add photos, go to <ph name="LINK">$1<ex>photos.google.com</ex></ph>
+      </message>
       <message name="IDS_PERSONALIZATION_APP_MY_IMAGES" desc="Label for the local images page in wallpaper app.">
         My Images
       </message>
diff --git a/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_GOOGLE_PHOTOS_ZERO_STATE_MESSAGE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_GOOGLE_PHOTOS_ZERO_STATE_MESSAGE.png.sha1
new file mode 100644
index 0000000..ecbf175
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_PERSONALIZATION_APP_GOOGLE_PHOTOS_ZERO_STATE_MESSAGE.png.sha1
@@ -0,0 +1 @@
+714e69604047b80d49c17634e1e0e86e163857d0
\ No newline at end of file
diff --git a/chromeos/crosapi/mojom/crosapi.mojom b/chromeos/crosapi/mojom/crosapi.mojom
index ca04c37..d51ceea 100644
--- a/chromeos/crosapi/mojom/crosapi.mojom
+++ b/chromeos/crosapi/mojom/crosapi.mojom
@@ -77,6 +77,12 @@
   // For example, "87.0.0.1 dev", "86.0.4240.38 beta".
   string browser_version@0;
 
+  // Lacros publishes the set of workarounds it has implemented. This is needed
+  // to cherry-picks of lacros bug fixes to older branches. See
+  // `ash_workarounds` for more details.
+  [MinVersion=1]
+  array<string>? lacros_workarounds@1;
+
   // TODO(crbug.com/1119925): Add more parameters later.
 };
 
@@ -600,8 +606,8 @@
 // If ash-chrome is newer than the browser, then some fields may not be
 // processed by the browser.
 //
-// Next version: 32
-// Next id: 31
+// Next version: 33
+// Next id: 32
 [Stable, RenamedFrom="crosapi.mojom.LacrosInitParams"]
 struct BrowserInitParams {
   // This is ash-chrome's version of the Crosapi interface. This is used by
@@ -801,6 +807,16 @@
   // is present for Ash.
   [MinVersion=31]
   bool is_unfiltered_bluetooth_device_enabled@30;
+
+  // Ash publishes the list of workarounds it has implemented to Lacros so
+  // that Lacros can dynamically change its behavior. This should typically take
+  // the format of a bug identifier, e.g. "crbug/123" or "b/456". This is
+  // primarily necessary for backporting behavior changes and fixes to older
+  // branches. When adding a new workaround, please indicate with a comment when
+  // we can stop publishing the workaround -- e.g. once Ash and Lacros are both
+  // past M100, then we can remove this workaround.
+  [MinVersion=32]
+  array<string>? ash_workarounds@31;
 };
 
 // BrowserService defines the APIs that live in a browser (such as
diff --git a/chromeos/dbus/fwupd/fake_fwupd_client.cc b/chromeos/dbus/fwupd/fake_fwupd_client.cc
index 29b9a37..b8aacaa 100644
--- a/chromeos/dbus/fwupd/fake_fwupd_client.cc
+++ b/chromeos/dbus/fwupd/fake_fwupd_client.cc
@@ -22,22 +22,22 @@
 
 void FakeFwupdClient::RequestDevices() {
   // TODO(swifton): This is a stub.
-  auto devices = std::make_unique<FwupdDeviceList>();
+  FwupdDeviceList devices;
   for (auto& observer : observers_)
-    observer.OnDeviceListResponse(devices.get());
+    observer.OnDeviceListResponse(&devices);
 }
 
 void FakeFwupdClient::RequestUpdates(const std::string& device_id) {
   // TODO(swifton): This is a stub.
-  auto updates = std::make_unique<FwupdUpdateList>();
 
   // This matches the behavior of the real class. I.e. if you send an unknown
   // id, nothing happens.
   if (device_id != kFakeDeviceIdForTesting)
     return;
 
+  FwupdUpdateList updates;
   for (auto& observer : observers_)
-    observer.OnUpdateListResponse(updates.get());
+    observer.OnUpdateListResponse(device_id, &updates);
 }
 
 }  // namespace chromeos
\ No newline at end of file
diff --git a/chromeos/dbus/fwupd/fwupd_client.cc b/chromeos/dbus/fwupd/fwupd_client.cc
index cd4bfdc..72701ed 100644
--- a/chromeos/dbus/fwupd/fwupd_client.cc
+++ b/chromeos/dbus/fwupd/fwupd_client.cc
@@ -64,7 +64,7 @@
     proxy_->CallMethodWithErrorResponse(
         &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
         base::BindOnce(&FwupdClientImpl::RequestUpdatesCallback,
-                       weak_ptr_factory_.GetWeakPtr()));
+                       weak_ptr_factory_.GetWeakPtr(), device_id));
   }
 
   void RequestDevices() override {
@@ -126,7 +126,8 @@
     return result;
   }
 
-  void RequestUpdatesCallback(dbus::Response* response,
+  void RequestUpdatesCallback(const std::string& device_id,
+                              dbus::Response* response,
                               dbus::ErrorResponse* error_response) {
     if (!response) {
       LOG(ERROR) << "No Dbus response received from fwupd.";
@@ -141,8 +142,7 @@
       return;
     }
 
-    auto updates = std::make_unique<FwupdUpdateList>();
-
+    FwupdUpdateList updates;
     while (array_reader.HasMoreData()) {
       // Parse update description.
       std::unique_ptr<base::DictionaryValue> dict =
@@ -164,12 +164,12 @@
         return;
       }
 
-      updates->emplace_back(version->GetString(), description->GetString(),
-                            priority->GetInt());
+      updates.emplace_back(version->GetString(), description->GetString(),
+                           priority->GetInt());
     }
 
     for (auto& observer : observers_) {
-      observer.OnUpdateListResponse(updates.get());
+      observer.OnUpdateListResponse(device_id, &updates);
     }
   }
 
@@ -188,8 +188,7 @@
       return;
     }
 
-    auto devices = std::make_unique<FwupdDeviceList>();
-
+    FwupdDeviceList devices;
     while (array_reader.HasMoreData()) {
       // Parse device description.
       std::unique_ptr<base::DictionaryValue> dict =
@@ -209,11 +208,11 @@
         return;
       }
 
-      devices->emplace_back(id->GetString(), name->GetString());
+      devices.emplace_back(id->GetString(), name->GetString());
     }
 
     for (auto& observer : observers_)
-      observer.OnDeviceListResponse(devices.get());
+      observer.OnDeviceListResponse(&devices);
   }
 
   void OnSignalConnected(const std::string& interface_name,
diff --git a/chromeos/dbus/fwupd/fwupd_client.h b/chromeos/dbus/fwupd/fwupd_client.h
index 1388fdc2..ee9e08a43 100644
--- a/chromeos/dbus/fwupd/fwupd_client.h
+++ b/chromeos/dbus/fwupd/fwupd_client.h
@@ -6,6 +6,7 @@
 #define CHROMEOS_DBUS_FWUPD_FWUPD_CLIENT_H_
 
 #include <memory>
+#include <string>
 
 #include "base/component_export.h"
 #include "base/observer_list.h"
@@ -21,7 +22,8 @@
    public:
     ~Observer() override = default;
     virtual void OnDeviceListResponse(FwupdDeviceList* devices) = 0;
-    virtual void OnUpdateListResponse(FwupdUpdateList* updates) = 0;
+    virtual void OnUpdateListResponse(const std::string& device_id,
+                                      FwupdUpdateList* updates) = 0;
   };
 
   void AddObserver(Observer* observer);
diff --git a/chromeos/dbus/fwupd/fwupd_client_unittest.cc b/chromeos/dbus/fwupd/fwupd_client_unittest.cc
index 62ba994..46f2ce0e 100644
--- a/chromeos/dbus/fwupd/fwupd_client_unittest.cc
+++ b/chromeos/dbus/fwupd/fwupd_client_unittest.cc
@@ -48,7 +48,8 @@
               (override));
   MOCK_METHOD(void,
               OnUpdateListResponse,
-              (chromeos::FwupdUpdateList * updates),
+              (const std::string& device_id,
+               chromeos::FwupdUpdateList* updates),
               (override));
 };
 
@@ -108,7 +109,8 @@
     CHECK_EQ(kFakeDeviceIdForTesting, (*devices)[0].id);
   }
 
-  void CheckUpdates(FwupdUpdateList* updates) {
+  void CheckUpdates(const std::string& device_id, FwupdUpdateList* updates) {
+    CHECK_EQ(kFakeDeviceIdForTesting, device_id);
     CHECK_EQ(kFakeUpdateVersionForTesting, (*updates)[0].version);
     CHECK_EQ(kFakeUpdateDescriptionForTesting, (*updates)[0].description);
     // This value is returned by DBus as a uint32_t and is added to a dictionary
@@ -225,7 +227,7 @@
   // The observer will check that the update description is parsed and passed
   // correctly.
   MockObserver observer;
-  EXPECT_CALL(observer, OnUpdateListResponse(_))
+  EXPECT_CALL(observer, OnUpdateListResponse(_, _))
       .Times(1)
       .WillRepeatedly(Invoke(this, &FwupdClientTest::CheckUpdates));
   fwupd_client_->AddObserver(&observer);
diff --git a/chromeos/dbus/fwupd/fwupd_device.cc b/chromeos/dbus/fwupd/fwupd_device.cc
index eef2c79..2f855dfa 100644
--- a/chromeos/dbus/fwupd/fwupd_device.cc
+++ b/chromeos/dbus/fwupd/fwupd_device.cc
@@ -11,8 +11,8 @@
 FwupdDevice::FwupdDevice(const std::string& id, const std::string& device_name)
     : id(id), device_name(device_name) {}
 
-FwupdDevice::FwupdDevice(const FwupdDevice& other) = default;
-
+FwupdDevice::FwupdDevice(FwupdDevice&& other) = default;
+FwupdDevice& FwupdDevice::operator=(FwupdDevice&& other) = default;
 FwupdDevice::~FwupdDevice() = default;
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/fwupd/fwupd_device.h b/chromeos/dbus/fwupd/fwupd_device.h
index 02347cd..c4ed8318 100644
--- a/chromeos/dbus/fwupd/fwupd_device.h
+++ b/chromeos/dbus/fwupd/fwupd_device.h
@@ -15,14 +15,15 @@
 struct COMPONENT_EXPORT(CHROMEOS_DBUS_FWUPD) FwupdDevice {
   FwupdDevice();
   FwupdDevice(const std::string& id, const std::string& device_name);
-  FwupdDevice(const FwupdDevice& other);
+  FwupdDevice(FwupdDevice&& other);
+  FwupdDevice& operator=(FwupdDevice&& other);
   ~FwupdDevice();
 
   std::string id;
   std::string device_name;
 };
 
-typedef std::vector<FwupdDevice> FwupdDeviceList;
+using FwupdDeviceList = std::vector<FwupdDevice>;
 
 }  // namespace chromeos
 
diff --git a/chromeos/dbus/fwupd/fwupd_update.cc b/chromeos/dbus/fwupd/fwupd_update.cc
index bf54178..6cbda596 100644
--- a/chromeos/dbus/fwupd/fwupd_update.cc
+++ b/chromeos/dbus/fwupd/fwupd_update.cc
@@ -13,8 +13,8 @@
                          int priority)
     : version(version), description(description), priority(priority) {}
 
-FwupdUpdate::FwupdUpdate(const FwupdUpdate& other) = default;
-
+FwupdUpdate::FwupdUpdate(FwupdUpdate&& other) = default;
+FwupdUpdate& FwupdUpdate::operator=(FwupdUpdate&& other) = default;
 FwupdUpdate::~FwupdUpdate() = default;
 
 }  // namespace chromeos
diff --git a/chromeos/dbus/fwupd/fwupd_update.h b/chromeos/dbus/fwupd/fwupd_update.h
index c4a5ff3..e5cb25b 100644
--- a/chromeos/dbus/fwupd/fwupd_update.h
+++ b/chromeos/dbus/fwupd/fwupd_update.h
@@ -11,13 +11,14 @@
 
 namespace chromeos {
 
-// Structure to hold FwupdUpdate data received from fwupd.
+// Structure to hold update details received from fwupd.
 struct COMPONENT_EXPORT(CHROMEOS_DBUS_FWUPD) FwupdUpdate {
   FwupdUpdate();
   FwupdUpdate(const std::string& version,
               const std::string& description,
               int priority);
-  FwupdUpdate(const FwupdUpdate& other);
+  FwupdUpdate(FwupdUpdate&& other);
+  FwupdUpdate& operator=(FwupdUpdate&& other);
   ~FwupdUpdate();
 
   std::string version;
@@ -25,7 +26,7 @@
   int priority;
 };
 
-typedef std::vector<FwupdUpdate> FwupdUpdateList;
+using FwupdUpdateList = std::vector<FwupdUpdate>;
 
 }  // namespace chromeos
 
diff --git a/chromeos/dbus/rmad/fake_rmad_client.cc b/chromeos/dbus/rmad/fake_rmad_client.cc
index 4634ed8b..6d16a37d 100644
--- a/chromeos/dbus/rmad/fake_rmad_client.cc
+++ b/chromeos/dbus/rmad/fake_rmad_client.cc
@@ -9,6 +9,13 @@
 
 namespace chromeos {
 namespace {
+
+// Fake state is only used in local builds when the rmad d-bus service is
+// unavailable.
+// Enabling fake state will cause Chrome OS to always display the RMA wizard and
+// the device will be unusable for anything but testing Shimless RMA.
+constexpr bool use_fake_state = false;
+
 constexpr char rsu_challenge_code[] =
     "HRBXHV84NSTHT25WJECYQKB8SARWFTMSWNGFT2FVEEPX69VE99USV3QFBEANDVXGQVL93QK2M6"
     "P3DNV4";
@@ -100,54 +107,61 @@
 /* static */
 void FakeRmadClient::CreateWithState() {
   FakeRmadClient* fake = new FakeRmadClient();
-  // Set up fake component repair state.
-  rmad::GetStateReply components_repair_state =
-      CreateStateReply(rmad::RmadState::kComponentsRepair, rmad::RMAD_ERROR_OK);
-  rmad::ComponentsRepairState::ComponentRepairStatus* component =
-      components_repair_state.mutable_state()
-          ->mutable_components_repair()
-          ->add_components();
-  component->set_component(rmad::RmadComponent::RMAD_COMPONENT_CAMERA);
-  component->set_repair_status(
-      rmad::ComponentsRepairState::ComponentRepairStatus::
-          RMAD_REPAIR_STATUS_UNKNOWN);
-  // Set up fake disable RSU state.
-  rmad::GetStateReply wp_disable_rsu_state =
-      CreateStateReply(rmad::RmadState::kWpDisableRsu, rmad::RMAD_ERROR_OK);
-  wp_disable_rsu_state.mutable_state()
-      ->mutable_wp_disable_rsu()
-      ->set_allocated_challenge_code(new std::string(rsu_challenge_code));
-  wp_disable_rsu_state.mutable_state()
-      ->mutable_wp_disable_rsu()
-      ->set_allocated_hwid(new std::string(rsu_hwid));
-  wp_disable_rsu_state.mutable_state()
-      ->mutable_wp_disable_rsu()
-      ->set_allocated_challenge_url(new std::string(rsu_challenge_url));
+  if (use_fake_state) {
+    // Set up fake component repair state.
+    rmad::GetStateReply components_repair_state = CreateStateReply(
+        rmad::RmadState::kComponentsRepair, rmad::RMAD_ERROR_OK);
+    rmad::ComponentsRepairState::ComponentRepairStatus* component =
+        components_repair_state.mutable_state()
+            ->mutable_components_repair()
+            ->add_components();
+    component->set_component(rmad::RmadComponent::RMAD_COMPONENT_CAMERA);
+    component->set_repair_status(
+        rmad::ComponentsRepairState::ComponentRepairStatus::
+            RMAD_REPAIR_STATUS_UNKNOWN);
+    // Set up fake disable RSU state.
+    rmad::GetStateReply wp_disable_rsu_state =
+        CreateStateReply(rmad::RmadState::kWpDisableRsu, rmad::RMAD_ERROR_OK);
+    wp_disable_rsu_state.mutable_state()
+        ->mutable_wp_disable_rsu()
+        ->set_allocated_challenge_code(new std::string(rsu_challenge_code));
+    wp_disable_rsu_state.mutable_state()
+        ->mutable_wp_disable_rsu()
+        ->set_allocated_hwid(new std::string(rsu_hwid));
+    wp_disable_rsu_state.mutable_state()
+        ->mutable_wp_disable_rsu()
+        ->set_allocated_challenge_url(new std::string(rsu_challenge_url));
 
-  std::vector<rmad::GetStateReply> fake_states = {
-      CreateStateReply(rmad::RmadState::kWelcome, rmad::RMAD_ERROR_OK),
-      components_repair_state,
-      CreateStateReply(rmad::RmadState::kDeviceDestination,
-                       rmad::RMAD_ERROR_OK),
-      CreateStateReply(rmad::RmadState::kWpDisableMethod, rmad::RMAD_ERROR_OK),
-      wp_disable_rsu_state,
-      CreateStateReply(rmad::RmadState::kWpDisablePhysical,
-                       rmad::RMAD_ERROR_OK),
-      CreateStateReply(rmad::RmadState::kWpDisableComplete,
-                       rmad::RMAD_ERROR_OK),
-      CreateStateReply(rmad::RmadState::kUpdateRoFirmware, rmad::RMAD_ERROR_OK),
-      CreateStateReply(rmad::RmadState::kRestock, rmad::RMAD_ERROR_OK),
-      CreateStateReply(rmad::RmadState::kUpdateDeviceInfo, rmad::RMAD_ERROR_OK),
-      // TODO(gavindodd): Add calibration states when implemented.
-      // rmad::RmadState::kCheckCalibration
-      // rmad::RmadState::kSetupCalibration
-      // rmad::RmadState::kRunCalibration
-      CreateStateReply(rmad::RmadState::kProvisionDevice, rmad::RMAD_ERROR_OK),
-      CreateStateReply(rmad::RmadState::kWpEnablePhysical, rmad::RMAD_ERROR_OK),
-      CreateStateReply(rmad::RmadState::kFinalize, rmad::RMAD_ERROR_OK),
-      CreateStateReply(rmad::RmadState::kRepairComplete, rmad::RMAD_ERROR_OK),
-  };
-  fake->SetFakeStateReplies(fake_states);
+    std::vector<rmad::GetStateReply> fake_states = {
+        CreateStateReply(rmad::RmadState::kWelcome, rmad::RMAD_ERROR_OK),
+        components_repair_state,
+        CreateStateReply(rmad::RmadState::kDeviceDestination,
+                         rmad::RMAD_ERROR_OK),
+        CreateStateReply(rmad::RmadState::kWpDisableMethod,
+                         rmad::RMAD_ERROR_OK),
+        wp_disable_rsu_state,
+        CreateStateReply(rmad::RmadState::kWpDisablePhysical,
+                         rmad::RMAD_ERROR_OK),
+        CreateStateReply(rmad::RmadState::kWpDisableComplete,
+                         rmad::RMAD_ERROR_OK),
+        CreateStateReply(rmad::RmadState::kUpdateRoFirmware,
+                         rmad::RMAD_ERROR_OK),
+        CreateStateReply(rmad::RmadState::kRestock, rmad::RMAD_ERROR_OK),
+        CreateStateReply(rmad::RmadState::kUpdateDeviceInfo,
+                         rmad::RMAD_ERROR_OK),
+        // TODO(gavindodd): Add calibration states when implemented.
+        // rmad::RmadState::kCheckCalibration
+        // rmad::RmadState::kSetupCalibration
+        // rmad::RmadState::kRunCalibration
+        CreateStateReply(rmad::RmadState::kProvisionDevice,
+                         rmad::RMAD_ERROR_OK),
+        CreateStateReply(rmad::RmadState::kWpEnablePhysical,
+                         rmad::RMAD_ERROR_OK),
+        CreateStateReply(rmad::RmadState::kFinalize, rmad::RMAD_ERROR_OK),
+        CreateStateReply(rmad::RmadState::kRepairComplete, rmad::RMAD_ERROR_OK),
+    };
+    fake->SetFakeStateReplies(fake_states);
+  }
 }
 
 FakeRmadClient::FakeRmadClient() {
@@ -155,6 +169,11 @@
 }
 FakeRmadClient::~FakeRmadClient() = default;
 
+void FakeRmadClient::CheckInRma(DBusMethodCallback<bool> callback) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE, base::BindOnce(std::move(callback), NumStates() > 0));
+}
+
 void FakeRmadClient::GetCurrentState(
     DBusMethodCallback<rmad::GetStateReply> callback) {
   if (NumStates() > 0) {
diff --git a/chromeos/dbus/rmad/fake_rmad_client.h b/chromeos/dbus/rmad/fake_rmad_client.h
index 34f8da4..93849af 100644
--- a/chromeos/dbus/rmad/fake_rmad_client.h
+++ b/chromeos/dbus/rmad/fake_rmad_client.h
@@ -24,6 +24,8 @@
   FakeRmadClient& operator=(const FakeRmadClient&) = delete;
   ~FakeRmadClient() override;
 
+  void CheckInRma(DBusMethodCallback<bool> callback) override;
+
   void GetCurrentState(
       DBusMethodCallback<rmad::GetStateReply> callback) override;
   void TransitionNextState(
diff --git a/chromeos/dbus/rmad/fake_rmad_client_unittest.cc b/chromeos/dbus/rmad/fake_rmad_client_unittest.cc
index c1bce007..9e6e7e4 100644
--- a/chromeos/dbus/rmad/fake_rmad_client_unittest.cc
+++ b/chromeos/dbus/rmad/fake_rmad_client_unittest.cc
@@ -193,6 +193,32 @@
   return reply;
 }
 
+TEST_F(FakeRmadClientTest, CheckInRma_Default_False) {
+  base::RunLoop run_loop;
+  client_->CheckInRma(
+      base::BindLambdaForTesting([&](absl::optional<bool> response) {
+        EXPECT_TRUE(response.has_value());
+        EXPECT_FALSE(*response);
+        run_loop.Quit();
+      }));
+  run_loop.RunUntilIdle();
+}
+
+TEST_F(FakeRmadClientTest, CheckInRma_WithState_True) {
+  std::vector<rmad::GetStateReply> fake_states;
+  fake_states.push_back(CreateWelcomeStateReply(rmad::RMAD_ERROR_OK));
+  fake_client_()->SetFakeStateReplies(std::move(fake_states));
+
+  base::RunLoop run_loop;
+  client_->CheckInRma(
+      base::BindLambdaForTesting([&](absl::optional<bool> response) {
+        EXPECT_TRUE(response.has_value());
+        EXPECT_TRUE(*response);
+        run_loop.Quit();
+      }));
+  run_loop.RunUntilIdle();
+}
+
 TEST_F(FakeRmadClientTest, GetCurrentState_Default_RmaNotRequired) {
   base::RunLoop run_loop;
   client_->GetCurrentState(base::BindLambdaForTesting(
diff --git a/chromeos/dbus/rmad/rmad_client.cc b/chromeos/dbus/rmad/rmad_client.cc
index 4b57989..fe9867f 100644
--- a/chromeos/dbus/rmad/rmad_client.cc
+++ b/chromeos/dbus/rmad/rmad_client.cc
@@ -22,6 +22,8 @@
  public:
   void Init(dbus::Bus* bus);
 
+  void CheckInRma(DBusMethodCallback<bool> callback) override;
+
   void GetCurrentState(
       DBusMethodCallback<rmad::GetStateReply> callback) override;
   void TransitionNextState(
@@ -44,6 +46,9 @@
   ~RmadClientImpl() override = default;
 
  private:
+  void OnCheckInRma(DBusMethodCallback<bool> callback,
+                    dbus::Response* response);
+
   template <class T>
   void OnProtoReply(DBusMethodCallback<T> callback, dbus::Response* response);
 
@@ -103,6 +108,35 @@
   }
 }
 
+void RmadClientImpl::CheckInRma(DBusMethodCallback<bool> callback) {
+  dbus::MethodCall method_call(rmad::kRmadInterfaceName,
+                               rmad::kIsRmaRequiredMethod);
+  dbus::MessageWriter writer(&method_call);
+  rmad_proxy_->CallMethod(
+      &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
+      base::BindOnce(&RmadClientImpl::OnCheckInRma,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+}
+
+void RmadClientImpl::OnCheckInRma(DBusMethodCallback<bool> callback,
+                                  dbus::Response* response) {
+  if (!response) {
+    LOG(ERROR) << "Error calling rmad function for OnCheckInRma";
+    std::move(callback).Run(false);
+    return;
+  }
+
+  dbus::MessageReader reader(response);
+  bool is_rma_required = false;
+  if (!reader.PopBool(&is_rma_required)) {
+    LOG(ERROR) << "Unable to decode response for " << response->GetMember();
+    std::move(callback).Run(false);
+    return;
+  }
+  DCHECK(!reader.HasMoreData());
+  std::move(callback).Run(is_rma_required);
+}
+
 // Called when a dbus signal is initially connected.
 void RmadClientImpl::SignalConnected(const std::string& interface_name,
                                      const std::string& signal_name,
diff --git a/chromeos/dbus/rmad/rmad_client.h b/chromeos/dbus/rmad/rmad_client.h
index 2c252f0..dfc7989 100644
--- a/chromeos/dbus/rmad/rmad_client.h
+++ b/chromeos/dbus/rmad/rmad_client.h
@@ -67,6 +67,8 @@
   // Returns the global instance which may be null if not initialized.
   static RmadClient* Get();
 
+  virtual void CheckInRma(DBusMethodCallback<bool> callback) = 0;
+
   // Asynchronously gets the current RMA state.
   // The response contains an error code and the current state of the RMA
   // process.
diff --git a/chromeos/dbus/rmad/rmad_client_unittest.cc b/chromeos/dbus/rmad/rmad_client_unittest.cc
index 4e40494..ffdb4c5 100644
--- a/chromeos/dbus/rmad/rmad_client_unittest.cc
+++ b/chromeos/dbus/rmad/rmad_client_unittest.cc
@@ -345,6 +345,46 @@
   rmad::FinalizeStatus last_finalization_progress_;
 };  // namespace chromeos
 
+TEST_F(RmadClientTest, CheckInRma_NotInRma) {
+  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
+  dbus::MessageWriter(response.get()).AppendBool(false);
+
+  response_ = response.get();
+  EXPECT_CALL(*mock_proxy_.get(),
+              DoCallMethod(HasMember(rmad::kIsRmaRequiredMethod),
+                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
+      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));
+
+  base::RunLoop run_loop;
+  client_->CheckInRma(
+      base::BindLambdaForTesting([&](absl::optional<bool> response) {
+        EXPECT_TRUE(response.has_value());
+        EXPECT_FALSE(*response);
+        run_loop.Quit();
+      }));
+  run_loop.RunUntilIdle();
+}
+
+TEST_F(RmadClientTest, CheckInRma_InRma) {
+  std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
+  dbus::MessageWriter(response.get()).AppendBool(true);
+
+  response_ = response.get();
+  EXPECT_CALL(*mock_proxy_.get(),
+              DoCallMethod(HasMember(rmad::kIsRmaRequiredMethod),
+                           dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, _))
+      .WillOnce(Invoke(this, &RmadClientTest::OnCallDbusMethod));
+
+  base::RunLoop run_loop;
+  client_->CheckInRma(
+      base::BindLambdaForTesting([&](absl::optional<bool> response) {
+        EXPECT_TRUE(response.has_value());
+        EXPECT_TRUE(*response);
+        run_loop.Quit();
+      }));
+  run_loop.RunUntilIdle();
+}
+
 TEST_F(RmadClientTest, GetCurrentState) {
   std::unique_ptr<dbus::Response> response = dbus::Response::CreateEmpty();
   rmad::GetStateReply expected_proto;
diff --git a/chromeos/services/bluetooth_config/device_cache_impl.cc b/chromeos/services/bluetooth_config/device_cache_impl.cc
index 4e2b95f..68f9195f 100644
--- a/chromeos/services/bluetooth_config/device_cache_impl.cc
+++ b/chromeos/services/bluetooth_config/device_cache_impl.cc
@@ -6,6 +6,7 @@
 
 #include <algorithm>
 
+#include "base/containers/contains.h"
 #include "chromeos/services/bluetooth_config/device_conversion_util.h"
 
 namespace chromeos {
@@ -97,7 +98,8 @@
   if (new_paired_status) {
     // Remove from unpaired list and add to paired device list.
     bool unpaired_device_list_updated = RemoveFromUnpairedDeviceList(device);
-    bool paired_device_list_updated = AttemptUpdatePairedDeviceMetadata(device);
+    bool paired_device_list_updated =
+        AttemptSetDeviceInPairedDeviceList(device);
 
     if (unpaired_device_list_updated)
       NotifyUnpairedDevicesListChanged();
@@ -109,7 +111,7 @@
   // Remove from paired list and add to unpaired device list.
   bool paired_device_list_updated = RemoveFromPairedDeviceList(device);
   bool unpaired_device_list_updated =
-      AttemptUpdateUnpairedDeviceMetadata(device);
+      AttemptSetDeviceInUnpairedDeviceList(device);
 
   if (paired_device_list_updated)
     NotifyPairedDevicesListChanged();
@@ -190,6 +192,15 @@
 
 bool DeviceCacheImpl::AttemptUpdatePairedDeviceMetadata(
     device::BluetoothDevice* device) {
+  bool device_found = base::Contains(
+      paired_devices_, device->GetIdentifier(), [](const auto& paired_device) {
+        return paired_device->device_properties->id;
+      });
+
+  // If device is not found in |paired_devices|, don't update.
+  if (!device_found)
+    return false;
+
   // Remove existing metadata about |device|.
   bool updated = RemoveFromPairedDeviceList(device);
 
@@ -236,6 +247,16 @@
 
 bool DeviceCacheImpl::AttemptUpdateUnpairedDeviceMetadata(
     device::BluetoothDevice* device) {
+  bool device_found =
+      base::Contains(unpaired_devices_, device->GetIdentifier(),
+                     [](const auto& unpaired_device) {
+                       return unpaired_device->device_properties->id;
+                     });
+
+  // If device is not found in |unpaired_devices|, don't update.
+  if (!device_found)
+    return false;
+
   // Remove existing metadata about |device|.
   bool updated = RemoveFromUnpairedDeviceList(device);
 
diff --git a/chromeos/services/bluetooth_config/device_cache_impl.h b/chromeos/services/bluetooth_config/device_cache_impl.h
index 808c1195..e0d83a9 100644
--- a/chromeos/services/bluetooth_config/device_cache_impl.h
+++ b/chromeos/services/bluetooth_config/device_cache_impl.h
@@ -93,8 +93,9 @@
   // true if the device was removed from the list.
   bool RemoveFromPairedDeviceList(device::BluetoothDevice* device);
 
-  // Attempts to add updated metadata about |device| to |paired_devices_|.
-  // Returns true if the device was updated in the list.
+  // Attempts to add updated metadata about |device| to |paired_devices_|. If
+  // |device| is not found in |paired_devices_|, no update is performed. Returns
+  // true if the device was updated in the list.
   bool AttemptUpdatePairedDeviceMetadata(device::BluetoothDevice* device);
 
   // Sorts |paired_devices_| based on connection state. This function is called
@@ -114,7 +115,8 @@
   // true if the device was removed from the list.
   bool RemoveFromUnpairedDeviceList(device::BluetoothDevice* device);
 
-  // Attempts to add updated metadata about |device| to |unpaired_devices_|.
+  // Attempts to add updated metadata about |device| to |paired_devices_|. If
+  // |device| is not found in |unpaired_devices_|, no update is performed.
   // Returns true if the device was updated in the list.
   bool AttemptUpdateUnpairedDeviceMetadata(device::BluetoothDevice* device);
 
diff --git a/chromeos/services/bluetooth_config/device_cache_impl_unittest.cc b/chromeos/services/bluetooth_config/device_cache_impl_unittest.cc
index 2ecf3ed3..204b184 100644
--- a/chromeos/services/bluetooth_config/device_cache_impl_unittest.cc
+++ b/chromeos/services/bluetooth_config/device_cache_impl_unittest.cc
@@ -162,6 +162,29 @@
     fake_device_name_manager_.SetDeviceNickname(device_id, nickname);
   }
 
+  void ForgetDevice(const std::string& device_id) {
+    // Simulates the real-life behavior of when a device is forgotten.
+    auto it = FindDevice(device_id);
+    EXPECT_TRUE(it != mock_devices_.end());
+
+    // The device should start paired.
+    EXPECT_TRUE(it->get()->IsPaired());
+
+    // DevicePairedChanged() is called first.
+    device_cache_->DevicePairedChanged(mock_adapter_.get(), it->get(),
+                                       /*new_paired_status=*/false);
+
+    // DeviceChanged() gets called twice with device->IsPaired() still true.
+    device_cache_->DeviceChanged(mock_adapter_.get(), it->get());
+    device_cache_->DeviceChanged(mock_adapter_.get(), it->get());
+
+    // The device is then removed.
+    NiceMockDevice device = std::move(*it);
+    mock_devices_.erase(it);
+
+    device_cache_->DeviceRemoved(mock_adapter_.get(), device.get());
+  }
+
   PairedDeviceList GetPairedDevices() {
     return device_cache_->GetPairedDevices();
   }
@@ -431,6 +454,25 @@
   EXPECT_EQ(mojom::DeviceType::kPhone, list[0]->device_properties->device_type);
 }
 
+TEST_F(DeviceCacheImplTest, PairedDeviceForgotten) {
+  Init();
+  EXPECT_TRUE(GetPairedDevices().empty());
+
+  // Add a paired device.
+  std::string paired_device_id;
+  AddDevice(/*paired=*/true, /*connected=*/true, &paired_device_id);
+  EXPECT_EQ(1u, GetNumPairedDeviceListObserverEvents());
+  PairedDeviceList list = GetPairedDevices();
+  EXPECT_EQ(1u, list.size());
+  EXPECT_EQ(paired_device_id, list[0]->device_properties->id);
+  EXPECT_EQ(mojom::DeviceType::kUnknown,
+            list[0]->device_properties->device_type);
+
+  ForgetDevice(paired_device_id);
+  EXPECT_EQ(2u, GetNumPairedDeviceListObserverEvents());
+  EXPECT_TRUE(GetPairedDevices().empty());
+}
+
 TEST_F(DeviceCacheImplTest, UnpairedDeviceBluetoothClassChanges) {
   Init();
   EXPECT_TRUE(GetUnpairedDevices().empty());
diff --git a/chromeos/services/libassistant/grpc/assistant_client_impl.cc b/chromeos/services/libassistant/grpc/assistant_client_impl.cc
index 3eafdd1..730b6db7 100644
--- a/chromeos/services/libassistant/grpc/assistant_client_impl.cc
+++ b/chromeos/services/libassistant/grpc/assistant_client_impl.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/bind.h"
+#include "base/callback_forward.h"
 #include "base/callback_helpers.h"
 #include "base/check.h"
 #include "base/containers/flat_set.h"
@@ -48,6 +49,15 @@
     chromeos::libassistant::StateConfig{kMaxRpcRetries,
                                         kAssistantInteractionDefaultTimeoutMs};
 
+#define LOG_GRPC_STATUS(level, status, func_name)                \
+  if (status.ok()) {                                             \
+    DVLOG(level) << func_name << " succeed with ok status.";     \
+  } else {                                                       \
+    LOG(ERROR) << func_name << " failed with a non-ok status.";  \
+    LOG(ERROR) << "Error code: " << status.error_code()          \
+               << ", error message: " << status.error_message(); \
+  }
+
 // Creates a callback for logging the request status. The callback will
 // ignore the returned response as it either doesn't contain any information
 // we need or is empty.
@@ -56,15 +66,7 @@
 GetLoggingCallback(const std::string& request_name) {
   return base::BindOnce(
       [](const std::string& request_name, const grpc::Status& status,
-         const Response& ignored) {
-        if (status.ok()) {
-          DVLOG(2) << request_name << " succeed with ok status.";
-        } else {
-          LOG(ERROR) << request_name << " failed with a non-ok status.";
-          LOG(ERROR) << "Error code: " << status.error_code()
-                     << ", error message: " << status.error_message();
-        }
-      },
+         const Response& ignored) { LOG_GRPC_STATUS(2, status, request_name); },
       request_name);
 }
 
@@ -186,6 +188,59 @@
       custom_config);
 }
 
+void AssistantClientImpl::UpdateAssistantSettings(
+    const ::assistant::ui::SettingsUiUpdate& update,
+    const std::string& user_id,
+    base::OnceCallback<void(
+        const ::assistant::api::UpdateAssistantSettingsResponse&)> on_done) {
+  using ::assistant::api::UpdateAssistantSettingsRequest;
+  using ::assistant::api::UpdateAssistantSettingsResponse;
+
+  UpdateAssistantSettingsRequest request;
+  // Sets obfuscated gaia id.
+  request.set_user_id(user_id);
+  // Sets the update to be applied to the settings.
+  *request.mutable_update_settings_ui_request()->mutable_settings_update() =
+      update;
+
+  auto cb = base::BindOnce(
+      [](base::OnceCallback<void(const UpdateAssistantSettingsResponse&)>
+             on_done,
+         const grpc::Status& status,
+         const UpdateAssistantSettingsResponse& response) {
+        LOG_GRPC_STATUS(/*level=*/2, status, "UpdateAssistantSettings")
+        std::move(on_done).Run(response);
+      },
+      std::move(on_done));
+  libassistant_client_.CallServiceMethod(request, std::move(cb),
+                                         kDefaultStateConfig);
+}
+
+void AssistantClientImpl::GetAssistantSettings(
+    const ::assistant::ui::SettingsUiSelector& selector,
+    const std::string& user_id,
+    base::OnceCallback<
+        void(const ::assistant::api::GetAssistantSettingsResponse&)> on_done) {
+  using ::assistant::api::GetAssistantSettingsRequest;
+  using ::assistant::api::GetAssistantSettingsResponse;
+
+  GetAssistantSettingsRequest request;
+  *request.mutable_get_settings_ui_request()->mutable_selector() = selector;
+  request.set_user_id(user_id);
+
+  auto cb = base::BindOnce(
+      [](base::OnceCallback<void(const GetAssistantSettingsResponse&)> on_done,
+         const grpc::Status& status,
+         const GetAssistantSettingsResponse& response) {
+        LOG_GRPC_STATUS(/*level=*/2, status, "GetAssistantSettings")
+        std::move(on_done).Run(response);
+      },
+      std::move(on_done));
+
+  libassistant_client_.CallServiceMethod(request, std::move(cb),
+                                         kDefaultStateConfig);
+}
+
 void AssistantClientImpl::AddTimeToTimer(const std::string& id,
                                          const base::TimeDelta& duration) {
   ::assistant::api::AddTimeToTimerRequest request;
diff --git a/chromeos/services/libassistant/grpc/assistant_client_impl.h b/chromeos/services/libassistant/grpc/assistant_client_impl.h
index 9303807..8a12266 100644
--- a/chromeos/services/libassistant/grpc/assistant_client_impl.h
+++ b/chromeos/services/libassistant/grpc/assistant_client_impl.h
@@ -52,6 +52,18 @@
   void SetAuthenticationInfo(const AuthTokens& tokens) override;
   void SetInternalOptions(const std::string& locale,
                           bool spoken_feedback_enabled) override;
+  void UpdateAssistantSettings(
+      const ::assistant::ui::SettingsUiUpdate& update,
+      const std::string& user_id,
+      base::OnceCallback<void(
+          const ::assistant::api::UpdateAssistantSettingsResponse&)> on_done)
+      override;
+  void GetAssistantSettings(
+      const ::assistant::ui::SettingsUiSelector& selector,
+      const std::string& use_id,
+      base::OnceCallback<
+          void(const ::assistant::api::GetAssistantSettingsResponse&)> on_done)
+      override;
 
   // Timer related:
   void AddTimeToTimer(const std::string& id,
diff --git a/chromeos/services/libassistant/grpc/grpc_libassistant_client.cc b/chromeos/services/libassistant/grpc/grpc_libassistant_client.cc
index 2612f0f..7bc46fe 100644
--- a/chromeos/services/libassistant/grpc/grpc_libassistant_client.cc
+++ b/chromeos/services/libassistant/grpc/grpc_libassistant_client.cc
@@ -89,6 +89,22 @@
 
 template <>
 std::string
+GetLibassistGrpcMethodName<::assistant::api::UpdateAssistantSettingsRequest>() {
+  // Updates assistant settings on the server.
+  return chromeos::assistant::GetLibassistGrpcMethodName(
+      "ConfigSettingsService", "UpdateAssistantSettings");
+}
+
+template <>
+std::string
+GetLibassistGrpcMethodName<::assistant::api::GetAssistantSettingsRequest>() {
+  // Returns assistant settings from the server.
+  return chromeos::assistant::GetLibassistGrpcMethodName(
+      "ConfigSettingsService", "GetAssistantSettings");
+}
+
+template <>
+std::string
 GetLibassistGrpcMethodName<::assistant::api::AddTimeToTimerRequest>() {
   return chromeos::assistant::GetLibassistGrpcMethodName("AlarmTimerService",
                                                          "AddTimeToTimer");
diff --git a/chromeos/services/libassistant/grpc/utils/settings_utils.cc b/chromeos/services/libassistant/grpc/utils/settings_utils.cc
index e0fecdd42..e02af6a 100644
--- a/chromeos/services/libassistant/grpc/utils/settings_utils.cc
+++ b/chromeos/services/libassistant/grpc/utils/settings_utils.cc
@@ -115,8 +115,7 @@
 
   if (response_details.status() ==
       ::assistant::api::ResponseDetails_Status_SUCCESS) {
-    DCHECK(!response_details.has_error_message() &&
-           response.has_update_settings_ui_response() &&
+    DCHECK(response.has_update_settings_ui_response() &&
            response.update_settings_ui_response().has_update_result());
 
     // Upon success, returns a serialized proto |SettingsUiUpdateResult|.
diff --git a/chromeos/services/libassistant/settings_controller.cc b/chromeos/services/libassistant/settings_controller.cc
index eb88c08f..42b91e2b 100644
--- a/chromeos/services/libassistant/settings_controller.cc
+++ b/chromeos/services/libassistant/settings_controller.cc
@@ -321,18 +321,18 @@
   }
 }
 
-void SettingsController::OnAssistantClientCreated(
+void SettingsController::OnAssistantClientStarted(
     AssistantClient* assistant_client) {
   assistant_client_ = assistant_client;
 
   // Note we do not enable the device settings updater here, as it requires
-  // Libassistant to be started.
+  // Libassistant to be fully ready.
   UpdateAuthenticationTokens(authentication_tokens_);
   UpdateInternalOptions(locale_, spoken_feedback_enabled_, dark_mode_enabled_);
   UpdateListeningEnabled(listening_enabled_);
 }
 
-void SettingsController::OnAssistantClientStarted(
+void SettingsController::OnAssistantClientRunning(
     AssistantClient* assistant_client) {
   device_settings_updater_ =
       std::make_unique<DeviceSettingsUpdater>(this, assistant_client);
diff --git a/chromeos/services/libassistant/settings_controller.h b/chromeos/services/libassistant/settings_controller.h
index f42f5a8..f4871241 100644
--- a/chromeos/services/libassistant/settings_controller.h
+++ b/chromeos/services/libassistant/settings_controller.h
@@ -42,8 +42,8 @@
                       UpdateSettingsCallback callback) override;
 
   // AssistantClientObserver:
-  void OnAssistantClientCreated(AssistantClient* assistant_client) override;
   void OnAssistantClientStarted(AssistantClient* assistant_client) override;
+  void OnAssistantClientRunning(AssistantClient* assistant_client) override;
   void OnDestroyingAssistantClient(AssistantClient* assistant_client) override;
 
  private:
diff --git a/chromeos/services/libassistant/settings_controller_unittest.cc b/chromeos/services/libassistant/settings_controller_unittest.cc
index a267f58..ea25367 100644
--- a/chromeos/services/libassistant/settings_controller_unittest.cc
+++ b/chromeos/services/libassistant/settings_controller_unittest.cc
@@ -77,23 +77,23 @@
 
   SettingsController& controller() { return controller_; }
 
-  void CreateLibassistant() {
-    controller().OnAssistantClientCreated(assistant_client_.get());
-  }
-
   void StartLibassistant() {
     controller().OnAssistantClientStarted(assistant_client_.get());
   }
 
+  void RunningLibassistant() {
+    controller().OnAssistantClientRunning(assistant_client_.get());
+  }
+
   void DestroyLibassistant() {
     controller().OnDestroyingAssistantClient(assistant_client_.get());
 
     Init();
   }
 
-  void CreateAndStartLibassistant() {
-    CreateLibassistant();
+  void StartAndRunningLibassistant() {
     StartLibassistant();
+    RunningLibassistant();
   }
 
   void Init() {
@@ -131,7 +131,7 @@
 
 TEST_F(AssistantSettingsControllerTest,
        ShouldNotCrashAfterDestroyingLibassistant) {
-  CreateAndStartLibassistant();
+  StartLibassistant();
   DestroyLibassistant();
 
   controller().SetAuthenticationTokens({});
@@ -158,11 +158,11 @@
   EXPECT_NO_CALLS(assistant_client_mock(), SetInternalOptions);
   EXPECT_NO_CALLS(assistant_client_mock(), UpdateAssistantSettings);
   EXPECT_NO_CALLS(assistant_client_mock(), SetAuthenticationInfo);
-  CreateAndStartLibassistant();
+  StartLibassistant();
 }
 
 TEST_F(AssistantSettingsControllerTest, ShouldSetLocale) {
-  CreateLibassistant();
+  StartLibassistant();
 
   EXPECT_CALL(assistant_client_mock(), SetLocaleOverride("locale"));
 
@@ -172,7 +172,7 @@
 TEST_F(AssistantSettingsControllerTest,
        ShouldUseDefaultLocaleIfSettingToEmptyString) {
   const std::string default_locale = icu::Locale::getDefault().getName();
-  CreateLibassistant();
+  StartLibassistant();
 
   EXPECT_CALL(assistant_client_mock(), SetLocaleOverride(default_locale));
 
@@ -181,7 +181,7 @@
 
 TEST_F(AssistantSettingsControllerTest,
        ShouldNotSetInternalOptionsWhenLocaleIsNotSet) {
-  CreateLibassistant();
+  StartLibassistant();
 
   EXPECT_NO_CALLS(assistant_client_mock(), SetInternalOptions);
   EXPECT_NO_CALLS(assistant_client_mock(), SetDeviceAttributes);
@@ -193,7 +193,7 @@
 TEST_F(AssistantSettingsControllerTest,
        ShouldNotSetInternalOptionsWhenSpokenFeedbackEnabledIsNotSet) {
   IGNORE_CALLS(assistant_client_mock(), SetLocaleOverride);
-  CreateLibassistant();
+  StartLibassistant();
 
   EXPECT_NO_CALLS(assistant_client_mock(), SetInternalOptions);
   EXPECT_NO_CALLS(assistant_client_mock(), SetDeviceAttributes);
@@ -205,7 +205,7 @@
 TEST_F(AssistantSettingsControllerTest,
        ShouldNotSetInternalOptionsWhenDarkModeEnabledIsNotSet) {
   IGNORE_CALLS(assistant_client_mock(), SetLocaleOverride);
-  CreateLibassistant();
+  StartLibassistant();
 
   EXPECT_NO_CALLS(assistant_client_mock(), SetInternalOptions);
   EXPECT_NO_CALLS(assistant_client_mock(), SetDeviceAttributes);
@@ -219,7 +219,7 @@
   IGNORE_CALLS(assistant_client_mock(), SetLocaleOverride);
   controller().SetSpokenFeedbackEnabled(true);
   controller().SetDarkModeEnabled(false);
-  CreateLibassistant();
+  StartLibassistant();
 
   EXPECT_CALL(assistant_client_mock(), SetInternalOptions);
 
@@ -231,7 +231,7 @@
   IGNORE_CALLS(assistant_client_mock(), SetLocaleOverride);
   controller().SetLocale("locale");
   controller().SetDarkModeEnabled(false);
-  CreateLibassistant();
+  StartLibassistant();
 
   EXPECT_CALL(assistant_client_mock(), SetInternalOptions);
 
@@ -243,7 +243,7 @@
   IGNORE_CALLS(assistant_client_mock(), SetLocaleOverride);
   controller().SetLocale("locale");
   controller().SetSpokenFeedbackEnabled(true);
-  CreateLibassistant();
+  StartLibassistant();
 
   EXPECT_CALL(assistant_client_mock(), SetInternalOptions);
   EXPECT_CALL(assistant_client_mock(), SetDeviceAttributes);
@@ -252,7 +252,7 @@
 }
 
 TEST_F(AssistantSettingsControllerTest,
-       ShouldSetInternalOptionsAndLocaleWhenLibassistantIsCreated) {
+       ShouldSetInternalOptionsAndLocaleWhenLibassistantIsStarted) {
   controller().SetLocale("locale");
   controller().SetSpokenFeedbackEnabled(true);
   controller().SetDarkModeEnabled(false);
@@ -260,13 +260,13 @@
   EXPECT_CALL(assistant_client_mock(), SetLocaleOverride);
   EXPECT_CALL(assistant_client_mock(), SetInternalOptions);
 
-  CreateLibassistant();
+  StartLibassistant();
 }
 
 TEST_F(AssistantSettingsControllerTest,
        ShouldNotSetDeviceOptionsWhenLocaleIsNotSet) {
   IGNORE_CALLS(assistant_client_mock(), SetLocaleOverride);
-  CreateAndStartLibassistant();
+  StartLibassistant();
 
   EXPECT_NO_CALLS(assistant_client_mock(), UpdateAssistantSettings);
 
@@ -276,7 +276,7 @@
 TEST_F(AssistantSettingsControllerTest,
        ShouldNotSetDeviceOptionsWhenHotwordEnabledIsNotSet) {
   IGNORE_CALLS(assistant_client_mock(), SetLocaleOverride);
-  CreateAndStartLibassistant();
+  StartLibassistant();
 
   EXPECT_NO_CALLS(assistant_client_mock(), UpdateAssistantSettings);
 
@@ -286,7 +286,7 @@
 TEST_F(AssistantSettingsControllerTest,
        ShouldSetDeviceOptionsWhenLocaleIsUpdated) {
   IGNORE_CALLS(assistant_client_mock(), SetLocaleOverride);
-  CreateAndStartLibassistant();
+  StartAndRunningLibassistant();
   controller().SetHotwordEnabled(true);
 
   EXPECT_CALL(assistant_client_mock(), UpdateAssistantSettings);
@@ -297,7 +297,7 @@
 TEST_F(AssistantSettingsControllerTest,
        ShouldSetDeviceOptionsWhenHotwordEnabledIsUpdated) {
   IGNORE_CALLS(assistant_client_mock(), SetLocaleOverride);
-  CreateAndStartLibassistant();
+  StartAndRunningLibassistant();
   controller().SetLocale("locale");
 
   EXPECT_CALL(assistant_client_mock(), UpdateAssistantSettings);
@@ -306,22 +306,21 @@
 }
 
 TEST_F(AssistantSettingsControllerTest,
-       ShouldSetDeviceOptionsWhenLibassistantIsStarted) {
+       ShouldSetDeviceOptionsWhenLibassistantIsRunning) {
   IGNORE_CALLS(assistant_client_mock(), SetLocaleOverride);
-  CreateLibassistant();
-
+  StartLibassistant();
   controller().SetLocale("locale");
   controller().SetHotwordEnabled(true);
 
   EXPECT_CALL(assistant_client_mock(), UpdateAssistantSettings);
 
-  StartLibassistant();
+  RunningLibassistant();
 }
 
 TEST_F(AssistantSettingsControllerTest, ShouldSetAuthenticationTokens) {
   const AuthTokens expected = {{"user", "token"}};
 
-  CreateLibassistant();
+  StartLibassistant();
 
   EXPECT_CALL(assistant_client_mock(), SetAuthenticationInfo(expected));
 
@@ -330,7 +329,7 @@
 }
 
 TEST_F(AssistantSettingsControllerTest,
-       ShouldSetAuthenticationTokensWhenLibassistantIsCreated) {
+       ShouldSetAuthenticationTokensWhenLibassistantIsStarted) {
   const AuthTokens expected = {{"user", "token"}};
 
   controller().SetAuthenticationTokens(
@@ -338,12 +337,12 @@
 
   EXPECT_CALL(assistant_client_mock(), SetAuthenticationInfo(expected));
 
-  CreateLibassistant();
+  StartLibassistant();
 }
 
 TEST_F(AssistantSettingsControllerTest,
        ShouldSupportEmptyAuthenticationTokenList) {
-  CreateLibassistant();
+  StartLibassistant();
 
   const AuthTokens expected = {};
   EXPECT_CALL(assistant_client_mock(), SetAuthenticationInfo(expected));
@@ -352,7 +351,7 @@
 }
 
 TEST_F(AssistantSettingsControllerTest, ShouldSetListeningEnabled) {
-  CreateLibassistant();
+  StartLibassistant();
 
   EXPECT_CALL(assistant_client_mock(), EnableListening(true));
 
@@ -360,12 +359,12 @@
 }
 
 TEST_F(AssistantSettingsControllerTest,
-       ShouldSetListeningEnabledWhenLibassistantIsCreated) {
+       ShouldSetListeningEnabledWhenLibassistantIsStarted) {
   controller().SetListeningEnabled(false);
 
   EXPECT_CALL(assistant_client_mock(), EnableListening(false));
 
-  CreateLibassistant();
+  StartLibassistant();
 }
 
 TEST_F(AssistantSettingsControllerTest,
@@ -379,7 +378,7 @@
 
 TEST_F(AssistantSettingsControllerTest,
        GetSettingsShouldCallCallbackIfLibassistantIsStopped) {
-  CreateAndStartLibassistant();
+  StartLibassistant();
 
   base::MockCallback<SettingsController::GetSettingsCallback> callback;
   controller().GetSettings("selector", /*include_header=*/false, callback.Get());
@@ -400,7 +399,7 @@
 TEST_F(AssistantSettingsControllerTest,
        UpdateSettingsShouldCallCallbackIfLibassistantIsStopped) {
   IGNORE_CALLS(assistant_client_mock(), UpdateAssistantSettings);
-  CreateAndStartLibassistant();
+  StartLibassistant();
 
   base::MockCallback<SettingsController::UpdateSettingsCallback> callback;
   controller().UpdateSettings("selector", callback.Get());
@@ -411,7 +410,7 @@
 
 TEST_F(AssistantSettingsControllerTest,
        ShouldInvokeGetAssistantSettingsWhenGetSettingsCalled) {
-  CreateAndStartLibassistant();
+  StartLibassistant();
 
   EXPECT_CALL(assistant_client_mock(), GetAssistantSettings);
 
@@ -423,7 +422,7 @@
 
 TEST_F(AssistantSettingsControllerTest,
        ShouldInvokeUpdateAssistantSettingsWhenUpdateSettingsCalled) {
-  CreateAndStartLibassistant();
+  StartLibassistant();
 
   EXPECT_CALL(assistant_client_mock(), UpdateAssistantSettings);
 
diff --git a/chromeos/services/multidevice_setup/BUILD.gn b/chromeos/services/multidevice_setup/BUILD.gn
index 2b37158..684f4349 100644
--- a/chromeos/services/multidevice_setup/BUILD.gn
+++ b/chromeos/services/multidevice_setup/BUILD.gn
@@ -21,6 +21,9 @@
     "feature_state_manager.h",
     "feature_state_manager_impl.cc",
     "feature_state_manager_impl.h",
+    "global_state_feature_manager.h",
+    "global_state_feature_manager_impl.cc",
+    "global_state_feature_manager_impl.h",
     "grandfathered_easy_unlock_host_disabler.cc",
     "grandfathered_easy_unlock_host_disabler.h",
     "host_backend_delegate.cc",
@@ -50,9 +53,8 @@
     "privileged_host_device_setter_base.h",
     "privileged_host_device_setter_impl.cc",
     "privileged_host_device_setter_impl.h",
-    "wifi_sync_feature_manager.h",
-    "wifi_sync_feature_manager_impl.cc",
-    "wifi_sync_feature_manager_impl.h",
+    "wifi_sync_notification_controller.cc",
+    "wifi_sync_notification_controller.h",
   ]
 
   deps = [
@@ -102,6 +104,8 @@
     "fake_feature_state_manager.h",
     "fake_feature_state_observer.cc",
     "fake_feature_state_observer.h",
+    "fake_global_state_feature_manager.cc",
+    "fake_global_state_feature_manager.h",
     "fake_host_backend_delegate.cc",
     "fake_host_backend_delegate.h",
     "fake_host_device_timestamp_manager.cc",
@@ -112,8 +116,6 @@
     "fake_host_status_provider.h",
     "fake_host_verifier.cc",
     "fake_host_verifier.h",
-    "fake_wifi_sync_feature_manager.cc",
-    "fake_wifi_sync_feature_manager.h",
   ]
 
   deps = [
@@ -134,6 +136,7 @@
     "android_sms_app_installing_status_observer_unittest.cc",
     "eligible_host_devices_provider_impl_unittest.cc",
     "feature_state_manager_impl_unittest.cc",
+    "global_state_feature_manager_impl_unittest.cc",
     "grandfathered_easy_unlock_host_disabler_unittest.cc",
     "host_backend_delegate_impl_unittest.cc",
     "host_device_timestamp_manager_impl_unittest.cc",
@@ -142,7 +145,7 @@
     "multidevice_setup_impl_unittest.cc",
     "multidevice_setup_service_unittest.cc",
     "privileged_host_device_setter_impl_unittest.cc",
-    "wifi_sync_feature_manager_impl_unittest.cc",
+    "wifi_sync_notification_controller_unittest.cc",
   ]
 
   deps = [
diff --git a/chromeos/services/multidevice_setup/account_status_change_delegate_notifier.h b/chromeos/services/multidevice_setup/account_status_change_delegate_notifier.h
index 2772216..5275af4 100644
--- a/chromeos/services/multidevice_setup/account_status_change_delegate_notifier.h
+++ b/chromeos/services/multidevice_setup/account_status_change_delegate_notifier.h
@@ -45,8 +45,8 @@
   friend class MultiDeviceSetupImpl;
   friend class MultiDeviceSetupAccountStatusChangeDelegateNotifierTest;
   friend class MultiDeviceSetupImplTest;
-  friend class WifiSyncFeatureManagerImpl;
-  friend class MultiDeviceSetupWifiSyncFeatureManagerImplTest;
+  friend class MultiDeviceSetupWifiSyncNotificationControllerTest;
+  friend class WifiSyncNotificationController;
 
   void FlushForTesting();
 
diff --git a/chromeos/services/multidevice_setup/fake_global_state_feature_manager.cc b/chromeos/services/multidevice_setup/fake_global_state_feature_manager.cc
new file mode 100644
index 0000000..c19d7a8
--- /dev/null
+++ b/chromeos/services/multidevice_setup/fake_global_state_feature_manager.cc
@@ -0,0 +1,26 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/multidevice_setup/fake_global_state_feature_manager.h"
+
+namespace chromeos {
+
+namespace multidevice_setup {
+
+FakeGlobalStateFeatureManager::FakeGlobalStateFeatureManager()
+    : GlobalStateFeatureManager() {}
+
+FakeGlobalStateFeatureManager::~FakeGlobalStateFeatureManager() = default;
+
+void FakeGlobalStateFeatureManager::SetIsFeatureEnabled(bool enabled) {
+  is_feature_enabled_ = enabled;
+}
+
+bool FakeGlobalStateFeatureManager::IsFeatureEnabled() {
+  return is_feature_enabled_;
+}
+
+}  // namespace multidevice_setup
+
+}  // namespace chromeos
diff --git a/chromeos/services/multidevice_setup/fake_global_state_feature_manager.h b/chromeos/services/multidevice_setup/fake_global_state_feature_manager.h
new file mode 100644
index 0000000..9c7d799
--- /dev/null
+++ b/chromeos/services/multidevice_setup/fake_global_state_feature_manager.h
@@ -0,0 +1,31 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_MULTIDEVICE_SETUP_FAKE_GLOBAL_STATE_FEATURE_MANAGER_H_
+#define CHROMEOS_SERVICES_MULTIDEVICE_SETUP_FAKE_GLOBAL_STATE_FEATURE_MANAGER_H_
+
+#include "chromeos/services/multidevice_setup/global_state_feature_manager.h"
+
+namespace chromeos {
+
+namespace multidevice_setup {
+
+// Test GlobalStateFeatureManager implementation.
+class FakeGlobalStateFeatureManager : public GlobalStateFeatureManager {
+ public:
+  FakeGlobalStateFeatureManager();
+  ~FakeGlobalStateFeatureManager() override;
+
+  // GlobalStateFeatureManager:
+  void SetIsFeatureEnabled(bool enabled) override;
+  bool IsFeatureEnabled() override;
+
+ private:
+  bool is_feature_enabled_ = false;
+};
+
+}  // namespace multidevice_setup
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_MULTIDEVICE_SETUP_FAKE_GLOBAL_STATE_FEATURE_MANAGER_H_
diff --git a/chromeos/services/multidevice_setup/fake_wifi_sync_feature_manager.cc b/chromeos/services/multidevice_setup/fake_wifi_sync_feature_manager.cc
deleted file mode 100644
index 353d6b2..0000000
--- a/chromeos/services/multidevice_setup/fake_wifi_sync_feature_manager.cc
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromeos/services/multidevice_setup/fake_wifi_sync_feature_manager.h"
-
-namespace chromeos {
-
-namespace multidevice_setup {
-
-FakeWifiSyncFeatureManager::FakeWifiSyncFeatureManager()
-    : WifiSyncFeatureManager() {}
-
-FakeWifiSyncFeatureManager::~FakeWifiSyncFeatureManager() = default;
-
-void FakeWifiSyncFeatureManager::SetIsWifiSyncEnabled(bool enabled) {
-  is_wifi_sync_enabled_ = enabled;
-}
-
-bool FakeWifiSyncFeatureManager::IsWifiSyncEnabled() {
-  return is_wifi_sync_enabled_;
-}
-
-}  // namespace multidevice_setup
-
-}  // namespace chromeos
diff --git a/chromeos/services/multidevice_setup/fake_wifi_sync_feature_manager.h b/chromeos/services/multidevice_setup/fake_wifi_sync_feature_manager.h
deleted file mode 100644
index 1eb4684..0000000
--- a/chromeos/services/multidevice_setup/fake_wifi_sync_feature_manager.h
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_SERVICES_MULTIDEVICE_SETUP_FAKE_WIFI_SYNC_FEATURE_MANAGER_H_
-#define CHROMEOS_SERVICES_MULTIDEVICE_SETUP_FAKE_WIFI_SYNC_FEATURE_MANAGER_H_
-
-#include "chromeos/components/multidevice/remote_device_ref.h"
-#include "chromeos/services/multidevice_setup/wifi_sync_feature_manager.h"
-
-namespace chromeos {
-
-namespace multidevice_setup {
-
-// Test WifiSyncFeatureManager implementation.
-class FakeWifiSyncFeatureManager : public WifiSyncFeatureManager {
- public:
-  FakeWifiSyncFeatureManager();
-  ~FakeWifiSyncFeatureManager() override;
-
-  // WifiSyncFeatureManager:
-  void SetIsWifiSyncEnabled(bool enabled) override;
-  bool IsWifiSyncEnabled() override;
-
- private:
-  bool is_wifi_sync_enabled_ = false;
-};
-
-}  // namespace multidevice_setup
-}  // namespace chromeos
-
-#endif  // CHROMEOS_SERVICES_MULTIDEVICE_SETUP_FAKE_WIFI_SYNC_FEATURE_MANAGER_H_
diff --git a/chromeos/services/multidevice_setup/feature_state_manager_impl.cc b/chromeos/services/multidevice_setup/feature_state_manager_impl.cc
index ad48998..bc1273f 100644
--- a/chromeos/services/multidevice_setup/feature_state_manager_impl.cc
+++ b/chromeos/services/multidevice_setup/feature_state_manager_impl.cc
@@ -9,6 +9,7 @@
 #include "ash/constants/ash_features.h"
 #include "base/bind.h"
 #include "base/containers/contains.h"
+#include "base/containers/flat_map.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_functions.h"
@@ -16,8 +17,8 @@
 #include "chromeos/components/multidevice/logging/logging.h"
 #include "chromeos/components/multidevice/remote_device_ref.h"
 #include "chromeos/components/multidevice/software_feature.h"
+#include "chromeos/services/multidevice_setup/global_state_feature_manager.h"
 #include "chromeos/services/multidevice_setup/public/cpp/prefs.h"
-#include "chromeos/services/multidevice_setup/wifi_sync_feature_manager.h"
 #include "components/prefs/pref_service.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -198,18 +199,19 @@
     HostStatusProvider* host_status_provider,
     device_sync::DeviceSyncClient* device_sync_client,
     AndroidSmsPairingStateTracker* android_sms_pairing_state_tracker,
-    WifiSyncFeatureManager* wifi_sync_feature_manager,
+    const base::flat_map<mojom::Feature, GlobalStateFeatureManager*>&
+        global_state_feature_managers,
     bool is_secondary_user) {
   if (test_factory_) {
     return test_factory_->CreateInstance(
         pref_service, host_status_provider, device_sync_client,
-        android_sms_pairing_state_tracker, wifi_sync_feature_manager,
+        android_sms_pairing_state_tracker, global_state_feature_managers,
         is_secondary_user);
   }
 
   return base::WrapUnique(new FeatureStateManagerImpl(
       pref_service, host_status_provider, device_sync_client,
-      android_sms_pairing_state_tracker, wifi_sync_feature_manager,
+      android_sms_pairing_state_tracker, global_state_feature_managers,
       is_secondary_user));
 }
 
@@ -226,13 +228,14 @@
     HostStatusProvider* host_status_provider,
     device_sync::DeviceSyncClient* device_sync_client,
     AndroidSmsPairingStateTracker* android_sms_pairing_state_tracker,
-    WifiSyncFeatureManager* wifi_sync_feature_manager,
+    const base::flat_map<mojom::Feature, GlobalStateFeatureManager*>&
+        global_state_feature_managers,
     bool is_secondary_user)
     : pref_service_(pref_service),
       host_status_provider_(host_status_provider),
       device_sync_client_(device_sync_client),
       android_sms_pairing_state_tracker_(android_sms_pairing_state_tracker),
-      wifi_sync_feature_manager_(wifi_sync_feature_manager),
+      global_state_feature_managers_(global_state_feature_managers),
       is_secondary_user_(is_secondary_user),
       feature_to_enabled_pref_name_map_(GenerateFeatureToEnabledPrefNameMap()),
       feature_to_allowed_pref_name_map_(GenerateFeatureToAllowedPrefNameMap()),
@@ -287,13 +290,11 @@
 void FeatureStateManagerImpl::PerformSetFeatureEnabledState(
     mojom::Feature feature,
     bool enabled) {
-  // Wifi sync enabled toggle acts as a global toggle which applies to all
-  // Chrome OS devices and a connected Android phone.
-  if (feature == mojom::Feature::kWifiSync) {
-    wifi_sync_feature_manager_->SetIsWifiSyncEnabled(enabled);
+  if (global_state_feature_managers_.contains(feature)) {
+    global_state_feature_managers_.at(feature)->SetIsFeatureEnabled(enabled);
     // Need to manually trigger UpdateFeatureStateCache since changes to
-    // wifi sync is not observed by |registrar_| and will not invoke
-    // OnPrefValueChanged
+    // this global feature state is not observed by |registrar_| and will not
+    // invoke OnPrefValueChanged
     UpdateFeatureStateCache(true /* notify_observers_of_changes */);
     return;
   }
@@ -502,10 +503,10 @@
                  multidevice::SoftwareFeatureState::kEnabled;
     }
 
-    // Edge Case: Wifi Sync is considered activated on host when the state is
-    // kSupported or kEnabled. kEnabled/kSupported correspond to on/off for Wifi
-    // Sync Host.
-    return (feature == mojom::Feature::kWifiSync &&
+    // Edge Case: features with global states are considered activated on host
+    // when the state is kSupported or kEnabled. kEnabled/kSupported correspond
+    // to on/off for the global host state.
+    return (global_state_feature_managers_.contains(feature) &&
             feature_state == multidevice::SoftwareFeatureState::kSupported);
   }
 
@@ -530,11 +531,8 @@
 
 mojom::FeatureState FeatureStateManagerImpl::GetEnabledOrDisabledState(
     mojom::Feature feature) {
-  // WifiSyncFeatureManager is the source of truth for Wifi Sync enabled state.
-  // It is a global setting that applies to all synced Chrome OS devices and a
-  // connected Android phone.
-  if (feature == mojom::Feature::kWifiSync) {
-    return (wifi_sync_feature_manager_->IsWifiSyncEnabled()
+  if (global_state_feature_managers_.contains(feature)) {
+    return (global_state_feature_managers_.at(feature)->IsFeatureEnabled()
                 ? mojom::FeatureState::kEnabledByUser
                 : mojom::FeatureState::kDisabledByUser);
   }
diff --git a/chromeos/services/multidevice_setup/feature_state_manager_impl.h b/chromeos/services/multidevice_setup/feature_state_manager_impl.h
index 06d260a..99168b0 100644
--- a/chromeos/services/multidevice_setup/feature_state_manager_impl.h
+++ b/chromeos/services/multidevice_setup/feature_state_manager_impl.h
@@ -9,10 +9,10 @@
 #include "base/timer/timer.h"
 #include "chromeos/services/device_sync/public/cpp/device_sync_client.h"
 #include "chromeos/services/multidevice_setup/feature_state_manager.h"
+#include "chromeos/services/multidevice_setup/global_state_feature_manager.h"
 #include "chromeos/services/multidevice_setup/host_status_provider.h"
 #include "chromeos/services/multidevice_setup/public/cpp/android_sms_pairing_state_tracker.h"
 #include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
-#include "chromeos/services/multidevice_setup/wifi_sync_feature_manager.h"
 #include "components/prefs/pref_change_registrar.h"
 
 class PrefService;
@@ -38,7 +38,8 @@
         HostStatusProvider* host_status_provider,
         device_sync::DeviceSyncClient* device_sync_client,
         AndroidSmsPairingStateTracker* android_sms_pairing_state_tracker,
-        WifiSyncFeatureManager* wifi_sync_feature_manager,
+        const base::flat_map<mojom::Feature, GlobalStateFeatureManager*>&
+            global_state_feature_managers,
         bool is_secondary_user);
     static void SetFactoryForTesting(Factory* test_factory);
 
@@ -49,7 +50,8 @@
         HostStatusProvider* host_status_provider,
         device_sync::DeviceSyncClient* device_sync_client,
         AndroidSmsPairingStateTracker* android_sms_pairing_state_tracker,
-        WifiSyncFeatureManager* wifi_sync_feature_manager,
+        const base::flat_map<mojom::Feature, GlobalStateFeatureManager*>&
+            global_state_feature_managers,
         bool is_secondary_user) = 0;
 
    private:
@@ -67,7 +69,8 @@
       HostStatusProvider* host_status_provider,
       device_sync::DeviceSyncClient* device_sync_client,
       AndroidSmsPairingStateTracker* android_sms_pairing_state_tracker,
-      WifiSyncFeatureManager* wifi_sync_feature_manager,
+      const base::flat_map<mojom::Feature, GlobalStateFeatureManager*>&
+          global_state_feature_managers,
       bool is_secondary_user);
 
   // FeatureStateManager:
@@ -107,7 +110,8 @@
   HostStatusProvider* host_status_provider_;
   device_sync::DeviceSyncClient* device_sync_client_;
   AndroidSmsPairingStateTracker* android_sms_pairing_state_tracker_;
-  WifiSyncFeatureManager* wifi_sync_feature_manager_;
+  const base::flat_map<mojom::Feature, GlobalStateFeatureManager*>
+      global_state_feature_managers_;
 
   // Certain features may be unavailable to secondary users logged into a
   // Chromebook. Currently, such features include PhoneHub and its subfeatures.
diff --git a/chromeos/services/multidevice_setup/feature_state_manager_impl_unittest.cc b/chromeos/services/multidevice_setup/feature_state_manager_impl_unittest.cc
index d6812d7..921d0ad9 100644
--- a/chromeos/services/multidevice_setup/feature_state_manager_impl_unittest.cc
+++ b/chromeos/services/multidevice_setup/feature_state_manager_impl_unittest.cc
@@ -14,8 +14,8 @@
 #include "chromeos/components/multidevice/remote_device_test_util.h"
 #include "chromeos/services/device_sync/public/cpp/fake_device_sync_client.h"
 #include "chromeos/services/multidevice_setup/fake_feature_state_manager.h"
+#include "chromeos/services/multidevice_setup/fake_global_state_feature_manager.h"
 #include "chromeos/services/multidevice_setup/fake_host_status_provider.h"
-#include "chromeos/services/multidevice_setup/fake_wifi_sync_feature_manager.h"
 #include "chromeos/services/multidevice_setup/public/cpp/fake_android_sms_pairing_state_tracker.h"
 #include "chromeos/services/multidevice_setup/public/cpp/prefs.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
@@ -121,14 +121,25 @@
         std::make_unique<FakeAndroidSmsPairingStateTracker>();
     fake_android_sms_pairing_state_tracker_->SetPairingComplete(true);
 
-    fake_wifi_sync_feature_manager_ =
-        std::make_unique<FakeWifiSyncFeatureManager>();
+    fake_global_state_feature_managers_.emplace(
+        mojom::Feature::kPhoneHubCameraRoll,
+        std::make_unique<FakeGlobalStateFeatureManager>());
+    fake_global_state_feature_managers_.emplace(
+        mojom::Feature::kWifiSync,
+        std::make_unique<FakeGlobalStateFeatureManager>());
 
     manager_ = FeatureStateManagerImpl::Factory::Create(
         test_pref_service_.get(), fake_host_status_provider_.get(),
         fake_device_sync_client_.get(),
         fake_android_sms_pairing_state_tracker_.get(),
-        fake_wifi_sync_feature_manager_.get(), is_secondary_user);
+        {{mojom::Feature::kPhoneHubCameraRoll,
+          fake_global_state_feature_managers_
+              .at(mojom::Feature::kPhoneHubCameraRoll)
+              .get()},
+         {mojom::Feature::kWifiSync,
+          fake_global_state_feature_managers_.at(mojom::Feature::kWifiSync)
+              .get()}},
+        is_secondary_user);
 
     fake_observer_ = std::make_unique<FakeFeatureStateManagerObserver>();
     manager_->AddObserver(fake_observer_.get());
@@ -247,8 +258,11 @@
   }
 
   FeatureStateManager* manager() { return manager_.get(); }
-  FakeWifiSyncFeatureManager* wifi_sync_manager() {
-    return fake_wifi_sync_feature_manager_.get();
+
+  base::flat_map<mojom::Feature,
+                 std::unique_ptr<FakeGlobalStateFeatureManager>>&
+  global_state_feature_managers() {
+    return fake_global_state_feature_managers_;
   }
 
  private:
@@ -263,7 +277,8 @@
   std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
   std::unique_ptr<FakeAndroidSmsPairingStateTracker>
       fake_android_sms_pairing_state_tracker_;
-  std::unique_ptr<FakeWifiSyncFeatureManager> fake_wifi_sync_feature_manager_;
+  base::flat_map<mojom::Feature, std::unique_ptr<FakeGlobalStateFeatureManager>>
+      fake_global_state_feature_managers_;
 
   std::unique_ptr<FakeFeatureStateManagerObserver> fake_observer_;
 
@@ -801,13 +816,16 @@
 
 TEST_F(MultiDeviceSetupFeatureStateManagerImplTest, CameraRoll) {
   SetupFeatureStateManager();
-
-  TryAllUnverifiedHostStatesAndVerifyFeatureState(mojom::Feature::kEche);
+  // Set the initial global state to disabled, so that the camera roll feature
+  // state will be |kDisabledByUser| when it becomes supported on both the host
+  // and client devices.
+  global_state_feature_managers()[mojom::Feature::kPhoneHubCameraRoll]
+      ->SetIsFeatureEnabled(false);
+  TryAllUnverifiedHostStatesAndVerifyFeatureState(
+      mojom::Feature::kPhoneHubCameraRoll);
 
   SetVerifiedHost();
   VerifyFeatureState(mojom::FeatureState::kNotSupportedByChromebook,
-                     mojom::Feature::kPhoneHub);
-  VerifyFeatureState(mojom::FeatureState::kNotSupportedByChromebook,
                      mojom::Feature::kPhoneHubCameraRoll);
 
   SetSoftwareFeatureState(true /* use_local_device */,
@@ -818,53 +836,64 @@
       multidevice::SoftwareFeature::kPhoneHubCameraRollClient,
       multidevice::SoftwareFeatureState::kSupported);
   VerifyFeatureState(mojom::FeatureState::kNotSupportedByPhone,
-                     mojom::Feature::kPhoneHub);
-  VerifyFeatureState(mojom::FeatureState::kNotSupportedByPhone,
                      mojom::Feature::kPhoneHubCameraRoll);
 
   SetSoftwareFeatureState(false /* use_local_device */,
                           multidevice::SoftwareFeature::kPhoneHubHost,
                           multidevice::SoftwareFeatureState::kEnabled);
+  VerifyFeatureState(mojom::FeatureState::kNotSupportedByPhone,
+                     mojom::Feature::kPhoneHubCameraRoll);
+
+  // Camera Roll is considered disabled if it host is supported but disabled.
+  SetSoftwareFeatureState(false /* use_local_device */,
+                          multidevice::SoftwareFeature::kPhoneHubCameraRollHost,
+                          multidevice::SoftwareFeatureState::kSupported);
+  VerifyFeatureState(mojom::FeatureState::kDisabledByUser,
+                     mojom::Feature::kPhoneHubCameraRoll);
+
+  // Camera Roll does not automatically enable with Phone Hub.
+  test_pref_service()->SetBoolean(kPhoneHubEnabledPrefName, true);
+  VerifyFeatureState(mojom::FeatureState::kDisabledByUser,
+                     mojom::Feature::kPhoneHubCameraRoll);
+
+  // The GlobalStateFeatureManager should be updated when the host state changes
+  // to |kEnabled|. It will then update the feature state to |kEnabledByUser|.
+  global_state_feature_managers()[mojom::Feature::kPhoneHubCameraRoll]
+      ->SetIsFeatureEnabled(true);
   SetSoftwareFeatureState(false /* use_local_device */,
                           multidevice::SoftwareFeature::kPhoneHubCameraRollHost,
                           multidevice::SoftwareFeatureState::kEnabled);
-  VerifyFeatureState(mojom::FeatureState::kDisabledByUser,
-                     mojom::Feature::kPhoneHub);
-  VerifyFeatureState(mojom::FeatureState::kDisabledByUser,
+  VerifyFeatureState(mojom::FeatureState::kEnabledByUser,
                      mojom::Feature::kPhoneHubCameraRoll);
 
-  // Camera Roll does not automatically enable with Phone Hub
-  test_pref_service()->SetBoolean(kPhoneHubEnabledPrefName, true);
-  VerifyFeatureState(mojom::FeatureState::kEnabledByUser,
-                     mojom::Feature::kPhoneHub);
+  // Simulate user toggling the camera roll state, and verify that the
+  // GlobalStateFeatureManager was updated accordingly.
+  manager()->SetFeatureEnabledState(mojom::Feature::kPhoneHubCameraRoll, false);
   VerifyFeatureState(mojom::FeatureState::kDisabledByUser,
                      mojom::Feature::kPhoneHubCameraRoll);
+  EXPECT_FALSE(
+      global_state_feature_managers()[mojom::Feature::kPhoneHubCameraRoll]
+          ->IsFeatureEnabled());
 
-  // Enable Camera Roll
-  test_pref_service()->SetBoolean(kPhoneHubCameraRollEnabledPrefName, true);
-  VerifyFeatureState(mojom::FeatureState::kEnabledByUser,
-                     mojom::Feature::kPhoneHub);
+  manager()->SetFeatureEnabledState(mojom::Feature::kPhoneHubCameraRoll, true);
   VerifyFeatureState(mojom::FeatureState::kEnabledByUser,
                      mojom::Feature::kPhoneHubCameraRoll);
+  EXPECT_TRUE(
+      global_state_feature_managers()[mojom::Feature::kPhoneHubCameraRoll]
+          ->IsFeatureEnabled());
 
   // Camera Roll is automatically disabled when Phone Hub is disabled
   test_pref_service()->SetBoolean(kPhoneHubEnabledPrefName, false);
-  VerifyFeatureState(mojom::FeatureState::kDisabledByUser,
-                     mojom::Feature::kPhoneHub);
   VerifyFeatureState(mojom::FeatureState::kUnavailableTopLevelFeatureDisabled,
                      mojom::Feature::kPhoneHubCameraRoll);
 
   // Camera Roll restores its previous state when Phone Hub is enabled
   test_pref_service()->SetBoolean(kPhoneHubEnabledPrefName, true);
   VerifyFeatureState(mojom::FeatureState::kEnabledByUser,
-                     mojom::Feature::kPhoneHub);
-  VerifyFeatureState(mojom::FeatureState::kEnabledByUser,
                      mojom::Feature::kPhoneHubCameraRoll);
 
   // Prohibiting Camera Roll does not prohibit Phone Hub
   test_pref_service()->SetBoolean(kPhoneHubCameraRollAllowedPrefName, false);
-  VerifyFeatureState(mojom::FeatureState::kEnabledByUser,
-                     mojom::Feature::kPhoneHub);
   VerifyFeatureState(mojom::FeatureState::kProhibitedByPolicy,
                      mojom::Feature::kPhoneHubCameraRoll);
 
@@ -872,14 +901,16 @@
   test_pref_service()->SetBoolean(kPhoneHubCameraRollAllowedPrefName, true);
   test_pref_service()->SetBoolean(kPhoneHubAllowedPrefName, false);
   VerifyFeatureState(mojom::FeatureState::kProhibitedByPolicy,
-                     mojom::Feature::kPhoneHub);
-  VerifyFeatureState(mojom::FeatureState::kProhibitedByPolicy,
                      mojom::Feature::kPhoneHubCameraRoll);
 }
 
 TEST_F(MultiDeviceSetupFeatureStateManagerImplTest, WifiSync) {
   SetupFeatureStateManager();
-
+  // Set the initial global state to disabled, so that the wifi sync feature
+  // state will be |kDisabledByUser| when it becomes supported on both the host
+  // and client devices.
+  global_state_feature_managers()[mojom::Feature::kWifiSync]
+      ->SetIsFeatureEnabled(false);
   TryAllUnverifiedHostStatesAndVerifyFeatureState(mojom::Feature::kWifiSync);
 
   SetVerifiedHost();
@@ -889,32 +920,40 @@
   SetSoftwareFeatureState(true /* use_local_device */,
                           multidevice::SoftwareFeature::kWifiSyncClient,
                           multidevice::SoftwareFeatureState::kSupported);
-  wifi_sync_manager()->SetIsWifiSyncEnabled(true);
   VerifyFeatureState(mojom::FeatureState::kDisabledByUser,
                      mojom::Feature::kWifiSync);
   VerifyFeatureStateChange(1u /* expected_index */, mojom::Feature::kWifiSync,
                            mojom::FeatureState::kDisabledByUser);
 
+  // The GlobalStateFeatureManager should be updated when the host state changes
+  // to |kEnabled|. It will then update the feature state to |kEnabledByUser|.
+  global_state_feature_managers()[mojom::Feature::kWifiSync]
+      ->SetIsFeatureEnabled(true);
   SetSoftwareFeatureState(false /* use_local_device */,
                           multidevice::SoftwareFeature::kWifiSyncHost,
                           multidevice::SoftwareFeatureState::kEnabled);
-  wifi_sync_manager()->SetIsWifiSyncEnabled(false);
   VerifyFeatureState(mojom::FeatureState::kEnabledByUser,
                      mojom::Feature::kWifiSync);
   VerifyFeatureStateChange(2u /* expected_index */, mojom::Feature::kWifiSync,
                            mojom::FeatureState::kEnabledByUser);
 
+  // Simulate user toggling the wifi sync state, and verify that the
+  // GlobalStateFeatureManager was updated accordingly.
   manager()->SetFeatureEnabledState(mojom::Feature::kWifiSync, false);
   VerifyFeatureState(mojom::FeatureState::kDisabledByUser,
                      mojom::Feature::kWifiSync);
   VerifyFeatureStateChange(3u /* expected_index */, mojom::Feature::kWifiSync,
                            mojom::FeatureState::kDisabledByUser);
+  EXPECT_FALSE(global_state_feature_managers()[mojom::Feature::kWifiSync]
+                   ->IsFeatureEnabled());
 
   manager()->SetFeatureEnabledState(mojom::Feature::kWifiSync, true);
   VerifyFeatureState(mojom::FeatureState::kEnabledByUser,
                      mojom::Feature::kWifiSync);
   VerifyFeatureStateChange(4u /* expected_index */, mojom::Feature::kWifiSync,
                            mojom::FeatureState::kEnabledByUser);
+  EXPECT_TRUE(global_state_feature_managers()[mojom::Feature::kWifiSync]
+                  ->IsFeatureEnabled());
 
   MakeBetterTogetherSuiteDisabledByUser();
   VerifyFeatureState(mojom::FeatureState::kUnavailableSuiteDisabled,
diff --git a/chromeos/services/multidevice_setup/global_state_feature_manager.h b/chromeos/services/multidevice_setup/global_state_feature_manager.h
new file mode 100644
index 0000000..a1ab943
--- /dev/null
+++ b/chromeos/services/multidevice_setup/global_state_feature_manager.h
@@ -0,0 +1,40 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_MULTIDEVICE_SETUP_GLOBAL_STATE_FEATURE_MANAGER_H_
+#define CHROMEOS_SERVICES_MULTIDEVICE_SETUP_GLOBAL_STATE_FEATURE_MANAGER_H_
+
+namespace chromeos {
+
+namespace multidevice_setup {
+
+// Manages the state of a feature whose host enabled state is synced across
+// all connected devices. The global host enabled state will be used to
+// determine whether the feature is enabled on this client device.
+// Such features are different from normal features where the host enabled state
+// is solely set by the host device, and a local enabled state is used to
+// control whetether the feature is enabled on this client device.
+class GlobalStateFeatureManager {
+ public:
+  virtual ~GlobalStateFeatureManager() = default;
+  GlobalStateFeatureManager(const GlobalStateFeatureManager&) = delete;
+  GlobalStateFeatureManager& operator=(const GlobalStateFeatureManager&) =
+      delete;
+
+  // Attempts to enable/disable the managed feature on the backend for the host
+  // device that is synced at the time SetIsFeatureEnabled is called.
+  virtual void SetIsFeatureEnabled(bool enabled) = 0;
+
+  // Returns whether the managed feature is enabled/disabled.
+  virtual bool IsFeatureEnabled() = 0;
+
+ protected:
+  GlobalStateFeatureManager() = default;
+};
+
+}  // namespace multidevice_setup
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_MULTIDEVICE_SETUP_GLOBAL_STATE_FEATURE_MANAGER_H_
diff --git a/chromeos/services/multidevice_setup/global_state_feature_manager_impl.cc b/chromeos/services/multidevice_setup/global_state_feature_manager_impl.cc
new file mode 100644
index 0000000..a8b237d
--- /dev/null
+++ b/chromeos/services/multidevice_setup/global_state_feature_manager_impl.cc
@@ -0,0 +1,389 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/multidevice_setup/global_state_feature_manager_impl.h"
+
+#include <memory>
+#include <sstream>
+#include <string>
+#include <utility>
+
+#include "ash/constants/ash_features.h"
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "chromeos/components/multidevice/logging/logging.h"
+#include "chromeos/components/multidevice/remote_device_ref.h"
+#include "chromeos/components/multidevice/software_feature.h"
+#include "chromeos/components/multidevice/software_feature_state.h"
+#include "chromeos/services/device_sync/feature_status_change.h"
+#include "chromeos/services/device_sync/public/cpp/device_sync_client.h"
+#include "chromeos/services/device_sync/public/mojom/device_sync.mojom.h"
+#include "chromeos/services/multidevice_setup/host_status_provider.h"
+#include "chromeos/services/multidevice_setup/public/cpp/prefs.h"
+#include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
+#include "chromeos/services/multidevice_setup/wifi_sync_notification_controller.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+
+namespace chromeos {
+
+namespace multidevice_setup {
+
+namespace {
+
+const char kPhoneHubCameraRollPendingStatePrefName[] =
+    "multidevice_setup.phone_hub_camera_roll_pending_state";
+// This pref name is left in a legacy format to maintain compatibility.
+const char kWifiSyncPendingStatePrefName[] =
+    "multidevice_setup.pending_set_wifi_sync_enabled_request";
+
+// The number of minutes to wait before retrying a failed attempt.
+const int kNumMinutesBetweenRetries = 5;
+
+}  // namespace
+
+// static
+GlobalStateFeatureManagerImpl::Factory*
+    GlobalStateFeatureManagerImpl::Factory::test_factory_ = nullptr;
+
+// static
+std::unique_ptr<GlobalStateFeatureManager>
+GlobalStateFeatureManagerImpl::Factory::Create(
+    Option option,
+    HostStatusProvider* host_status_provider,
+    PrefService* pref_service,
+    device_sync::DeviceSyncClient* device_sync_client,
+    std::unique_ptr<base::OneShotTimer> timer) {
+  if (test_factory_) {
+    return test_factory_->CreateInstance(option, host_status_provider,
+                                         pref_service, device_sync_client,
+                                         std::move(timer));
+  }
+
+  mojom::Feature managed_feature;
+  multidevice::SoftwareFeature managed_host_feature;
+  std::string pending_state_pref_name;
+  switch (option) {
+    case Option::kPhoneHubCameraRoll:
+      managed_feature = mojom::Feature::kPhoneHubCameraRoll;
+      managed_host_feature =
+          multidevice::SoftwareFeature::kPhoneHubCameraRollHost;
+      pending_state_pref_name = kPhoneHubCameraRollPendingStatePrefName;
+      break;
+    case Option::kWifiSync:
+      managed_feature = mojom::Feature::kWifiSync;
+      managed_host_feature = multidevice::SoftwareFeature::kWifiSyncHost;
+      pending_state_pref_name = kWifiSyncPendingStatePrefName;
+      break;
+  }
+  return base::WrapUnique(new GlobalStateFeatureManagerImpl(
+      managed_feature, managed_host_feature, pending_state_pref_name,
+      host_status_provider, pref_service, device_sync_client,
+      std::move(timer)));
+}
+
+// static
+void GlobalStateFeatureManagerImpl::Factory::SetFactoryForTesting(
+    Factory* test_factory) {
+  test_factory_ = test_factory;
+}
+
+GlobalStateFeatureManagerImpl::Factory::~Factory() = default;
+
+void GlobalStateFeatureManagerImpl::RegisterPrefs(
+    PrefRegistrySimple* registry) {
+  registry->RegisterIntegerPref(kPhoneHubCameraRollPendingStatePrefName,
+                                static_cast<int>(PendingState::kPendingNone));
+  registry->RegisterIntegerPref(kWifiSyncPendingStatePrefName,
+                                static_cast<int>(PendingState::kPendingNone));
+}
+
+GlobalStateFeatureManagerImpl::GlobalStateFeatureManagerImpl(
+    mojom::Feature managed_feature,
+    multidevice::SoftwareFeature managed_host_feature,
+    const std::string& pending_state_pref_name,
+    HostStatusProvider* host_status_provider,
+    PrefService* pref_service,
+    device_sync::DeviceSyncClient* device_sync_client,
+    std::unique_ptr<base::OneShotTimer> timer)
+    : GlobalStateFeatureManager(),
+      managed_feature_(managed_feature),
+      managed_host_feature_(managed_host_feature),
+      pending_state_pref_name_(pending_state_pref_name),
+      host_status_provider_(host_status_provider),
+      pref_service_(pref_service),
+      device_sync_client_(device_sync_client),
+      timer_(std::move(timer)) {
+  host_status_provider_->AddObserver(this);
+  device_sync_client_->AddObserver(this);
+
+  if (GetCurrentState() == CurrentState::kValidPendingRequest) {
+    AttemptSetHostStateNetworkRequest(false /* is_retry */);
+  }
+
+  if (ShouldEnableOnVerify()) {
+    ProcessEnableOnVerifyAttempt();
+  }
+}
+
+GlobalStateFeatureManagerImpl::~GlobalStateFeatureManagerImpl() {
+  host_status_provider_->RemoveObserver(this);
+  device_sync_client_->RemoveObserver(this);
+}
+
+void GlobalStateFeatureManagerImpl::OnHostStatusChange(
+    const HostStatusProvider::HostStatusWithDevice& host_status_with_device) {
+  if (GetCurrentState() == CurrentState::kNoVerifiedHost &&
+      !ShouldEnableOnVerify()) {
+    ResetPendingNetworkRequest();
+  }
+
+  if (ShouldAttemptToEnableAfterHostVerified()) {
+    SetPendingState(PendingState::kSetPendingEnableOnVerify);
+    return;
+  }
+
+  if (ShouldEnableOnVerify()) {
+    ProcessEnableOnVerifyAttempt();
+  }
+}
+
+void GlobalStateFeatureManagerImpl::OnNewDevicesSynced() {
+  if (GetCurrentState() != CurrentState::kValidPendingRequest &&
+      !ShouldEnableOnVerify()) {
+    ResetPendingNetworkRequest();
+  }
+}
+
+void GlobalStateFeatureManagerImpl::SetIsFeatureEnabled(bool enabled) {
+  if (GetCurrentState() == CurrentState::kNoVerifiedHost) {
+    PA_LOG(ERROR) << "GlobalStateFeatureManagerImpl::SetIsFeatureEnabled:  "
+                     "Network request "
+                     "not attempted because there is No Verified Host";
+    ResetPendingNetworkRequest();
+    return;
+  }
+
+  SetPendingState(enabled ? PendingState::kPendingEnable
+                          : PendingState::kPendingDisable);
+
+  if (managed_feature_ == mojom::Feature::kWifiSync)
+    pref_service_->SetBoolean(kCanShowWifiSyncAnnouncementPrefName, false);
+
+  // Stop timer since new attempt is started.
+  timer_->Stop();
+  AttemptSetHostStateNetworkRequest(false /* is_retry */);
+}
+
+bool GlobalStateFeatureManagerImpl::IsFeatureEnabled() {
+  CurrentState current_state = GetCurrentState();
+  if (current_state == CurrentState::kNoVerifiedHost) {
+    return false;
+  }
+
+  if (current_state == CurrentState::kValidPendingRequest) {
+    return GetPendingState() == PendingState::kPendingEnable;
+  }
+
+  return host_status_provider_->GetHostWithStatus()
+             .host_device()
+             ->GetSoftwareFeatureState(managed_host_feature_) ==
+         multidevice::SoftwareFeatureState::kEnabled;
+}
+
+void GlobalStateFeatureManagerImpl::ResetPendingNetworkRequest() {
+  SetPendingState(PendingState::kPendingNone);
+  timer_->Stop();
+}
+
+void GlobalStateFeatureManagerImpl::SetPendingState(
+    PendingState pending_state) {
+  pref_service_->SetInteger(pending_state_pref_name_,
+                            static_cast<int>(pending_state));
+}
+
+GlobalStateFeatureManagerImpl::PendingState
+GlobalStateFeatureManagerImpl::GetPendingState() {
+  return static_cast<PendingState>(
+      pref_service_->GetInteger(pending_state_pref_name_));
+}
+
+GlobalStateFeatureManagerImpl::CurrentState
+GlobalStateFeatureManagerImpl::GetCurrentState() {
+  if (host_status_provider_->GetHostWithStatus().host_status() !=
+      mojom::HostStatus::kHostVerified) {
+    return CurrentState::kNoVerifiedHost;
+  }
+
+  PendingState pending_state = GetPendingState();
+
+  // If the pending request is kSetPendingEnableOnVerify then there is no
+  // actionable pending equest. The pending request will be changed from
+  // kSetPendingEnableOnVerify when the host has been verified.
+  if (pending_state == PendingState::kPendingNone ||
+      pending_state == PendingState::kSetPendingEnableOnVerify) {
+    return CurrentState::kNoPendingRequest;
+  }
+
+  bool enabled_on_host =
+      (host_status_provider_->GetHostWithStatus()
+           .host_device()
+           ->GetSoftwareFeatureState(managed_host_feature_) ==
+       multidevice::SoftwareFeatureState::kEnabled);
+  bool pending_enabled = (pending_state == PendingState::kPendingEnable);
+
+  if (pending_enabled == enabled_on_host) {
+    return CurrentState::kPendingMatchesBackend;
+  }
+
+  return CurrentState::kValidPendingRequest;
+}
+
+void GlobalStateFeatureManagerImpl::AttemptSetHostStateNetworkRequest(
+    bool is_retry) {
+  if (network_request_in_flight_) {
+    return;
+  }
+
+  bool pending_enabled = (GetPendingState() == PendingState::kPendingEnable);
+
+  PA_LOG(INFO) << "GlobalStateFeatureManagerImpl::" << __func__ << ": "
+               << (is_retry ? "Retrying attempt" : "Attempting") << " to "
+               << (pending_enabled ? "enable" : "disable");
+
+  network_request_in_flight_ = true;
+  multidevice::RemoteDeviceRef host_device =
+      *host_status_provider_->GetHostWithStatus().host_device();
+
+  if (features::ShouldUseV1DeviceSync()) {
+    // Even if the |device_to_set| has a non-trivial Instance ID, we still
+    // invoke the v1 DeviceSync RPC to set the feature state. This ensures that
+    // GmsCore will be notified of the change regardless of what version of
+    // DeviceSync it is running. The v1 and v2 RPCs to change feature states
+    // ultimately update the same backend database entry. Note: The
+    // RemoteDeviceProvider guarantees that every device will have a public key
+    // while v1 DeviceSync is enabled.
+    device_sync_client_->SetSoftwareFeatureState(
+        host_device.public_key(), managed_host_feature_,
+        pending_enabled /* enabled */, pending_enabled /* is_exclusive */,
+        base::BindOnce(&GlobalStateFeatureManagerImpl::
+                           OnSetHostStateNetworkRequestFinished,
+                       weak_ptr_factory_.GetWeakPtr(), pending_enabled));
+  } else {
+    device_sync_client_->SetFeatureStatus(
+        host_device.instance_id(), managed_host_feature_,
+        pending_enabled ? device_sync::FeatureStatusChange::kEnableExclusively
+                        : device_sync::FeatureStatusChange::kDisable,
+        base::BindOnce(&GlobalStateFeatureManagerImpl::
+                           OnSetHostStateNetworkRequestFinished,
+                       weak_ptr_factory_.GetWeakPtr(), pending_enabled));
+  }
+}
+
+void GlobalStateFeatureManagerImpl::OnSetHostStateNetworkRequestFinished(
+    bool attempted_to_enable,
+    device_sync::mojom::NetworkRequestResult result_code) {
+  network_request_in_flight_ = false;
+
+  bool success =
+      (result_code == device_sync::mojom::NetworkRequestResult::kSuccess);
+
+  std::stringstream ss;
+  ss << "GlobalStateFeatureManagerImpl::" << __func__ << ": Completed with "
+     << (success ? "success" : "failure")
+     << ". Attempted to enable: " << (attempted_to_enable ? "true" : "false");
+
+  if (success) {
+    PA_LOG(VERBOSE) << ss.str();
+    PendingState pending_state = GetPendingState();
+    if (pending_state == PendingState::kPendingNone) {
+      return;
+    }
+
+    bool pending_enabled = (pending_state == PendingState::kPendingEnable);
+    // If the network request was successful but there is still a pending
+    // network request then trigger a network request immediately. This could
+    // happen if there was a second attempt to set the backend while the first
+    // one was still in progress.
+    if (attempted_to_enable != pending_enabled) {
+      AttemptSetHostStateNetworkRequest(false /* is_retry */);
+    }
+    return;
+  }
+
+  ss << ", Error code: " << result_code;
+  PA_LOG(WARNING) << ss.str();
+
+  // If the network request failed and there is still a pending network request,
+  // schedule a retry.
+  if (GetCurrentState() == CurrentState::kValidPendingRequest) {
+    timer_->Start(
+        FROM_HERE, base::Minutes(kNumMinutesBetweenRetries),
+        base::BindOnce(
+            &GlobalStateFeatureManagerImpl::AttemptSetHostStateNetworkRequest,
+            base::Unretained(this), true /* is_retry */));
+  }
+}
+
+bool GlobalStateFeatureManagerImpl::ShouldEnableOnVerify() {
+  return (GetPendingState() == PendingState::kSetPendingEnableOnVerify);
+}
+
+void GlobalStateFeatureManagerImpl::ProcessEnableOnVerifyAttempt() {
+  mojom::HostStatus host_status =
+      host_status_provider_->GetHostWithStatus().host_status();
+
+  // If host is not set.
+  if (host_status == mojom::HostStatus::kNoEligibleHosts ||
+      host_status == mojom::HostStatus::kEligibleHostExistsButNoHostSet) {
+    ResetPendingNetworkRequest();
+    return;
+  }
+
+  if (host_status != mojom::HostStatus::kHostVerified) {
+    return;
+  }
+
+  if (IsFeatureEnabled()) {
+    ResetPendingNetworkRequest();
+    return;
+  }
+
+  SetIsFeatureEnabled(true);
+}
+
+bool GlobalStateFeatureManagerImpl::ShouldAttemptToEnableAfterHostVerified() {
+  HostStatusProvider::HostStatusWithDevice host_status_with_device =
+      host_status_provider_->GetHostWithStatus();
+
+  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
+  // setup flow has been completed on the local device.
+  if (host_status_with_device.host_status() !=
+      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation) {
+    return false;
+  }
+
+  // Check if the feature is prohibited by enterprise policy or if feature flag
+  // is disabled.
+  if (!IsFeatureAllowed(managed_feature_, pref_service_)) {
+    return false;
+  }
+
+  // Check if the feature is supported by host device.
+  if (host_status_with_device.host_device()->GetSoftwareFeatureState(
+          managed_host_feature_) ==
+      multidevice::SoftwareFeatureState::kNotSupported) {
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace multidevice_setup
+
+}  // namespace chromeos
diff --git a/chromeos/services/multidevice_setup/global_state_feature_manager_impl.h b/chromeos/services/multidevice_setup/global_state_feature_manager_impl.h
new file mode 100644
index 0000000..4d6cd14
--- /dev/null
+++ b/chromeos/services/multidevice_setup/global_state_feature_manager_impl.h
@@ -0,0 +1,168 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_MULTIDEVICE_SETUP_GLOBAL_STATE_FEATURE_MANAGER_IMPL_H_
+#define CHROMEOS_SERVICES_MULTIDEVICE_SETUP_GLOBAL_STATE_FEATURE_MANAGER_IMPL_H_
+
+#include <memory>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+#include "chromeos/services/device_sync/public/cpp/device_sync_client.h"
+#include "chromeos/services/device_sync/public/mojom/device_sync.mojom.h"
+#include "chromeos/services/multidevice_setup/global_state_feature_manager.h"
+#include "chromeos/services/multidevice_setup/host_status_provider.h"
+#include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace chromeos {
+
+namespace multidevice {
+enum class SoftwareFeature;
+}
+
+namespace multidevice_setup {
+
+// Concrete GlobalStateFeatureManager implementation, which utilizes
+// DeviceSyncClient to communicate with the back-end.
+//
+// This toggles the managed feature's host state between enabled/supported on
+// cryptauth for a synced phone, where the supported state is considered
+// disabled by user.
+//
+// Toggling the feature state is a global action, so it will be reflected on all
+// synced devices.
+class GlobalStateFeatureManagerImpl
+    : public GlobalStateFeatureManager,
+      public HostStatusProvider::Observer,
+      public device_sync::DeviceSyncClient::Observer {
+ public:
+  class Factory {
+   public:
+    enum class Option {
+      // Corresponds to |mojom::Feature::kPhoneHubCameraRoll|.
+      kPhoneHubCameraRoll,
+      // Corresponds to |mojom::Feature::kWifiSync|.
+      kWifiSync
+    };
+
+    static std::unique_ptr<GlobalStateFeatureManager> Create(
+        Option option,
+        HostStatusProvider* host_status_provider,
+        PrefService* pref_service,
+        device_sync::DeviceSyncClient* device_sync_client,
+        std::unique_ptr<base::OneShotTimer> timer =
+            std::make_unique<base::OneShotTimer>());
+    static void SetFactoryForTesting(Factory* test_factory);
+
+   protected:
+    virtual ~Factory();
+    virtual std::unique_ptr<GlobalStateFeatureManager> CreateInstance(
+        Option option,
+        HostStatusProvider* host_status_provider,
+        PrefService* pref_service,
+        device_sync::DeviceSyncClient* device_sync_client,
+        std::unique_ptr<base::OneShotTimer> timer) = 0;
+
+   private:
+    static Factory* test_factory_;
+  };
+
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+  ~GlobalStateFeatureManagerImpl() override;
+  GlobalStateFeatureManagerImpl(const GlobalStateFeatureManagerImpl&) = delete;
+  GlobalStateFeatureManagerImpl& operator=(
+      const GlobalStateFeatureManagerImpl&) = delete;
+
+ private:
+  GlobalStateFeatureManagerImpl(
+      mojom::Feature managed_feature,
+      multidevice::SoftwareFeature managed_host_feature,
+      const std::string& pending_state_pref_name,
+      HostStatusProvider* host_status_provider,
+      PrefService* pref_service,
+      device_sync::DeviceSyncClient* device_sync_client,
+      std::unique_ptr<base::OneShotTimer> timer);
+
+  // HostStatusProvider::Observer,
+  void OnHostStatusChange(const HostStatusProvider::HostStatusWithDevice&
+                              host_status_with_device) override;
+
+  // DeviceSyncClient::Observer:
+  void OnNewDevicesSynced() override;
+
+  // GlobalStateFeatureManager:
+
+  // Attempts to enable/disable the managed feature on the backend for the host
+  // device that is synced at the time SetIsFeatureEnabled is called.
+  //
+  // If a the request fails (e.g., the device is offline or the server is down),
+  // this object will continue to attempt the request until one of the following
+  // happens: the
+  // request succeeds, SetIsFeatureEnabled() called is with different value, or
+  // the synced host device changes.
+  //
+  // If there is already a pending request and this function is called with the
+  // same request, a retry will be attempted immediately.
+  void SetIsFeatureEnabled(bool enabled) override;
+
+  // Returns whether the managed feature is enabled/disabled. If there is a
+  // pending request to enable or disable the feature, the state that the
+  // pending request is intending to set to is returned, otherwise the state on
+  // the backend is returned.
+  bool IsFeatureEnabled() override;
+
+  // Numerical values cannot be changed because they map to integers that are
+  // stored persistently in prefs.
+  enum class PendingState {
+    kPendingNone = 0,
+    kPendingEnable = 1,
+    kPendingDisable = 2,
+    kSetPendingEnableOnVerify = 3
+  };
+
+  enum class CurrentState {
+    kNoVerifiedHost,
+    kNoPendingRequest,
+    kPendingMatchesBackend,
+    kValidPendingRequest
+  };
+
+  PendingState GetPendingState();
+  CurrentState GetCurrentState();
+
+  void ResetPendingNetworkRequest();
+  void SetPendingState(PendingState pending_state);
+  void AttemptSetHostStateNetworkRequest(bool is_retry);
+  void OnSetHostStateNetworkRequestFinished(
+      bool attempted_to_enable,
+      device_sync::mojom::NetworkRequestResult result_code);
+  bool ShouldEnableOnVerify();
+  void ProcessEnableOnVerifyAttempt();
+  bool ShouldAttemptToEnableAfterHostVerified();
+
+  // The feature being managed.
+  mojom::Feature managed_feature_;
+  // Corresponding CryptAuth host feature for |managed_feature_|.
+  multidevice::SoftwareFeature managed_host_feature_;
+  const std::string pending_state_pref_name_;
+  HostStatusProvider* host_status_provider_;
+  PrefService* pref_service_;
+  device_sync::DeviceSyncClient* device_sync_client_;
+  std::unique_ptr<base::OneShotTimer> timer_;
+
+  bool network_request_in_flight_ = false;
+
+  base::WeakPtrFactory<GlobalStateFeatureManagerImpl> weak_ptr_factory_{this};
+};
+
+}  // namespace multidevice_setup
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_MULTIDEVICE_SETUP_GLOBAL_STATE_FEATURE_MANAGER_IMPL_H_
diff --git a/chromeos/services/multidevice_setup/global_state_feature_manager_impl_unittest.cc b/chromeos/services/multidevice_setup/global_state_feature_manager_impl_unittest.cc
new file mode 100644
index 0000000..e6c399b
--- /dev/null
+++ b/chromeos/services/multidevice_setup/global_state_feature_manager_impl_unittest.cc
@@ -0,0 +1,879 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/multidevice_setup/global_state_feature_manager_impl.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "ash/constants/ash_features.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "base/timer/mock_timer.h"
+#include "chromeos/components/multidevice/remote_device_ref.h"
+#include "chromeos/components/multidevice/remote_device_test_util.h"
+#include "chromeos/components/multidevice/software_feature.h"
+#include "chromeos/components/multidevice/software_feature_state.h"
+#include "chromeos/services/device_sync/public/cpp/fake_device_sync_client.h"
+#include "chromeos/services/multidevice_setup/fake_host_status_provider.h"
+#include "chromeos/services/multidevice_setup/global_state_feature_manager.h"
+#include "chromeos/services/multidevice_setup/public/cpp/prefs.h"
+#include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
+#include "chromeos/services/multidevice_setup/wifi_sync_notification_controller.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace chromeos {
+
+namespace multidevice_setup {
+
+namespace {
+
+const GlobalStateFeatureManagerImpl::Factory::Option kTestOption =
+    GlobalStateFeatureManagerImpl::Factory::Option::kWifiSync;
+const multidevice::SoftwareFeature kTestHostFeature =
+    multidevice::SoftwareFeature::kWifiSyncHost;
+const multidevice::SoftwareFeature kTestClientFeature =
+    multidevice::SoftwareFeature::kWifiSyncClient;
+const std::string& kFeatureAllowedPrefName = kWifiSyncAllowedPrefName;
+const char kPendingStatePrefName[] =
+    "multidevice_setup.pending_set_wifi_sync_enabled_request";
+const base::Feature& kTestFeatureFlag = chromeos::features::kWifiSyncAndroid;
+
+enum PendingState {
+  kPendingNone = 0,
+  kPendingEnable = 1,
+  kPendingDisable = 2,
+  kSetPendingEnableOnVerify = 3
+};
+
+const size_t kNumTestDevices = 4;
+
+}  // namespace
+
+class MultiDeviceSetupGlobalStateFeatureManagerImplTest
+    : public ::testing::TestWithParam<bool> {
+ public:
+  MultiDeviceSetupGlobalStateFeatureManagerImplTest(
+      const MultiDeviceSetupGlobalStateFeatureManagerImplTest&) = delete;
+  MultiDeviceSetupGlobalStateFeatureManagerImplTest& operator=(
+      const MultiDeviceSetupGlobalStateFeatureManagerImplTest&) = delete;
+
+ protected:
+  MultiDeviceSetupGlobalStateFeatureManagerImplTest()
+      : test_devices_(
+            multidevice::CreateRemoteDeviceRefListForTest(kNumTestDevices)) {}
+  ~MultiDeviceSetupGlobalStateFeatureManagerImplTest() override = default;
+
+  // testing::Test:
+  void SetUp() override {
+    // Tests are run once to simulate when v1 DeviceSync is enabled and once to
+    // simulate when it is disabled, leaving only v2 DeviceSync operational. In
+    // the former case, only public keys are needed, and in the latter case,
+    // only Instance IDs are needed.
+    for (multidevice::RemoteDeviceRef device : test_devices_) {
+      if (features::ShouldUseV1DeviceSync())
+        GetMutableRemoteDevice(device)->instance_id.clear();
+      else
+        GetMutableRemoteDevice(device)->public_key.clear();
+    }
+
+    SetFeatureSupportedInDeviceSyncClient();
+
+    fake_host_status_provider_ = std::make_unique<FakeHostStatusProvider>();
+
+    test_pref_service_ =
+        std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
+    GlobalStateFeatureManagerImpl::RegisterPrefs(
+        test_pref_service_->registry());
+    WifiSyncNotificationController::RegisterPrefs(
+        test_pref_service_->registry());
+    test_pref_service_->registry()->RegisterBooleanPref(kFeatureAllowedPrefName,
+                                                        true);
+    fake_device_sync_client_ =
+        std::make_unique<device_sync::FakeDeviceSyncClient>();
+    fake_device_sync_client_->set_synced_devices(test_devices_);
+    multidevice::RemoteDeviceRef local_device =
+        multidevice::CreateRemoteDeviceRefForTest();
+    GetMutableRemoteDevice(local_device)
+        ->software_features[kTestClientFeature] =
+        multidevice::SoftwareFeatureState::kSupported;
+    fake_device_sync_client_->set_local_device_metadata(local_device);
+  }
+
+  void TearDown() override {}
+
+  void SetHostInDeviceSyncClient(
+      const absl::optional<multidevice::RemoteDeviceRef>& host_device) {
+    for (const auto& remote_device : test_devices_) {
+      bool should_be_host =
+          host_device != absl::nullopt &&
+          ((!remote_device.instance_id().empty() &&
+            host_device->instance_id() == remote_device.instance_id()) ||
+           (!remote_device.GetDeviceId().empty() &&
+            host_device->GetDeviceId() == remote_device.GetDeviceId()));
+
+      GetMutableRemoteDevice(remote_device)
+          ->software_features
+              [multidevice::SoftwareFeature::kBetterTogetherHost] =
+          should_be_host ? multidevice::SoftwareFeatureState::kEnabled
+                         : multidevice::SoftwareFeatureState::kSupported;
+    }
+    fake_device_sync_client_->NotifyNewDevicesSynced();
+  }
+
+  void SetFeatureSupportedInDeviceSyncClient() {
+    for (const auto& remote_device : test_devices_) {
+      GetMutableRemoteDevice(remote_device)
+          ->software_features[kTestHostFeature] =
+          multidevice::SoftwareFeatureState::kSupported;
+    }
+  }
+
+  void CreateDelegate(
+      const absl::optional<multidevice::RemoteDeviceRef>& initial_host,
+      int initial_pending_state = kPendingNone) {
+    SetHostInDeviceSyncClient(initial_host);
+    test_pref_service_->SetInteger(kPendingStatePrefName,
+                                   initial_pending_state);
+
+    auto mock_timer = std::make_unique<base::MockOneShotTimer>();
+    mock_timer_ = mock_timer.get();
+
+    SetHostWithStatus(initial_host);
+
+    delegate_ = GlobalStateFeatureManagerImpl::Factory::Create(
+        kTestOption, fake_host_status_provider_.get(), test_pref_service_.get(),
+        fake_device_sync_client_.get(), std::move(mock_timer));
+  }
+
+  void SetHostWithStatus(
+      const absl::optional<multidevice::RemoteDeviceRef>& host_device) {
+    mojom::HostStatus host_status =
+        (host_device == absl::nullopt ? mojom::HostStatus::kNoEligibleHosts
+                                      : mojom::HostStatus::kHostVerified);
+    fake_host_status_provider_->SetHostWithStatus(host_status, host_device);
+  }
+
+  void SetIsFeatureEnabled(bool enabled) {
+    delegate_->SetIsFeatureEnabled(enabled);
+
+    HostStatusProvider::HostStatusWithDevice host_with_status =
+        fake_host_status_provider_->GetHostWithStatus();
+    if (host_with_status.host_status() != mojom::HostStatus::kHostVerified) {
+      return;
+    }
+
+    multidevice::RemoteDeviceRef host_device = *host_with_status.host_device();
+
+    bool enabled_on_backend =
+        (host_device.GetSoftwareFeatureState(kTestHostFeature) ==
+         multidevice::SoftwareFeatureState::kEnabled);
+    bool pending_request_state_same_as_backend =
+        (enabled == enabled_on_backend);
+
+    if (pending_request_state_same_as_backend) {
+      return;
+    }
+
+    VerifyLatestSetHostNetworkRequest(host_device, enabled);
+  }
+
+  void VerifyLatestSetHostNetworkRequest(
+      const multidevice::RemoteDeviceRef expected_host,
+      bool expected_should_enable) {
+    if (features::ShouldUseV1DeviceSync()) {
+      ASSERT_FALSE(
+          fake_device_sync_client_->set_software_feature_state_inputs_queue()
+              .empty());
+      const device_sync::FakeDeviceSyncClient::SetSoftwareFeatureStateInputs&
+          inputs = fake_device_sync_client_
+                       ->set_software_feature_state_inputs_queue()
+                       .back();
+      EXPECT_EQ(expected_host.public_key(), inputs.public_key);
+      EXPECT_EQ(kTestHostFeature, inputs.software_feature);
+      EXPECT_EQ(expected_should_enable, inputs.enabled);
+      EXPECT_EQ(expected_should_enable, inputs.is_exclusive);
+      return;
+    }
+
+    // Verify inputs to SetFeatureStatus().
+    ASSERT_FALSE(
+        fake_device_sync_client_->set_feature_status_inputs_queue().empty());
+    const device_sync::FakeDeviceSyncClient::SetFeatureStatusInputs& inputs =
+        fake_device_sync_client_->set_feature_status_inputs_queue().back();
+    EXPECT_EQ(expected_host.instance_id(), inputs.device_instance_id);
+    EXPECT_EQ(kTestHostFeature, inputs.feature);
+    EXPECT_EQ(expected_should_enable
+                  ? device_sync::FeatureStatusChange::kEnableExclusively
+                  : device_sync::FeatureStatusChange::kDisable,
+              inputs.status_change);
+  }
+
+  int GetSetHostNetworkRequestCallbackQueueSize() {
+    return features::ShouldUseV1DeviceSync()
+               ? fake_device_sync_client_
+                     ->GetSetSoftwareFeatureStateInputsQueueSize()
+               : fake_device_sync_client_->GetSetFeatureStatusInputsQueueSize();
+  }
+
+  void InvokePendingSetHostNetworkRequestCallback(
+      device_sync::mojom::NetworkRequestResult result_code,
+      bool expected_to_notify_observer_and_start_retry_timer) {
+    if (features::ShouldUseV1DeviceSync()) {
+      fake_device_sync_client_->InvokePendingSetSoftwareFeatureStateCallback(
+          result_code);
+    } else {
+      fake_device_sync_client_->InvokePendingSetFeatureStatusCallback(
+          result_code);
+    }
+
+    EXPECT_EQ(expected_to_notify_observer_and_start_retry_timer,
+              mock_timer_->IsRunning());
+  }
+
+  void SetHostInDeviceSyncClient(
+      const absl::optional<multidevice::RemoteDeviceRef>& host_device,
+      bool enabled) {
+    GetMutableRemoteDevice(*host_device)->software_features[kTestHostFeature] =
+        (enabled ? multidevice::SoftwareFeatureState::kEnabled
+                 : multidevice::SoftwareFeatureState::kSupported);
+    fake_device_sync_client_->NotifyNewDevicesSynced();
+  }
+
+  void SetFeatureFlags(bool use_v1_devicesync, bool enable_feature_flag) {
+    std::vector<base::Feature> enabled_features;
+    std::vector<base::Feature> disabled_features;
+
+    // These flags have no direct effect of on the GlobalStateFeatureManager;
+    // however, v2 Enrollment and DeviceSync must be enabled before v1
+    // DeviceSync can be disabled.
+    enabled_features.push_back(chromeos::features::kCryptAuthV2Enrollment);
+    enabled_features.push_back(chromeos::features::kCryptAuthV2DeviceSync);
+
+    if (use_v1_devicesync) {
+      disabled_features.push_back(
+          chromeos::features::kDisableCryptAuthV1DeviceSync);
+    } else {
+      enabled_features.push_back(
+          chromeos::features::kDisableCryptAuthV1DeviceSync);
+    }
+
+    if (enable_feature_flag) {
+      enabled_features.push_back(kTestFeatureFlag);
+    } else {
+      disabled_features.push_back(kTestFeatureFlag);
+    }
+
+    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
+  }
+
+  FakeHostStatusProvider* fake_host_status_provider() {
+    return fake_host_status_provider_.get();
+  }
+
+  device_sync::FakeDeviceSyncClient* fake_device_sync_client() {
+    return fake_device_sync_client_.get();
+  }
+
+  base::MockOneShotTimer* mock_timer() { return mock_timer_; }
+
+  GlobalStateFeatureManager* delegate() { return delegate_.get(); }
+
+  sync_preferences::TestingPrefServiceSyncable* test_pref_service() {
+    return test_pref_service_.get();
+  }
+
+  const multidevice::RemoteDeviceRefList& test_devices() const {
+    return test_devices_;
+  }
+
+ private:
+  base::test::TaskEnvironment task_environment_;
+  multidevice::RemoteDeviceRefList test_devices_;
+
+  std::unique_ptr<FakeHostStatusProvider> fake_host_status_provider_;
+  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable>
+      test_pref_service_;
+  std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
+
+  base::MockOneShotTimer* mock_timer_;
+
+  std::unique_ptr<GlobalStateFeatureManager> delegate_;
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest, Success) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(test_devices()[0] /* initial_host */);
+
+  // Attempt to enable the feature on host device and succeed
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  SetIsFeatureEnabled(true);
+  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_TRUE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+  InvokePendingSetHostNetworkRequestCallback(
+      device_sync::mojom::NetworkRequestResult::kSuccess,
+      false /* expected_to_notify_observer_and_start_retry_timer */);
+  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
+  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kEnabled);
+
+  // Attempt to disable the feature on host device and succeed
+  SetIsFeatureEnabled(false);
+  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kEnabled);
+
+  InvokePendingSetHostNetworkRequestCallback(
+      device_sync::mojom::NetworkRequestResult::kSuccess,
+      false /* expected_to_notify_observer_and_start_retry_timer */);
+  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
+  SetHostInDeviceSyncClient(test_devices()[0], false /* enabled */);
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       NewDevicesSyncedBeforeCallback) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(test_devices()[0] /* initial_host */);
+
+  // Attempt to enable the feature on host device and succeed
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  SetIsFeatureEnabled(true);
+  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_TRUE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+  // Triggers OnNewDevicesSynced
+  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
+  // Triggers Success Callback
+  InvokePendingSetHostNetworkRequestCallback(
+      device_sync::mojom::NetworkRequestResult::kSuccess,
+      false /* expected_to_notify_observer_and_start_retry_timer */);
+  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
+
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kEnabled);
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest, Failure) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(test_devices()[0] /* initial_host */);
+
+  // Attempt to enable the feature on host device and fail
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  SetIsFeatureEnabled(true);
+  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_TRUE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  InvokePendingSetHostNetworkRequestCallback(
+      device_sync::mojom::NetworkRequestResult::kOffline,
+      true /* expected_to_notify_observer_and_start_retry_timer */);
+  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  // A retry should have been scheduled, so fire the timer to start the retry.
+  mock_timer()->Fire();
+
+  // Simulate another failure.
+  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_TRUE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  InvokePendingSetHostNetworkRequestCallback(
+      device_sync::mojom::NetworkRequestResult::kOffline,
+      true /* expected_to_notify_observer_and_start_retry_timer */);
+  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       MultipleRequests_FirstFail_ThenSucceed) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(test_devices()[0] /* initial_host */);
+
+  // Attempt to enable the feature on host device and fail
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  SetIsFeatureEnabled(true);
+  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_TRUE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  InvokePendingSetHostNetworkRequestCallback(
+      device_sync::mojom::NetworkRequestResult::kOffline,
+      true /* expected_to_notify_observer_and_start_retry_timer */);
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  // The retry timer is running; however, instead of relying on that, call
+  // SetIsFeatureEnabled() again to trigger an immediate
+  // retry without the timer.
+  SetIsFeatureEnabled(true);
+  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_TRUE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  InvokePendingSetHostNetworkRequestCallback(
+      device_sync::mojom::NetworkRequestResult::kSuccess,
+      false /* expected_to_notify_observer_and_start_retry_timer */);
+  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
+  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kEnabled);
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       PendingRequest_NoSyncedHostDevice) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(test_devices()[0] /* initial_host */);
+
+  // Attempt to enable the feature on test_device 0
+  SetIsFeatureEnabled(true);
+  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_TRUE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  // Fail to set host enabled on test_device 0
+  InvokePendingSetHostNetworkRequestCallback(
+      device_sync::mojom::NetworkRequestResult::kOffline,
+      true /* expected_to_notify_observer_and_start_retry_timer */);
+  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+  EXPECT_TRUE(mock_timer()->IsRunning());
+
+  // Remove synced device. This should remove the pending request and stop the
+  // retry timer.
+  SetHostInDeviceSyncClient(absl::nullopt);
+  SetHostWithStatus(absl::nullopt);
+  EXPECT_FALSE(mock_timer()->IsRunning());
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       InitialPendingEnableRequest_NoInitialDevice) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(absl::nullopt /* initial_host */,
+                 kPendingEnable /* initial_pending_state*/);
+
+  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       InitialPendingEnableRequest_Success) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(test_devices()[0] /* initial_host */,
+                 kPendingEnable /* initial_pending_state*/);
+
+  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_TRUE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  InvokePendingSetHostNetworkRequestCallback(
+      device_sync::mojom::NetworkRequestResult::kSuccess,
+      false /* expected_to_notify_observer_and_start_retry_timer */);
+  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
+  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kEnabled);
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       MultiplePendingRequests_EnableDisable) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(test_devices()[0] /* initial_host */);
+
+  // Attempt to enable->disable the feature without invoking any
+  // callbacks.
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  SetIsFeatureEnabled(true);
+  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_TRUE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  // The feature is already disabled on back-end so there should be no new
+  // pending request
+  SetIsFeatureEnabled(false);
+  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       PendingRequest_SyncedHostBecomesUnverified) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(test_devices()[0] /* initial_host */,
+                 kPendingEnable /* initial_pending_state */);
+
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);
+
+  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
+            kPendingNone);
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       Retrying_SyncedHostBecomesUnverified) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(test_devices()[0] /* initial_host */);
+
+  SetIsFeatureEnabled(true);
+  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_TRUE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  InvokePendingSetHostNetworkRequestCallback(
+      device_sync::mojom::NetworkRequestResult::kOffline,
+      true /* expected_to_notify_observer_and_start_retry_timer */);
+  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_TRUE(mock_timer()->IsRunning());
+
+  // Host becomes unverified, this should stop timer and clear pending request
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);
+  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
+            kPendingNone);
+  EXPECT_FALSE(mock_timer()->IsRunning());
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       FailureCallback_SyncedHostBecomesUnverified) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(test_devices()[0] /* initial_host */);
+
+  SetIsFeatureEnabled(true);
+  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_TRUE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  // Set host unverified. This should reset pending request.
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);
+  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
+            kPendingNone);
+
+  // Invoke failure callback. No retry should be scheduled.
+  InvokePendingSetHostNetworkRequestCallback(
+      device_sync::mojom::NetworkRequestResult::kOffline,
+      false /* expected_to_notify_observer_and_start_retry_timer */);
+  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_FALSE(mock_timer()->IsRunning());
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       NoVerifiedHost_AttemptToEnable) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(test_devices()[0] /* initial_host */);
+
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);
+
+  // Attempt to enable the feature on host device
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  SetIsFeatureEnabled(true);
+  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       StatusChangedOnRemoteDevice) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(test_devices()[0] /* initial_host */);
+
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  // Simulate enabled on a remote device.
+  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
+  EXPECT_TRUE(delegate()->IsFeatureEnabled());
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       SimultaneousRequests_StartOff_ToggleOnOff) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(test_devices()[0] /* initial_host */);
+
+  // Attempt to enable
+  SetIsFeatureEnabled(true);
+  // Attempt to disable
+  SetIsFeatureEnabled(false);
+
+  // Only one network request should be in flight at a time
+  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
+
+  // Successfully enable on host
+  InvokePendingSetHostNetworkRequestCallback(
+      device_sync::mojom::NetworkRequestResult::kSuccess,
+      false /* expected_to_notify_observer_and_start_retry_timer */);
+  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kEnabled);
+
+  // A new network request should be scheduled to disable
+  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
+  InvokePendingSetHostNetworkRequestCallback(
+      device_sync::mojom::NetworkRequestResult::kSuccess,
+      false /* expected_to_notify_observer_and_start_retry_timer */);
+  SetHostInDeviceSyncClient(test_devices()[0], false /* enabled */);
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       SetPendingEnableOnVerify_HostSetLocallyThenHostVerified) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(absl::nullopt /* initial_host */);
+
+  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
+  // setup flow has been completed on the local device.
+  SetHostInDeviceSyncClient(test_devices()[0]);
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
+      test_devices()[0]);
+  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
+            kSetPendingEnableOnVerify);
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kHostVerified, test_devices()[0]);
+  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
+            kPendingEnable);
+  EXPECT_TRUE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+  InvokePendingSetHostNetworkRequestCallback(
+      device_sync::mojom::NetworkRequestResult::kSuccess,
+      false /* expected_to_notify_observer_and_start_retry_timer */);
+  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
+  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
+
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kEnabled);
+}
+
+TEST_P(
+    MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+    SetPendingEnableOnVerify_HostSetLocallyThenHostSetNotVerifiedThenHostVerified) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(absl::nullopt /* initial_host */);
+
+  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
+  // setup flow has been completed on the local device.
+  SetHostInDeviceSyncClient(test_devices()[0]);
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
+      test_devices()[0]);
+  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
+            kSetPendingEnableOnVerify);
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);
+  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
+            kSetPendingEnableOnVerify);
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kHostVerified, test_devices()[0]);
+  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
+            kPendingEnable);
+  EXPECT_TRUE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+  InvokePendingSetHostNetworkRequestCallback(
+      device_sync::mojom::NetworkRequestResult::kSuccess,
+      false /* expected_to_notify_observer_and_start_retry_timer */);
+  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
+  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
+
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kEnabled);
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       SetPendingEnableOnVerify_FeatureFlagOff) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  false /* enable_feature_flag */);
+  CreateDelegate(absl::nullopt /* initial_host */);
+
+  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
+  // setup flow has been completed on the local device.
+  SetHostInDeviceSyncClient(test_devices()[0]);
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
+      test_devices()[0]);
+  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
+            kPendingNone);
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       SetPendingEnableOnVerify_FeatureNotAllowedByPolicy) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  // Disable by policy
+  test_pref_service()->SetBoolean(kFeatureAllowedPrefName, false);
+  CreateDelegate(absl::nullopt /* initial_host */);
+
+  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
+  // setup flow has been completed on the local device.
+  SetHostInDeviceSyncClient(test_devices()[0]);
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
+      test_devices()[0]);
+  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
+            kPendingNone);
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       SetPendingEnableOnVerify_FeatureNotSupportedOnHostDevice) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(absl::nullopt /* initial_host */);
+  GetMutableRemoteDevice(test_devices()[0])
+      ->software_features[kTestHostFeature] =
+      multidevice::SoftwareFeatureState::kNotSupported;
+
+  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
+  // setup flow has been completed on the local device.
+  SetHostInDeviceSyncClient(test_devices()[0]);
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
+      test_devices()[0]);
+  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
+            kPendingNone);
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       SetPendingEnableOnVerify_HostRemoved) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  CreateDelegate(absl::nullopt /* initial_host */);
+
+  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
+  // setup flow has been completed on the local device.
+  SetHostInDeviceSyncClient(test_devices()[0]);
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
+      test_devices()[0]);
+  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
+            kSetPendingEnableOnVerify);
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+
+  // Host is added but not verified.
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);
+  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
+            kSetPendingEnableOnVerify);
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+
+  // Host is removed before it was verified. This simulates the user going
+  // through the forget phone flow before the phone was able to be verified.
+  // The feature manager should stop the enable attempt because it requires a
+  // paired host device that transitions from unverified to verified.
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kEligibleHostExistsButNoHostSet, absl::nullopt);
+  EXPECT_EQ(test_pref_service()->GetInteger(kPendingStatePrefName),
+            kPendingNone);
+  EXPECT_FALSE(delegate()->IsFeatureEnabled());
+}
+
+TEST_P(MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+       SetPendingEnableOnVerify_InitialPendingRequest) {
+  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
+                  true /* enable_feature_flag */);
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kHostVerified, test_devices()[0]);
+  CreateDelegate(test_devices()[0] /* initial_host */,
+                 kSetPendingEnableOnVerify /* initial_pending_state */);
+
+  EXPECT_TRUE(delegate()->IsFeatureEnabled());
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kSupported);
+  InvokePendingSetHostNetworkRequestCallback(
+      device_sync::mojom::NetworkRequestResult::kSuccess,
+      false /* expected_to_notify_observer_and_start_retry_timer */);
+  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
+  SetHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
+
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(kTestHostFeature),
+            multidevice::SoftwareFeatureState::kEnabled);
+}
+
+// Runs tests twice; once with v1 DeviceSync enabled and once with it disabled.
+// TODO(https://crbug.com/1019206): Remove when v1 DeviceSync is disabled,
+// when all devices should have an Instance ID.
+INSTANTIATE_TEST_SUITE_P(All,
+                         MultiDeviceSetupGlobalStateFeatureManagerImplTest,
+                         ::testing::Bool());
+
+}  // namespace multidevice_setup
+
+}  // namespace chromeos
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_impl.cc b/chromeos/services/multidevice_setup/multidevice_setup_impl.cc
index 581626ed..830efc21e 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_impl.cc
+++ b/chromeos/services/multidevice_setup/multidevice_setup_impl.cc
@@ -19,6 +19,8 @@
 #include "chromeos/services/multidevice_setup/android_sms_app_installing_status_observer.h"
 #include "chromeos/services/multidevice_setup/eligible_host_devices_provider_impl.h"
 #include "chromeos/services/multidevice_setup/feature_state_manager_impl.h"
+#include "chromeos/services/multidevice_setup/global_state_feature_manager.h"
+#include "chromeos/services/multidevice_setup/global_state_feature_manager_impl.h"
 #include "chromeos/services/multidevice_setup/grandfathered_easy_unlock_host_disabler.h"
 #include "chromeos/services/multidevice_setup/host_backend_delegate_impl.h"
 #include "chromeos/services/multidevice_setup/host_device_timestamp_manager_impl.h"
@@ -28,7 +30,8 @@
 #include "chromeos/services/multidevice_setup/public/cpp/android_sms_pairing_state_tracker.h"
 #include "chromeos/services/multidevice_setup/public/cpp/auth_token_validator.h"
 #include "chromeos/services/multidevice_setup/public/cpp/oobe_completion_tracker.h"
-#include "chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl.h"
+#include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
+#include "chromeos/services/multidevice_setup/wifi_sync_notification_controller.h"
 
 namespace chromeos {
 
@@ -135,17 +138,33 @@
               host_device_timestamp_manager_.get(),
               oobe_completion_tracker,
               base::DefaultClock::GetInstance())),
-      wifi_sync_feature_manager_(WifiSyncFeatureManagerImpl::Factory::Create(
+      camera_roll_feature_manager_(
+          GlobalStateFeatureManagerImpl::Factory::Create(
+              GlobalStateFeatureManagerImpl::Factory::Option::
+                  kPhoneHubCameraRoll,
+              host_status_provider_.get(),
+              pref_service,
+              device_sync_client)),
+      wifi_sync_feature_manager_(GlobalStateFeatureManagerImpl::Factory::Create(
+          GlobalStateFeatureManagerImpl::Factory::Option::kWifiSync,
           host_status_provider_.get(),
           pref_service,
-          device_sync_client,
-          delegate_notifier_.get())),
+          device_sync_client)),
+      wifi_sync_notification_controller_(
+          WifiSyncNotificationController::Factory::Create(
+              wifi_sync_feature_manager_.get(),
+              host_status_provider_.get(),
+              pref_service,
+              device_sync_client,
+              delegate_notifier_.get())),
       feature_state_manager_(FeatureStateManagerImpl::Factory::Create(
           pref_service,
           host_status_provider_.get(),
           device_sync_client,
           android_sms_pairing_state_tracker,
-          wifi_sync_feature_manager_.get(),
+          {{mojom::Feature::kPhoneHubCameraRoll,
+            camera_roll_feature_manager_.get()},
+           {mojom::Feature::kWifiSync, wifi_sync_feature_manager_.get()}},
           is_secondary_user)),
       android_sms_app_installing_host_observer_(
           android_sms_app_helper_delegate
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_impl.h b/chromeos/services/multidevice_setup/multidevice_setup_impl.h
index e6e8d08e..243d09d9 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_impl.h
+++ b/chromeos/services/multidevice_setup/multidevice_setup_impl.h
@@ -9,11 +9,11 @@
 #include <string>
 
 #include "chromeos/services/multidevice_setup/feature_state_manager.h"
+#include "chromeos/services/multidevice_setup/global_state_feature_manager.h"
 #include "chromeos/services/multidevice_setup/host_status_provider.h"
 #include "chromeos/services/multidevice_setup/multidevice_setup_base.h"
 #include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
-#include "chromeos/services/multidevice_setup/wifi_sync_feature_manager.h"
-#include "chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl.h"
+#include "chromeos/services/multidevice_setup/wifi_sync_notification_controller.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote_set.h"
 #include "url/gurl.h"
@@ -150,7 +150,10 @@
       grandfathered_easy_unlock_host_disabler_;
   std::unique_ptr<HostDeviceTimestampManager> host_device_timestamp_manager_;
   std::unique_ptr<AccountStatusChangeDelegateNotifier> delegate_notifier_;
-  std::unique_ptr<WifiSyncFeatureManager> wifi_sync_feature_manager_;
+  std::unique_ptr<GlobalStateFeatureManager> camera_roll_feature_manager_;
+  std::unique_ptr<GlobalStateFeatureManager> wifi_sync_feature_manager_;
+  std::unique_ptr<WifiSyncNotificationController>
+      wifi_sync_notification_controller_;
   std::unique_ptr<FeatureStateManager> feature_state_manager_;
   std::unique_ptr<AndroidSmsAppInstallingStatusObserver>
       android_sms_app_installing_host_observer_;
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_impl_unittest.cc b/chromeos/services/multidevice_setup/multidevice_setup_impl_unittest.cc
index 7492636f..bd8b273 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_impl_unittest.cc
+++ b/chromeos/services/multidevice_setup/multidevice_setup_impl_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "ash/constants/ash_features.h"
 #include "base/bind.h"
+#include "base/containers/flat_map.h"
 #include "base/run_loop.h"
 #include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
@@ -22,13 +23,15 @@
 #include "chromeos/services/multidevice_setup/fake_eligible_host_devices_provider.h"
 #include "chromeos/services/multidevice_setup/fake_feature_state_manager.h"
 #include "chromeos/services/multidevice_setup/fake_feature_state_observer.h"
+#include "chromeos/services/multidevice_setup/fake_global_state_feature_manager.h"
 #include "chromeos/services/multidevice_setup/fake_host_backend_delegate.h"
 #include "chromeos/services/multidevice_setup/fake_host_device_timestamp_manager.h"
 #include "chromeos/services/multidevice_setup/fake_host_status_observer.h"
 #include "chromeos/services/multidevice_setup/fake_host_status_provider.h"
 #include "chromeos/services/multidevice_setup/fake_host_verifier.h"
-#include "chromeos/services/multidevice_setup/fake_wifi_sync_feature_manager.h"
 #include "chromeos/services/multidevice_setup/feature_state_manager_impl.h"
+#include "chromeos/services/multidevice_setup/global_state_feature_manager.h"
+#include "chromeos/services/multidevice_setup/global_state_feature_manager_impl.h"
 #include "chromeos/services/multidevice_setup/grandfathered_easy_unlock_host_disabler.h"
 #include "chromeos/services/multidevice_setup/host_backend_delegate_impl.h"
 #include "chromeos/services/multidevice_setup/host_device_timestamp_manager_impl.h"
@@ -40,7 +43,7 @@
 #include "chromeos/services/multidevice_setup/public/cpp/fake_auth_token_validator.h"
 #include "chromeos/services/multidevice_setup/public/cpp/oobe_completion_tracker.h"
 #include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
-#include "chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl.h"
+#include "chromeos/services/multidevice_setup/wifi_sync_notification_controller.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -249,10 +252,10 @@
   FakeHostStatusProvider* instance_ = nullptr;
 };
 
-class FakeWifiSyncFeatureManagerFactory
-    : public WifiSyncFeatureManagerImpl::Factory {
+class FakeGlobalStateFeatureManagerFactory
+    : public GlobalStateFeatureManagerImpl::Factory {
  public:
-  FakeWifiSyncFeatureManagerFactory(
+  FakeGlobalStateFeatureManagerFactory(
       FakeHostStatusProviderFactory* fake_host_status_provider_factory,
       sync_preferences::TestingPrefServiceSyncable*
           expected_testing_pref_service,
@@ -261,39 +264,71 @@
         expected_testing_pref_service_(expected_testing_pref_service),
         expected_device_sync_client_(expected_device_sync_client) {}
 
-  FakeWifiSyncFeatureManagerFactory(const FakeWifiSyncFeatureManagerFactory&) =
-      delete;
-  FakeWifiSyncFeatureManagerFactory& operator=(
-      const FakeWifiSyncFeatureManagerFactory&) = delete;
-
-  ~FakeWifiSyncFeatureManagerFactory() override = default;
-
-  FakeWifiSyncFeatureManager* instance() { return instance_; }
+  FakeGlobalStateFeatureManagerFactory(
+      const FakeGlobalStateFeatureManagerFactory&) = delete;
+  FakeGlobalStateFeatureManagerFactory& operator=(
+      const FakeGlobalStateFeatureManagerFactory&) = delete;
+  ~FakeGlobalStateFeatureManagerFactory() override = default;
 
  private:
-  // WifiSyncFeatureManagerImpl::Factory:
-  std::unique_ptr<WifiSyncFeatureManager> CreateInstance(
+  // GlobalStateFeatureManagerImpl::Factory:
+  std::unique_ptr<GlobalStateFeatureManager> CreateInstance(
+      GlobalStateFeatureManagerImpl::Factory::Option option,
       HostStatusProvider* host_status_provider,
       PrefService* pref_service,
       device_sync::DeviceSyncClient* device_sync_client,
-      AccountStatusChangeDelegateNotifier* delegate_notifier,
       std::unique_ptr<base::OneShotTimer> timer) override {
-    EXPECT_FALSE(instance_);
     EXPECT_EQ(fake_host_status_provider_factory_->instance(),
               host_status_provider);
     EXPECT_EQ(expected_testing_pref_service_, pref_service);
     EXPECT_EQ(expected_device_sync_client_, device_sync_client);
 
-    auto instance = std::make_unique<FakeWifiSyncFeatureManager>();
-    instance_ = instance.get();
-    return instance;
+    return std::make_unique<FakeGlobalStateFeatureManager>();
   }
 
   FakeHostStatusProviderFactory* fake_host_status_provider_factory_;
   sync_preferences::TestingPrefServiceSyncable* expected_testing_pref_service_;
   device_sync::FakeDeviceSyncClient* expected_device_sync_client_;
+};
 
-  FakeWifiSyncFeatureManager* instance_ = nullptr;
+class FakeWifiSyncNotificationControllerFactory
+    : public WifiSyncNotificationController::Factory {
+ public:
+  FakeWifiSyncNotificationControllerFactory(
+      FakeHostStatusProviderFactory* fake_host_status_provider_factory,
+      sync_preferences::TestingPrefServiceSyncable*
+          expected_testing_pref_service,
+      device_sync::FakeDeviceSyncClient* expected_device_sync_client)
+      : fake_host_status_provider_factory_(fake_host_status_provider_factory),
+        expected_testing_pref_service_(expected_testing_pref_service),
+        expected_device_sync_client_(expected_device_sync_client) {}
+
+  FakeWifiSyncNotificationControllerFactory(
+      const FakeWifiSyncNotificationControllerFactory&) = delete;
+  FakeWifiSyncNotificationControllerFactory& operator=(
+      const FakeWifiSyncNotificationControllerFactory&) = delete;
+  ~FakeWifiSyncNotificationControllerFactory() override = default;
+
+ private:
+  // WifiSyncNotificationController::Factory:
+  std::unique_ptr<WifiSyncNotificationController> CreateInstance(
+      GlobalStateFeatureManager* wifi_sync_feature_manager,
+      HostStatusProvider* host_status_provider,
+      PrefService* pref_service,
+      device_sync::DeviceSyncClient* device_sync_client,
+      AccountStatusChangeDelegateNotifier* delegate_notifier) override {
+    EXPECT_EQ(fake_host_status_provider_factory_->instance(),
+              host_status_provider);
+    EXPECT_EQ(expected_testing_pref_service_, pref_service);
+    EXPECT_EQ(expected_device_sync_client_, device_sync_client);
+    // Only check inputs and return nullptr. We do not want to trigger any logic
+    // in these unit tests.
+    return nullptr;
+  }
+
+  FakeHostStatusProviderFactory* fake_host_status_provider_factory_;
+  sync_preferences::TestingPrefServiceSyncable* expected_testing_pref_service_;
+  device_sync::FakeDeviceSyncClient* expected_device_sync_client_;
 };
 
 class FakeGrandfatheredEasyUnlockHostDisablerFactory
@@ -369,7 +404,8 @@
       HostStatusProvider* host_status_provider,
       device_sync::DeviceSyncClient* device_sync_client,
       AndroidSmsPairingStateTracker* android_sms_pairing_state_tracker,
-      WifiSyncFeatureManager* wifi_sync_feature_manager,
+      const base::flat_map<mojom::Feature, GlobalStateFeatureManager*>&
+          global_state_feature_managers,
       bool is_secondary_user) override {
     EXPECT_FALSE(instance_);
     EXPECT_EQ(expected_testing_pref_service_, pref_service);
@@ -595,12 +631,19 @@
     HostStatusProviderImpl::Factory::SetFactoryForTesting(
         fake_host_status_provider_factory_.get());
 
-    fake_wifi_sync_feature_manager_factory_ =
-        std::make_unique<FakeWifiSyncFeatureManagerFactory>(
+    fake_global_state_feature_manager_factory_ =
+        std::make_unique<FakeGlobalStateFeatureManagerFactory>(
             fake_host_status_provider_factory_.get(), test_pref_service_.get(),
             fake_device_sync_client_.get());
-    WifiSyncFeatureManagerImpl::Factory::SetFactoryForTesting(
-        fake_wifi_sync_feature_manager_factory_.get());
+    GlobalStateFeatureManagerImpl::Factory::SetFactoryForTesting(
+        fake_global_state_feature_manager_factory_.get());
+
+    fake_wifi_sync_notification_controller_factory_ =
+        std::make_unique<FakeWifiSyncNotificationControllerFactory>(
+            fake_host_status_provider_factory_.get(), test_pref_service_.get(),
+            fake_device_sync_client_.get());
+    WifiSyncNotificationController::Factory::SetFactoryForTesting(
+        fake_wifi_sync_notification_controller_factory_.get());
 
     fake_grandfathered_easy_unlock_host_disabler_factory_ =
         std::make_unique<FakeGrandfatheredEasyUnlockHostDisablerFactory>(
@@ -659,7 +702,8 @@
         nullptr);
     AndroidSmsAppInstallingStatusObserver::Factory::SetFactoryForTesting(
         nullptr);
-    WifiSyncFeatureManagerImpl::Factory::SetFactoryForTesting(nullptr);
+    GlobalStateFeatureManagerImpl::Factory::SetFactoryForTesting(nullptr);
+    WifiSyncNotificationController::Factory::SetFactoryForTesting(nullptr);
   }
 
   bool IsV1DeviceSyncEnabled() { return GetParam(); }
@@ -981,8 +1025,10 @@
   std::unique_ptr<FakeHostVerifierFactory> fake_host_verifier_factory_;
   std::unique_ptr<FakeHostStatusProviderFactory>
       fake_host_status_provider_factory_;
-  std::unique_ptr<FakeWifiSyncFeatureManagerFactory>
-      fake_wifi_sync_feature_manager_factory_;
+  std::unique_ptr<FakeGlobalStateFeatureManagerFactory>
+      fake_global_state_feature_manager_factory_;
+  std::unique_ptr<FakeWifiSyncNotificationControllerFactory>
+      fake_wifi_sync_notification_controller_factory_;
   std::unique_ptr<FakeGrandfatheredEasyUnlockHostDisablerFactory>
       fake_grandfathered_easy_unlock_host_disabler_factory_;
   std::unique_ptr<FakeFeatureStateManagerFactory>
diff --git a/chromeos/services/multidevice_setup/multidevice_setup_service.cc b/chromeos/services/multidevice_setup/multidevice_setup_service.cc
index 779f690c..58e6f85 100644
--- a/chromeos/services/multidevice_setup/multidevice_setup_service.cc
+++ b/chromeos/services/multidevice_setup/multidevice_setup_service.cc
@@ -8,6 +8,7 @@
 #include "chromeos/components/multidevice/logging/logging.h"
 #include "chromeos/services/multidevice_setup/account_status_change_delegate_notifier_impl.h"
 #include "chromeos/services/multidevice_setup/android_sms_app_installing_status_observer.h"
+#include "chromeos/services/multidevice_setup/global_state_feature_manager_impl.h"
 #include "chromeos/services/multidevice_setup/grandfathered_easy_unlock_host_disabler.h"
 #include "chromeos/services/multidevice_setup/host_backend_delegate_impl.h"
 #include "chromeos/services/multidevice_setup/host_device_timestamp_manager_impl.h"
@@ -18,7 +19,7 @@
 #include "chromeos/services/multidevice_setup/public/cpp/android_sms_app_helper_delegate.h"
 #include "chromeos/services/multidevice_setup/public/cpp/android_sms_pairing_state_tracker.h"
 #include "chromeos/services/multidevice_setup/public/cpp/prefs.h"
-#include "chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl.h"
+#include "chromeos/services/multidevice_setup/wifi_sync_notification_controller.h"
 
 namespace chromeos {
 
@@ -30,7 +31,8 @@
   HostDeviceTimestampManagerImpl::RegisterPrefs(registry);
   AccountStatusChangeDelegateNotifierImpl::RegisterPrefs(registry);
   HostBackendDelegateImpl::RegisterPrefs(registry);
-  WifiSyncFeatureManagerImpl::RegisterPrefs(registry);
+  GlobalStateFeatureManagerImpl::RegisterPrefs(registry);
+  WifiSyncNotificationController::RegisterPrefs(registry);
   HostVerifierImpl::RegisterPrefs(registry);
   GrandfatheredEasyUnlockHostDisabler::RegisterPrefs(registry);
   AndroidSmsAppInstallingStatusObserver::RegisterPrefs(registry);
diff --git a/chromeos/services/multidevice_setup/wifi_sync_feature_manager.h b/chromeos/services/multidevice_setup/wifi_sync_feature_manager.h
deleted file mode 100644
index e62388d..0000000
--- a/chromeos/services/multidevice_setup/wifi_sync_feature_manager.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_SERVICES_MULTIDEVICE_SETUP_WIFI_SYNC_FEATURE_MANAGER_H_
-#define CHROMEOS_SERVICES_MULTIDEVICE_SETUP_WIFI_SYNC_FEATURE_MANAGER_H_
-
-#include "chromeos/components/multidevice/remote_device_ref.h"
-#include "chromeos/services/multidevice_setup/host_status_provider.h"
-
-namespace chromeos {
-
-namespace multidevice_setup {
-
-// Manager for setting and receiving the Wifi Sync Host enabled/disabled state.
-// This class is considered the source of truth for the current state of Wifi
-// Sync Host.
-class WifiSyncFeatureManager {
- public:
-  virtual ~WifiSyncFeatureManager() = default;
-  WifiSyncFeatureManager(const WifiSyncFeatureManager&) = delete;
-  WifiSyncFeatureManager& operator=(const WifiSyncFeatureManager&) = delete;
-
-  // Attempts to enable/disable Wifi Sync on the backend for the host
-  // device that is synced at the time SetIsWifiSyncEnabled  is called.
-  virtual void SetIsWifiSyncEnabled(bool enabled) = 0;
-
-  // Returns whether Wifi Sync is enabled/disabled.
-  virtual bool IsWifiSyncEnabled() = 0;
-
- protected:
-  WifiSyncFeatureManager() = default;
-};
-
-}  // namespace multidevice_setup
-
-}  // namespace chromeos
-
-#endif  // CHROMEOS_SERVICES_MULTIDEVICE_SETUP_WIFI_SYNC_FEATURE_MANAGER_H_
diff --git a/chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl.cc b/chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl.cc
deleted file mode 100644
index 4bdb440..0000000
--- a/chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl.cc
+++ /dev/null
@@ -1,453 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl.h"
-
-#include <sstream>
-
-#include "ash/constants/ash_features.h"
-#include "base/bind.h"
-#include "base/memory/ptr_util.h"
-#include "base/power_monitor/power_monitor.h"
-#include "chromeos/components/multidevice/logging/logging.h"
-#include "chromeos/components/multidevice/software_feature.h"
-#include "chromeos/components/multidevice/software_feature_state.h"
-#include "chromeos/services/device_sync/feature_status_change.h"
-#include "chromeos/services/multidevice_setup/host_status_provider.h"
-#include "chromeos/services/multidevice_setup/public/cpp/prefs.h"
-#include "components/prefs/pref_registry_simple.h"
-#include "components/prefs/pref_service.h"
-#include "components/session_manager/core/session_manager.h"
-
-namespace chromeos {
-
-namespace multidevice_setup {
-
-namespace {
-
-const char kPendingWifiSyncRequestEnabledPrefName[] =
-    "multidevice_setup.pending_set_wifi_sync_enabled_request";
-
-// Pref to track whether the announcement notification can be shown the next
-// time the device is unlocked with a verified host and wi-fi sync supported but
-// disabled.
-const char kCanShowAnnouncementPrefName[] =
-    "multidevice_setup.can_show_wifi_sync_announcement";
-
-// The number of minutes to wait before retrying a failed attempt.
-const int kNumMinutesBetweenRetries = 5;
-
-}  // namespace
-
-// static
-WifiSyncFeatureManagerImpl::Factory*
-    WifiSyncFeatureManagerImpl::Factory::test_factory_ = nullptr;
-
-// static
-std::unique_ptr<WifiSyncFeatureManager>
-WifiSyncFeatureManagerImpl::Factory::Create(
-    HostStatusProvider* host_status_provider,
-    PrefService* pref_service,
-    device_sync::DeviceSyncClient* device_sync_client,
-    AccountStatusChangeDelegateNotifier* delegate_notifier,
-    std::unique_ptr<base::OneShotTimer> timer) {
-  if (test_factory_) {
-    return test_factory_->CreateInstance(host_status_provider, pref_service,
-                                         device_sync_client, delegate_notifier,
-                                         std::move(timer));
-  }
-
-  return base::WrapUnique(new WifiSyncFeatureManagerImpl(
-      host_status_provider, pref_service, device_sync_client, delegate_notifier,
-      std::move(timer)));
-}
-
-// static
-void WifiSyncFeatureManagerImpl::Factory::SetFactoryForTesting(
-    Factory* test_factory) {
-  test_factory_ = test_factory;
-}
-
-WifiSyncFeatureManagerImpl::Factory::~Factory() = default;
-
-void WifiSyncFeatureManagerImpl::RegisterPrefs(PrefRegistrySimple* registry) {
-  registry->RegisterIntegerPref(kPendingWifiSyncRequestEnabledPrefName,
-                                static_cast<int>(PendingState::kPendingNone));
-  registry->RegisterBooleanPref(kCanShowAnnouncementPrefName, true);
-}
-
-WifiSyncFeatureManagerImpl::WifiSyncFeatureManagerImpl(
-    HostStatusProvider* host_status_provider,
-    PrefService* pref_service,
-    device_sync::DeviceSyncClient* device_sync_client,
-    AccountStatusChangeDelegateNotifier* delegate_notifier,
-    std::unique_ptr<base::OneShotTimer> timer)
-    : WifiSyncFeatureManager(),
-      host_status_provider_(host_status_provider),
-      pref_service_(pref_service),
-      device_sync_client_(device_sync_client),
-      delegate_notifier_(delegate_notifier),
-      timer_(std::move(timer)) {
-  host_status_provider_->AddObserver(this);
-  device_sync_client_->AddObserver(this);
-
-  if (pref_service_->GetBoolean(kCanShowAnnouncementPrefName)) {
-    session_manager::SessionManager::Get()->AddObserver(this);
-    base::PowerMonitor::AddPowerSuspendObserver(this);
-    did_register_session_observers_ = true;
-  }
-
-  if (GetCurrentState() == CurrentState::kValidPendingRequest) {
-    AttemptSetWifiSyncHostStateNetworkRequest(false /* is_retry */);
-  }
-
-  if (ShouldEnableOnVerify()) {
-    ProcessEnableOnVerifyAttempt();
-  }
-}
-
-WifiSyncFeatureManagerImpl::~WifiSyncFeatureManagerImpl() {
-  host_status_provider_->RemoveObserver(this);
-  device_sync_client_->RemoveObserver(this);
-  if (did_register_session_observers_) {
-    session_manager::SessionManager::Get()->RemoveObserver(this);
-    base::PowerMonitor::RemovePowerSuspendObserver(this);
-  }
-}
-
-void WifiSyncFeatureManagerImpl::OnHostStatusChange(
-    const HostStatusProvider::HostStatusWithDevice& host_status_with_device) {
-  if (GetCurrentState() == CurrentState::kNoVerifiedHost &&
-      !ShouldEnableOnVerify()) {
-    ResetPendingWifiSyncHostNetworkRequest();
-  }
-
-  if (ShouldAttemptToEnableAfterHostVerified()) {
-    SetPendingWifiSyncHostNetworkRequest(
-        PendingState::kSetPendingEnableOnVerify);
-    return;
-  }
-
-  if (ShouldEnableOnVerify()) {
-    ProcessEnableOnVerifyAttempt();
-  }
-}
-
-void WifiSyncFeatureManagerImpl::OnNewDevicesSynced() {
-  if (GetCurrentState() != CurrentState::kValidPendingRequest &&
-      !ShouldEnableOnVerify()) {
-    ResetPendingWifiSyncHostNetworkRequest();
-  }
-}
-
-void WifiSyncFeatureManagerImpl::OnSessionStateChanged() {
-  ShowAnnouncementNotificationIfEligible();
-}
-
-void WifiSyncFeatureManagerImpl::OnResume() {
-  ShowAnnouncementNotificationIfEligible();
-}
-
-void WifiSyncFeatureManagerImpl::ShowAnnouncementNotificationIfEligible() {
-  // Show the announcement notification when the device is unlocked and
-  // eligible for wi-fi sync.  This is done on unlock/resume to avoid showing
-  // it on the first sign-in when it would distract from showoff and other
-  // announcements.
-
-  if (session_manager::SessionManager::Get()->IsUserSessionBlocked()) {
-    return;
-  }
-
-  if (!IsFeatureAllowed(mojom::Feature::kWifiSync, pref_service_)) {
-    return;
-  }
-
-  if (!pref_service_->GetBoolean(kCanShowAnnouncementPrefName)) {
-    return;
-  }
-
-  if (!IsWifiSyncSupported()) {
-    return;
-  }
-
-  if (GetCurrentState() == CurrentState::kNoVerifiedHost ||
-      IsWifiSyncEnabled()) {
-    pref_service_->SetBoolean(kCanShowAnnouncementPrefName, false);
-    return;
-  }
-
-  if (!delegate_notifier_->delegate()) {
-    return;
-  }
-
-  delegate_notifier_->delegate()->OnBecameEligibleForWifiSync();
-  pref_service_->SetBoolean(kCanShowAnnouncementPrefName, false);
-}
-
-void WifiSyncFeatureManagerImpl::SetIsWifiSyncEnabled(bool enabled) {
-  if (GetCurrentState() == CurrentState::kNoVerifiedHost) {
-    PA_LOG(ERROR)
-        << "WifiSyncFeatureManagerImpl::SetIsWifiSyncEnabled:  Network request "
-           "not attempted because there is No Verified Host";
-    ResetPendingWifiSyncHostNetworkRequest();
-    return;
-  }
-
-  SetPendingWifiSyncHostNetworkRequest(enabled ? PendingState::kPendingEnable
-                                               : PendingState::kPendingDisable);
-  pref_service_->SetBoolean(kCanShowAnnouncementPrefName, false);
-
-  // Stop timer since new attempt is started.
-  timer_->Stop();
-  AttemptSetWifiSyncHostStateNetworkRequest(false /* is_retry */);
-}
-
-bool WifiSyncFeatureManagerImpl::IsWifiSyncEnabled() {
-  CurrentState current_state = GetCurrentState();
-  if (current_state == CurrentState::kNoVerifiedHost) {
-    return false;
-  }
-
-  if (current_state == CurrentState::kValidPendingRequest) {
-    return GetPendingState() == PendingState::kPendingEnable;
-  }
-
-  return host_status_provider_->GetHostWithStatus()
-             .host_device()
-             ->GetSoftwareFeatureState(
-                 multidevice::SoftwareFeature::kWifiSyncHost) ==
-         multidevice::SoftwareFeatureState::kEnabled;
-}
-
-bool WifiSyncFeatureManagerImpl::IsWifiSyncSupported() {
-  CurrentState current_state = GetCurrentState();
-  if (current_state == CurrentState::kNoVerifiedHost) {
-    return false;
-  }
-
-  absl::optional<multidevice::RemoteDeviceRef> host_device =
-      host_status_provider_->GetHostWithStatus().host_device();
-  if (!host_device) {
-    PA_LOG(ERROR) << "WifiSyncFeatureManagerImpl::" << __func__
-                  << ": Host device unexpectedly null.";
-    return false;
-  }
-
-  if (host_device->GetSoftwareFeatureState(
-          multidevice::SoftwareFeature::kWifiSyncHost) ==
-      multidevice::SoftwareFeatureState::kNotSupported) {
-    return false;
-  }
-
-  absl::optional<multidevice::RemoteDeviceRef> local_device =
-      device_sync_client_->GetLocalDeviceMetadata();
-  if (!local_device) {
-    PA_LOG(ERROR) << "WifiSyncFeatureManagerImpl::" << __func__
-                  << ": Local device unexpectedly null.";
-    return false;
-  }
-
-  if (local_device->GetSoftwareFeatureState(
-          multidevice::SoftwareFeature::kWifiSyncClient) ==
-      multidevice::SoftwareFeatureState::kNotSupported) {
-    return false;
-  }
-
-  return true;
-}
-
-void WifiSyncFeatureManagerImpl::ResetPendingWifiSyncHostNetworkRequest() {
-  SetPendingWifiSyncHostNetworkRequest(PendingState::kPendingNone);
-  timer_->Stop();
-}
-
-WifiSyncFeatureManagerImpl::PendingState
-WifiSyncFeatureManagerImpl::GetPendingState() {
-  return static_cast<PendingState>(
-      pref_service_->GetInteger(kPendingWifiSyncRequestEnabledPrefName));
-}
-
-WifiSyncFeatureManagerImpl::CurrentState
-WifiSyncFeatureManagerImpl::GetCurrentState() {
-  if (host_status_provider_->GetHostWithStatus().host_status() !=
-      mojom::HostStatus::kHostVerified) {
-    return CurrentState::kNoVerifiedHost;
-  }
-
-  PendingState pending_state = GetPendingState();
-
-  // If the pending request is kSetPendingEnableOnVerify then there is no
-  // actionable pending equest. The pending request will be changed from
-  // kSetPendingEnableOnVerify when the host has been verified.
-  if (pending_state == PendingState::kPendingNone ||
-      pending_state == PendingState::kSetPendingEnableOnVerify) {
-    return CurrentState::kNoPendingRequest;
-  }
-
-  bool enabled_on_host =
-      (host_status_provider_->GetHostWithStatus()
-           .host_device()
-           ->GetSoftwareFeatureState(
-               multidevice::SoftwareFeature::kWifiSyncHost) ==
-       multidevice::SoftwareFeatureState::kEnabled);
-  bool pending_enabled = (pending_state == PendingState::kPendingEnable);
-
-  if (pending_enabled == enabled_on_host) {
-    return CurrentState::kPendingMatchesBackend;
-  }
-
-  return CurrentState::kValidPendingRequest;
-}
-
-void WifiSyncFeatureManagerImpl::SetPendingWifiSyncHostNetworkRequest(
-    PendingState pending_state) {
-  pref_service_->SetInteger(kPendingWifiSyncRequestEnabledPrefName,
-                            static_cast<int>(pending_state));
-}
-
-void WifiSyncFeatureManagerImpl::AttemptSetWifiSyncHostStateNetworkRequest(
-    bool is_retry) {
-  if (network_request_in_flight_) {
-    return;
-  }
-
-  bool pending_enabled = (GetPendingState() == PendingState::kPendingEnable);
-
-  PA_LOG(INFO) << "WifiSyncFeatureManagerImpl::"
-               << "AttemptSetWifiSyncHostStateNetworkRequest(): "
-               << (is_retry ? "Retrying attempt" : "Attempting") << " to "
-               << (pending_enabled ? "enable" : "disable") << " wifi sync.";
-
-  network_request_in_flight_ = true;
-  multidevice::RemoteDeviceRef host_device =
-      *host_status_provider_->GetHostWithStatus().host_device();
-
-  if (features::ShouldUseV1DeviceSync()) {
-    // Even if the |device_to_set| has a non-trivial Instance ID, we still
-    // invoke the v1 DeviceSync RPC to set the feature state. This ensures that
-    // GmsCore will be notified of the change regardless of what version of
-    // DeviceSync it is running. The v1 and v2 RPCs to change feature states
-    // ultimately update the same backend database entry. Note: The
-    // RemoteDeviceProvider guarantees that every device will have a public key
-    // while v1 DeviceSync is enabled.
-    device_sync_client_->SetSoftwareFeatureState(
-        host_device.public_key(), multidevice::SoftwareFeature::kWifiSyncHost,
-        pending_enabled /* enabled */, pending_enabled /* is_exclusive */,
-        base::BindOnce(&WifiSyncFeatureManagerImpl::
-                           OnSetWifiSyncHostStateNetworkRequestFinished,
-                       weak_ptr_factory_.GetWeakPtr(), pending_enabled));
-  } else {
-    device_sync_client_->SetFeatureStatus(
-        host_device.instance_id(), multidevice::SoftwareFeature::kWifiSyncHost,
-        pending_enabled ? device_sync::FeatureStatusChange::kEnableExclusively
-                        : device_sync::FeatureStatusChange::kDisable,
-        base::BindOnce(&WifiSyncFeatureManagerImpl::
-                           OnSetWifiSyncHostStateNetworkRequestFinished,
-                       weak_ptr_factory_.GetWeakPtr(), pending_enabled));
-  }
-}
-
-void WifiSyncFeatureManagerImpl::OnSetWifiSyncHostStateNetworkRequestFinished(
-    bool attempted_to_enable,
-    device_sync::mojom::NetworkRequestResult result_code) {
-  network_request_in_flight_ = false;
-
-  bool success =
-      (result_code == device_sync::mojom::NetworkRequestResult::kSuccess);
-
-  std::stringstream ss;
-  ss << "WifiSyncFeatureManagerImpl::"
-     << "OnSetWifiSyncHostStateNetworkRequestFinished(): "
-     << (success ? "Completed successful" : "Failure requesting") << " "
-     << "set WIFI_SYNC_HOST "
-     << ". Attempted to enable: " << (attempted_to_enable ? "true" : "false");
-
-  if (success) {
-    PA_LOG(VERBOSE) << ss.str();
-    PendingState pending_state = GetPendingState();
-    if (pending_state == PendingState::kPendingNone) {
-      return;
-    }
-
-    bool pending_enabled = (pending_state == PendingState::kPendingEnable);
-    // If the network request was successful but there is still a pending
-    // network request then trigger a network request immediately. This could
-    // happen if there was a second attempt to set the backend while the first
-    // one was still in progress.
-    if (attempted_to_enable != pending_enabled) {
-      AttemptSetWifiSyncHostStateNetworkRequest(false /* is_retry */);
-    }
-    return;
-  }
-
-  ss << ", Error code: " << result_code;
-  PA_LOG(WARNING) << ss.str();
-
-  // If the network request failed and there is still a pending network request,
-  // schedule a retry.
-  if (GetCurrentState() == CurrentState::kValidPendingRequest) {
-    timer_->Start(FROM_HERE, base::Minutes(kNumMinutesBetweenRetries),
-                  base::BindOnce(&WifiSyncFeatureManagerImpl::
-                                     AttemptSetWifiSyncHostStateNetworkRequest,
-                                 base::Unretained(this), true /* is_retry */));
-  }
-}
-
-bool WifiSyncFeatureManagerImpl::ShouldEnableOnVerify() {
-  return (GetPendingState() == PendingState::kSetPendingEnableOnVerify);
-}
-
-void WifiSyncFeatureManagerImpl::ProcessEnableOnVerifyAttempt() {
-  mojom::HostStatus host_status =
-      host_status_provider_->GetHostWithStatus().host_status();
-
-  // If host is not set.
-  if (host_status == mojom::HostStatus::kNoEligibleHosts ||
-      host_status == mojom::HostStatus::kEligibleHostExistsButNoHostSet) {
-    ResetPendingWifiSyncHostNetworkRequest();
-    return;
-  }
-
-  if (host_status != mojom::HostStatus::kHostVerified) {
-    return;
-  }
-
-  if (IsWifiSyncEnabled()) {
-    ResetPendingWifiSyncHostNetworkRequest();
-    return;
-  }
-
-  SetIsWifiSyncEnabled(true);
-}
-
-bool WifiSyncFeatureManagerImpl::ShouldAttemptToEnableAfterHostVerified() {
-  HostStatusProvider::HostStatusWithDevice host_status_with_device =
-      host_status_provider_->GetHostWithStatus();
-
-  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
-  // setup flow has been completed on the local device.
-  if (host_status_with_device.host_status() !=
-      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation) {
-    return false;
-  }
-
-  // Check if enterprise policy prohibits Wifi Sync or if feature flag is
-  // disabled.
-  if (!IsFeatureAllowed(mojom::Feature::kWifiSync, pref_service_)) {
-    return false;
-  }
-
-  // Check if wifi sync is supported by host device.
-  if (host_status_with_device.host_device()->GetSoftwareFeatureState(
-          multidevice::SoftwareFeature::kWifiSyncHost) ==
-      multidevice::SoftwareFeatureState::kNotSupported) {
-    return false;
-  }
-
-  return true;
-}
-
-}  // namespace multidevice_setup
-
-}  // namespace chromeos
diff --git a/chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl.h b/chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl.h
deleted file mode 100644
index 6855a15..0000000
--- a/chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl.h
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROMEOS_SERVICES_MULTIDEVICE_SETUP_WIFI_SYNC_FEATURE_MANAGER_IMPL_H_
-#define CHROMEOS_SERVICES_MULTIDEVICE_SETUP_WIFI_SYNC_FEATURE_MANAGER_IMPL_H_
-
-#include "base/memory/weak_ptr.h"
-#include "base/power_monitor/power_observer.h"
-#include "base/timer/timer.h"
-#include "chromeos/components/multidevice/remote_device_ref.h"
-#include "chromeos/services/device_sync/public/cpp/device_sync_client.h"
-#include "chromeos/services/multidevice_setup/account_status_change_delegate_notifier.h"
-#include "chromeos/services/multidevice_setup/wifi_sync_feature_manager.h"
-#include "components/session_manager/core/session_manager_observer.h"
-
-class PrefRegistrySimple;
-class PrefService;
-
-namespace chromeos {
-
-namespace multidevice_setup {
-
-// Concrete WifiSyncFeatureManager implementation, which utilizes
-// DeviceSyncClient to communicate with the back-end.
-//
-// This toggles WIFI_SYNC_HOST between enabled/supported on cryptauth for a
-// synced phone, where supported is considered disabled by user.
-//
-// Toggling WIFI_SYNC_HOST is a global action, so it will be reflected on all
-// synced devices.
-class WifiSyncFeatureManagerImpl
-    : public WifiSyncFeatureManager,
-      public HostStatusProvider::Observer,
-      public device_sync::DeviceSyncClient::Observer,
-      public base::PowerSuspendObserver,
-      public session_manager::SessionManagerObserver {
- public:
-  class Factory {
-   public:
-    static std::unique_ptr<WifiSyncFeatureManager> Create(
-        HostStatusProvider* host_status_provider,
-        PrefService* pref_service,
-        device_sync::DeviceSyncClient* device_sync_client,
-        AccountStatusChangeDelegateNotifier* delegate_notifier,
-        std::unique_ptr<base::OneShotTimer> timer =
-            std::make_unique<base::OneShotTimer>());
-    static void SetFactoryForTesting(Factory* test_factory);
-
-   protected:
-    virtual ~Factory();
-    virtual std::unique_ptr<WifiSyncFeatureManager> CreateInstance(
-        HostStatusProvider* host_status_provider,
-        PrefService* pref_service,
-        device_sync::DeviceSyncClient* device_sync_client,
-        AccountStatusChangeDelegateNotifier* delegate_notifier,
-        std::unique_ptr<base::OneShotTimer> timer) = 0;
-
-   private:
-    static Factory* test_factory_;
-  };
-
-  static void RegisterPrefs(PrefRegistrySimple* registry);
-
-  ~WifiSyncFeatureManagerImpl() override;
-  WifiSyncFeatureManagerImpl(const WifiSyncFeatureManagerImpl&) = delete;
-  WifiSyncFeatureManagerImpl& operator=(const WifiSyncFeatureManagerImpl&) =
-      delete;
-
- private:
-  WifiSyncFeatureManagerImpl(
-      HostStatusProvider* host_status_provider,
-      PrefService* pref_service,
-      device_sync::DeviceSyncClient* device_sync_client,
-      AccountStatusChangeDelegateNotifier* delegate_notifier,
-      std::unique_ptr<base::OneShotTimer> timer);
-
-  // HostStatusProvider::Observer,
-  void OnHostStatusChange(const HostStatusProvider::HostStatusWithDevice&
-                              host_status_with_device) override;
-
-  // DeviceSyncClient::Observer:
-  void OnNewDevicesSynced() override;
-
-  // SessionManagerObserver:
-  void OnSessionStateChanged() override;
-
-  // PowerSuspendObserver:
-  void OnResume() override;
-
-  // WifiSyncFeatureManager:
-
-  // Attempts to enable/disable WIFI_SYNC_HOST on the backend for the host
-  // device that is synced at the time SetIsWifiSyncEnabled  is called.
-  //
-  // If a the request fails (e.g., the device is offline or the server is down),
-  // the OnBackendRequestFailed() observer function is invoked, but this object
-  // continues to attempt the request until one of the following happens: the
-  // request succeeds, SetIsWifiSyncEnabled() called is with different value, or
-  // the synced host device changes.
-  //
-  // If there is already a pending request and this function is called with the
-  // same request, a retry will be attempted immediately.
-  void SetIsWifiSyncEnabled(bool enabled) override;
-
-  // Returns whether WIFI_SYNC_HOST is enabled/disabled. If there is a pending
-  // request to enable or disable WIFI_SYNC_HOST, the state that the pending
-  // request is intending to set WIFI_SYNC_HOST to is returned, otherwise the
-  // state on the back-end is returned.
-  bool IsWifiSyncEnabled() override;
-
-  // Numerical values cannot be changed because they map to integers that are
-  // stored persistently in prefs.
-  enum class PendingState {
-    kPendingNone = 0,
-    kPendingEnable = 1,
-    kPendingDisable = 2,
-    kSetPendingEnableOnVerify = 3
-  };
-
-  enum class CurrentState {
-    kNoVerifiedHost,
-    kNoPendingRequest,
-    kPendingMatchesBackend,
-    kValidPendingRequest
-  };
-
-  void ResetPendingWifiSyncHostNetworkRequest();
-  PendingState GetPendingState();
-  CurrentState GetCurrentState();
-  void SetPendingWifiSyncHostNetworkRequest(PendingState pending_state);
-  void AttemptSetWifiSyncHostStateNetworkRequest(bool is_retry);
-  void OnSetWifiSyncHostStateNetworkRequestFinished(
-      bool attempted_to_enable,
-      device_sync::mojom::NetworkRequestResult result_code);
-  bool ShouldEnableOnVerify();
-  void ProcessEnableOnVerifyAttempt();
-  bool ShouldAttemptToEnableAfterHostVerified();
-  void ShowAnnouncementNotificationIfEligible();
-  bool IsWifiSyncSupported();
-
-  HostStatusProvider* host_status_provider_;
-  PrefService* pref_service_;
-  device_sync::DeviceSyncClient* device_sync_client_;
-  AccountStatusChangeDelegateNotifier* delegate_notifier_;
-  std::unique_ptr<base::OneShotTimer> timer_;
-
-  bool did_register_session_observers_ = false;
-  bool network_request_in_flight_ = false;
-
-  base::WeakPtrFactory<WifiSyncFeatureManagerImpl> weak_ptr_factory_{this};
-};
-
-}  // namespace multidevice_setup
-
-}  // namespace chromeos
-
-#endif  // CHROMEOS_SERVICES_MULTIDEVICE_SETUP_WIFI_SYNC_FEATURE_MANAGER_IMPL_H_
diff --git a/chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl_unittest.cc b/chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl_unittest.cc
deleted file mode 100644
index 90b93c5..0000000
--- a/chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl_unittest.cc
+++ /dev/null
@@ -1,1014 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chromeos/services/multidevice_setup/wifi_sync_feature_manager_impl.h"
-
-#include <memory>
-
-#include "ash/constants/ash_features.h"
-#include "base/containers/flat_map.h"
-#include "base/test/scoped_feature_list.h"
-#include "base/test/task_environment.h"
-#include "base/timer/mock_timer.h"
-#include "base/unguessable_token.h"
-#include "chromeos/components/multidevice/remote_device_test_util.h"
-#include "chromeos/components/multidevice/software_feature.h"
-#include "chromeos/components/multidevice/software_feature_state.h"
-#include "chromeos/services/device_sync/public/cpp/fake_device_sync_client.h"
-#include "chromeos/services/multidevice_setup/fake_account_status_change_delegate.h"
-#include "chromeos/services/multidevice_setup/fake_account_status_change_delegate_notifier.h"
-#include "chromeos/services/multidevice_setup/fake_host_status_provider.h"
-#include "chromeos/services/multidevice_setup/public/cpp/prefs.h"
-#include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
-#include "components/session_manager/core/session_manager.h"
-#include "components/sync_preferences/testing_pref_service_syncable.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/abseil-cpp/absl/types/optional.h"
-
-namespace chromeos {
-
-namespace multidevice_setup {
-
-namespace {
-
-const char kPendingWifiSyncRequestEnabledPrefName[] =
-    "multidevice_setup.pending_set_wifi_sync_enabled_request";
-
-enum PendingState {
-  kPendingNone = 0,
-  kPendingEnable = 1,
-  kPendingDisable = 2,
-  kSetPendingEnableOnVerify = 3
-};
-
-const size_t kNumTestDevices = 4;
-
-}  // namespace
-
-class MultiDeviceSetupWifiSyncFeatureManagerImplTest
-    : public ::testing::TestWithParam<bool> {
- public:
-  MultiDeviceSetupWifiSyncFeatureManagerImplTest(
-      const MultiDeviceSetupWifiSyncFeatureManagerImplTest&) = delete;
-  MultiDeviceSetupWifiSyncFeatureManagerImplTest& operator=(
-      const MultiDeviceSetupWifiSyncFeatureManagerImplTest&) = delete;
-
- protected:
-  MultiDeviceSetupWifiSyncFeatureManagerImplTest()
-      : test_devices_(
-            multidevice::CreateRemoteDeviceRefListForTest(kNumTestDevices)) {}
-  ~MultiDeviceSetupWifiSyncFeatureManagerImplTest() override = default;
-
-  // testing::Test:
-  void SetUp() override {
-    // Tests are run once to simulate when v1 DeviceSync is enabled and once to
-    // simulate when it is disabled, leaving only v2 DeviceSync operational. In
-    // the former case, only public keys are needed, and in the latter case,
-    // only Instance IDs are needed.
-    for (multidevice::RemoteDeviceRef device : test_devices_) {
-      if (features::ShouldUseV1DeviceSync())
-        GetMutableRemoteDevice(device)->instance_id.clear();
-      else
-        GetMutableRemoteDevice(device)->public_key.clear();
-    }
-
-    SetWifiSyncSupportedInDeviceSyncClient();
-
-    fake_host_status_provider_ = std::make_unique<FakeHostStatusProvider>();
-
-    test_pref_service_ =
-        std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
-    WifiSyncFeatureManagerImpl::RegisterPrefs(test_pref_service_->registry());
-    // Allow Wifi Sync by policy
-    test_pref_service_->registry()->RegisterBooleanPref(
-        kWifiSyncAllowedPrefName, true);
-    session_manager_ = std::make_unique<session_manager::SessionManager>();
-    fake_device_sync_client_ =
-        std::make_unique<device_sync::FakeDeviceSyncClient>();
-    fake_device_sync_client_->set_synced_devices(test_devices_);
-    fake_account_status_change_delegate_ =
-        std::make_unique<FakeAccountStatusChangeDelegate>();
-    fake_account_status_change_delegate_notifier_ =
-        std::make_unique<FakeAccountStatusChangeDelegateNotifier>();
-    fake_account_status_change_delegate_notifier_
-        ->SetAccountStatusChangeDelegateRemote(
-            fake_account_status_change_delegate_->GenerateRemote());
-    fake_account_status_change_delegate_notifier_->FlushForTesting();
-    multidevice::RemoteDeviceRef local_device =
-        multidevice::CreateRemoteDeviceRefForTest();
-    GetMutableRemoteDevice(local_device)
-        ->software_features[multidevice::SoftwareFeature::kWifiSyncClient] =
-        multidevice::SoftwareFeatureState::kSupported;
-    fake_device_sync_client_->set_local_device_metadata(local_device);
-  }
-
-  void TearDown() override {}
-
-  void SetHostInDeviceSyncClient(
-      const absl::optional<multidevice::RemoteDeviceRef>& host_device) {
-    for (const auto& remote_device : test_devices_) {
-      bool should_be_host =
-          host_device != absl::nullopt &&
-          ((!remote_device.instance_id().empty() &&
-            host_device->instance_id() == remote_device.instance_id()) ||
-           (!remote_device.GetDeviceId().empty() &&
-            host_device->GetDeviceId() == remote_device.GetDeviceId()));
-
-      GetMutableRemoteDevice(remote_device)
-          ->software_features
-              [multidevice::SoftwareFeature::kBetterTogetherHost] =
-          should_be_host ? multidevice::SoftwareFeatureState::kEnabled
-                         : multidevice::SoftwareFeatureState::kSupported;
-    }
-    fake_device_sync_client_->NotifyNewDevicesSynced();
-  }
-
-  void SetWifiSyncSupportedInDeviceSyncClient() {
-    for (const auto& remote_device : test_devices_) {
-      GetMutableRemoteDevice(remote_device)
-          ->software_features[multidevice::SoftwareFeature::kWifiSyncHost] =
-          multidevice::SoftwareFeatureState::kSupported;
-    }
-  }
-
-  void CreateDelegate(
-      const absl::optional<multidevice::RemoteDeviceRef>& initial_host,
-      int initial_pending_wifi_sync_request = kPendingNone) {
-    SetHostInDeviceSyncClient(initial_host);
-    test_pref_service_->SetInteger(kPendingWifiSyncRequestEnabledPrefName,
-                                   initial_pending_wifi_sync_request);
-
-    auto mock_timer = std::make_unique<base::MockOneShotTimer>();
-    mock_timer_ = mock_timer.get();
-
-    SetHostWithStatus(initial_host);
-
-    delegate_ = WifiSyncFeatureManagerImpl::Factory::Create(
-        fake_host_status_provider_.get(), test_pref_service_.get(),
-        fake_device_sync_client_.get(),
-        fake_account_status_change_delegate_notifier_.get(),
-        std::move(mock_timer));
-  }
-
-  void SetHostWithStatus(
-      const absl::optional<multidevice::RemoteDeviceRef>& host_device) {
-    mojom::HostStatus host_status =
-        (host_device == absl::nullopt ? mojom::HostStatus::kNoEligibleHosts
-                                      : mojom::HostStatus::kHostVerified);
-    fake_host_status_provider_->SetHostWithStatus(host_status, host_device);
-  }
-
-  void SetIsWifiSyncEnabled(bool enabled) {
-    delegate_->SetIsWifiSyncEnabled(enabled);
-
-    HostStatusProvider::HostStatusWithDevice host_with_status =
-        fake_host_status_provider_->GetHostWithStatus();
-    if (host_with_status.host_status() != mojom::HostStatus::kHostVerified) {
-      return;
-    }
-
-    multidevice::RemoteDeviceRef host_device = *host_with_status.host_device();
-
-    bool enabled_on_backend =
-        (host_device.GetSoftwareFeatureState(
-             multidevice::SoftwareFeature::kWifiSyncHost) ==
-         multidevice::SoftwareFeatureState::kEnabled);
-    bool pending_request_state_same_as_backend =
-        (enabled == enabled_on_backend);
-
-    if (pending_request_state_same_as_backend) {
-      return;
-    }
-
-    VerifyLatestSetWifiSyncHostNetworkRequest(host_device, enabled);
-  }
-
-  void VerifyLatestSetWifiSyncHostNetworkRequest(
-      const multidevice::RemoteDeviceRef expected_host,
-      bool expected_should_enable) {
-    if (features::ShouldUseV1DeviceSync()) {
-      ASSERT_FALSE(
-          fake_device_sync_client_->set_software_feature_state_inputs_queue()
-              .empty());
-      const device_sync::FakeDeviceSyncClient::SetSoftwareFeatureStateInputs&
-          inputs = fake_device_sync_client_
-                       ->set_software_feature_state_inputs_queue()
-                       .back();
-      EXPECT_EQ(expected_host.public_key(), inputs.public_key);
-      EXPECT_EQ(multidevice::SoftwareFeature::kWifiSyncHost,
-                inputs.software_feature);
-      EXPECT_EQ(expected_should_enable, inputs.enabled);
-      EXPECT_EQ(expected_should_enable, inputs.is_exclusive);
-      return;
-    }
-
-    // Verify inputs to SetFeatureStatus().
-    ASSERT_FALSE(
-        fake_device_sync_client_->set_feature_status_inputs_queue().empty());
-    const device_sync::FakeDeviceSyncClient::SetFeatureStatusInputs& inputs =
-        fake_device_sync_client_->set_feature_status_inputs_queue().back();
-    EXPECT_EQ(expected_host.instance_id(), inputs.device_instance_id);
-    EXPECT_EQ(multidevice::SoftwareFeature::kWifiSyncHost, inputs.feature);
-    EXPECT_EQ(expected_should_enable
-                  ? device_sync::FeatureStatusChange::kEnableExclusively
-                  : device_sync::FeatureStatusChange::kDisable,
-              inputs.status_change);
-  }
-
-  int GetSetHostNetworkRequestCallbackQueueSize() {
-    return features::ShouldUseV1DeviceSync()
-               ? fake_device_sync_client_
-                     ->GetSetSoftwareFeatureStateInputsQueueSize()
-               : fake_device_sync_client_->GetSetFeatureStatusInputsQueueSize();
-  }
-
-  void InvokePendingSetWifiSyncHostNetworkRequestCallback(
-      device_sync::mojom::NetworkRequestResult result_code,
-      bool expected_to_notify_observer_and_start_retry_timer) {
-    if (features::ShouldUseV1DeviceSync()) {
-      fake_device_sync_client_->InvokePendingSetSoftwareFeatureStateCallback(
-          result_code);
-    } else {
-      fake_device_sync_client_->InvokePendingSetFeatureStatusCallback(
-          result_code);
-    }
-
-    EXPECT_EQ(expected_to_notify_observer_and_start_retry_timer,
-              mock_timer_->IsRunning());
-  }
-
-  void SetWifiSyncHostInDeviceSyncClient(
-      const absl::optional<multidevice::RemoteDeviceRef>& host_device,
-      bool enabled) {
-    GetMutableRemoteDevice(*host_device)
-        ->software_features[multidevice::SoftwareFeature::kWifiSyncHost] =
-        (enabled ? multidevice::SoftwareFeatureState::kEnabled
-                 : multidevice::SoftwareFeatureState::kSupported);
-    fake_device_sync_client_->NotifyNewDevicesSynced();
-  }
-
-  void SetFeatureFlags(bool use_v1_devicesync, bool enable_wifi_sync) {
-    std::vector<base::Feature> enabled_features;
-    std::vector<base::Feature> disabled_features;
-
-    // These flags have no direct effect of on the wifi sync feature manager;
-    // however, v2 Enrollment and DeviceSync must be enabled before v1
-    // DeviceSync can be disabled.
-    enabled_features.push_back(chromeos::features::kCryptAuthV2Enrollment);
-    enabled_features.push_back(chromeos::features::kCryptAuthV2DeviceSync);
-
-    if (use_v1_devicesync) {
-      disabled_features.push_back(
-          chromeos::features::kDisableCryptAuthV1DeviceSync);
-    } else {
-      enabled_features.push_back(
-          chromeos::features::kDisableCryptAuthV1DeviceSync);
-    }
-
-    if (enable_wifi_sync) {
-      enabled_features.push_back(chromeos::features::kWifiSyncAndroid);
-    } else {
-      disabled_features.push_back(chromeos::features::kWifiSyncAndroid);
-    }
-
-    scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
-  }
-
-  FakeHostStatusProvider* fake_host_status_provider() {
-    return fake_host_status_provider_.get();
-  }
-
-  device_sync::FakeDeviceSyncClient* fake_device_sync_client() {
-    return fake_device_sync_client_.get();
-  }
-
-  FakeAccountStatusChangeDelegate* fake_account_status_change_delegate() {
-    return fake_account_status_change_delegate_.get();
-  }
-
-  void FlushDelegateNotifier() {
-    fake_account_status_change_delegate_notifier_->FlushForTesting();
-  }
-
-  base::MockOneShotTimer* mock_timer() { return mock_timer_; }
-
-  WifiSyncFeatureManager* delegate() { return delegate_.get(); }
-
-  sync_preferences::TestingPrefServiceSyncable* test_pref_service() {
-    return test_pref_service_.get();
-  }
-
-  const multidevice::RemoteDeviceRefList& test_devices() const {
-    return test_devices_;
-  }
-
-  session_manager::SessionManager* session_manager() {
-    return session_manager_.get();
-  }
-
- private:
-  base::test::TaskEnvironment task_environment_;
-  multidevice::RemoteDeviceRefList test_devices_;
-
-  std::unique_ptr<session_manager::SessionManager> session_manager_;
-  std::unique_ptr<FakeHostStatusProvider> fake_host_status_provider_;
-  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable>
-      test_pref_service_;
-  std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
-  std::unique_ptr<FakeAccountStatusChangeDelegateNotifier>
-      fake_account_status_change_delegate_notifier_;
-  std::unique_ptr<FakeAccountStatusChangeDelegate>
-      fake_account_status_change_delegate_;
-
-  base::MockOneShotTimer* mock_timer_;
-
-  std::unique_ptr<WifiSyncFeatureManager> delegate_;
-
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest, Success) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(test_devices()[0] /* initial_host */);
-
-  // Attempt to enable wifi sync on host device and succeed
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  SetIsWifiSyncEnabled(true);
-  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_TRUE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-  InvokePendingSetWifiSyncHostNetworkRequestCallback(
-      device_sync::mojom::NetworkRequestResult::kSuccess,
-      false /* expected_to_notify_observer_and_start_retry_timer */);
-  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
-  SetWifiSyncHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
-
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kEnabled);
-
-  // Attempt to disable wifi sync on host device and succeed
-  SetIsWifiSyncEnabled(false);
-  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kEnabled);
-
-  InvokePendingSetWifiSyncHostNetworkRequestCallback(
-      device_sync::mojom::NetworkRequestResult::kSuccess,
-      false /* expected_to_notify_observer_and_start_retry_timer */);
-  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
-  SetWifiSyncHostInDeviceSyncClient(test_devices()[0], false /* enabled */);
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       NewDevicesSyncedBeforeCallback) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(test_devices()[0] /* initial_host */);
-
-  // Attempt to enable wifi sync on host device and succeed
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  SetIsWifiSyncEnabled(true);
-  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_TRUE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-  // Triggers OnNewDevicesSynced
-  SetWifiSyncHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
-  // Triggers Success Callback
-  InvokePendingSetWifiSyncHostNetworkRequestCallback(
-      device_sync::mojom::NetworkRequestResult::kSuccess,
-      false /* expected_to_notify_observer_and_start_retry_timer */);
-  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
-
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kEnabled);
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest, Failure) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(test_devices()[0] /* initial_host */);
-
-  // Attempt to enable wifi sync on host device and fail
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  SetIsWifiSyncEnabled(true);
-  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_TRUE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  InvokePendingSetWifiSyncHostNetworkRequestCallback(
-      device_sync::mojom::NetworkRequestResult::kOffline,
-      true /* expected_to_notify_observer_and_start_retry_timer */);
-  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  // A retry should have been scheduled, so fire the timer to start the retry.
-  mock_timer()->Fire();
-
-  // Simulate another failure.
-  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_TRUE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  InvokePendingSetWifiSyncHostNetworkRequestCallback(
-      device_sync::mojom::NetworkRequestResult::kOffline,
-      true /* expected_to_notify_observer_and_start_retry_timer */);
-  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       MultipleRequests_FirstFail_ThenSucceed) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(test_devices()[0] /* initial_host */);
-
-  // Attempt to enable wifi sync on host device and fail
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  SetIsWifiSyncEnabled(true);
-  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_TRUE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  InvokePendingSetWifiSyncHostNetworkRequestCallback(
-      device_sync::mojom::NetworkRequestResult::kOffline,
-      true /* expected_to_notify_observer_and_start_retry_timer */);
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  // The retry timer is running; however, instead of relying on that, call
-  // SetIsWifiSyncEnabled() again to trigger an immediate
-  // retry without the timer.
-  SetIsWifiSyncEnabled(true);
-  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_TRUE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  InvokePendingSetWifiSyncHostNetworkRequestCallback(
-      device_sync::mojom::NetworkRequestResult::kSuccess,
-      false /* expected_to_notify_observer_and_start_retry_timer */);
-  SetWifiSyncHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
-  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kEnabled);
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       PendingRequest_NoSyncedHostDevice) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(test_devices()[0] /* initial_host */);
-
-  // Attempt to enable wifi sync on test_device 0
-  SetIsWifiSyncEnabled(true);
-  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_TRUE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  // Fail to set wifi sync on test_device 0
-  InvokePendingSetWifiSyncHostNetworkRequestCallback(
-      device_sync::mojom::NetworkRequestResult::kOffline,
-      true /* expected_to_notify_observer_and_start_retry_timer */);
-  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-  EXPECT_TRUE(mock_timer()->IsRunning());
-
-  // Remove synced device. This should remove the pending request and stop the
-  // retry timer.
-  SetHostInDeviceSyncClient(absl::nullopt);
-  SetHostWithStatus(absl::nullopt);
-  EXPECT_FALSE(mock_timer()->IsRunning());
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       InitialPendingEnableRequest_NoInitialDevice) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(absl::nullopt /* initial_host */,
-                 kPendingEnable /* initial_pending_wifi_sync_request*/);
-
-  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       InitialPendingEnableRequest_Success) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(test_devices()[0] /* initial_host */,
-                 kPendingEnable /* initial_pending_wifi_sync_request*/);
-
-  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_TRUE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  InvokePendingSetWifiSyncHostNetworkRequestCallback(
-      device_sync::mojom::NetworkRequestResult::kSuccess,
-      false /* expected_to_notify_observer_and_start_retry_timer */);
-  SetWifiSyncHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
-  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kEnabled);
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       MultiplePendingRequests_EnableDisable) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(test_devices()[0] /* initial_host */);
-
-  // Attempt to enable->disable->enable wifi sync without invoking any
-  // callbacks.
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  SetIsWifiSyncEnabled(true);
-  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_TRUE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  // Wifi sync is already disabled on back-end so there should be no new pending
-  // request
-  SetIsWifiSyncEnabled(false);
-  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       PendingRequest_SyncedHostBecomesUnverified) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(test_devices()[0] /* initial_host */,
-                 kPendingEnable /* initial_pending_wifi_sync_request */);
-
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);
-
-  EXPECT_EQ(
-      test_pref_service()->GetInteger(kPendingWifiSyncRequestEnabledPrefName),
-      kPendingNone);
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       Retrying_SyncedHostBecomesUnverified) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(test_devices()[0] /* initial_host */);
-
-  SetIsWifiSyncEnabled(true);
-  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_TRUE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  InvokePendingSetWifiSyncHostNetworkRequestCallback(
-      device_sync::mojom::NetworkRequestResult::kOffline,
-      true /* expected_to_notify_observer_and_start_retry_timer */);
-  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_TRUE(mock_timer()->IsRunning());
-
-  // Host becomes unverified, this should stop timer and clear pending request
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);
-  EXPECT_EQ(
-      test_pref_service()->GetInteger(kPendingWifiSyncRequestEnabledPrefName),
-      kPendingNone);
-  EXPECT_FALSE(mock_timer()->IsRunning());
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       FailureCallback_SyncedHostBecomesUnverified) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(test_devices()[0] /* initial_host */);
-
-  SetIsWifiSyncEnabled(true);
-  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_TRUE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  // Set host unverified. This should reset pending request.
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);
-  EXPECT_EQ(
-      test_pref_service()->GetInteger(kPendingWifiSyncRequestEnabledPrefName),
-      kPendingNone);
-
-  // Invoke failure callback. No retry should be scheduled.
-  InvokePendingSetWifiSyncHostNetworkRequestCallback(
-      device_sync::mojom::NetworkRequestResult::kOffline,
-      false /* expected_to_notify_observer_and_start_retry_timer */);
-  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_FALSE(mock_timer()->IsRunning());
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       NoVerifiedHost_AttemptToEnable) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(test_devices()[0] /* initial_host */);
-
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);
-
-  // Attempt to enable wifi sync on host device
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  SetIsWifiSyncEnabled(true);
-  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       StatusChangedOnRemoteDevice) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(test_devices()[0] /* initial_host */);
-
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  // Simulate enabled on a remote device.
-  SetWifiSyncHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
-  EXPECT_TRUE(delegate()->IsWifiSyncEnabled());
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       SimultaneousRequests_StartOff_ToggleOnOff) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(test_devices()[0] /* initial_host */);
-
-  // Attempt to enable
-  SetIsWifiSyncEnabled(true);
-  // Attempt to disable
-  SetIsWifiSyncEnabled(false);
-
-  // Only one network request should be in flight at a time
-  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
-
-  // Successfully enable on host
-  InvokePendingSetWifiSyncHostNetworkRequestCallback(
-      device_sync::mojom::NetworkRequestResult::kSuccess,
-      false /* expected_to_notify_observer_and_start_retry_timer */);
-  SetWifiSyncHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kEnabled);
-
-  // A new network request should be scheduled to disable
-  EXPECT_EQ(1, GetSetHostNetworkRequestCallbackQueueSize());
-  InvokePendingSetWifiSyncHostNetworkRequestCallback(
-      device_sync::mojom::NetworkRequestResult::kSuccess,
-      false /* expected_to_notify_observer_and_start_retry_timer */);
-  SetWifiSyncHostInDeviceSyncClient(test_devices()[0], false /* enabled */);
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       SetPendingEnableOnVerify_HostSetLocallyThenHostVerified) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(absl::nullopt /* initial_host */);
-
-  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
-  // setup flow has been completed on the local device.
-  SetHostInDeviceSyncClient(test_devices()[0]);
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
-      test_devices()[0]);
-  EXPECT_EQ(
-      test_pref_service()->GetInteger(kPendingWifiSyncRequestEnabledPrefName),
-      kSetPendingEnableOnVerify);
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kHostVerified, test_devices()[0]);
-  EXPECT_EQ(
-      test_pref_service()->GetInteger(kPendingWifiSyncRequestEnabledPrefName),
-      kPendingEnable);
-  EXPECT_TRUE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-  InvokePendingSetWifiSyncHostNetworkRequestCallback(
-      device_sync::mojom::NetworkRequestResult::kSuccess,
-      false /* expected_to_notify_observer_and_start_retry_timer */);
-  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
-  SetWifiSyncHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
-
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kEnabled);
-}
-
-TEST_P(
-    MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-    SetPendingEnableOnVerify_HostSetLocallyThenHostSetNotVerifiedThenHostVerified) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(absl::nullopt /* initial_host */);
-
-  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
-  // setup flow has been completed on the local device.
-  SetHostInDeviceSyncClient(test_devices()[0]);
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
-      test_devices()[0]);
-  EXPECT_EQ(
-      test_pref_service()->GetInteger(kPendingWifiSyncRequestEnabledPrefName),
-      kSetPendingEnableOnVerify);
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);
-  EXPECT_EQ(
-      test_pref_service()->GetInteger(kPendingWifiSyncRequestEnabledPrefName),
-      kSetPendingEnableOnVerify);
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kHostVerified, test_devices()[0]);
-  EXPECT_EQ(
-      test_pref_service()->GetInteger(kPendingWifiSyncRequestEnabledPrefName),
-      kPendingEnable);
-  EXPECT_TRUE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-  InvokePendingSetWifiSyncHostNetworkRequestCallback(
-      device_sync::mojom::NetworkRequestResult::kSuccess,
-      false /* expected_to_notify_observer_and_start_retry_timer */);
-  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
-  SetWifiSyncHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
-
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kEnabled);
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       SetPendingEnableOnVerify_WifiSyncFlagOff) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  false /* enable_wifi_sync */);
-  CreateDelegate(absl::nullopt /* initial_host */);
-
-  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
-  // setup flow has been completed on the local device.
-  SetHostInDeviceSyncClient(test_devices()[0]);
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
-      test_devices()[0]);
-  EXPECT_EQ(
-      test_pref_service()->GetInteger(kPendingWifiSyncRequestEnabledPrefName),
-      kPendingNone);
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       SetPendingEnableOnVerify_WifiSyncNotAllowedByPolicy) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  // Disable by policy
-  test_pref_service()->SetBoolean(kWifiSyncAllowedPrefName, false);
-  CreateDelegate(absl::nullopt /* initial_host */);
-
-  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
-  // setup flow has been completed on the local device.
-  SetHostInDeviceSyncClient(test_devices()[0]);
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
-      test_devices()[0]);
-  EXPECT_EQ(
-      test_pref_service()->GetInteger(kPendingWifiSyncRequestEnabledPrefName),
-      kPendingNone);
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       SetPendingEnableOnVerify_WifiSyncNotSupportedOnHostDevice) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(absl::nullopt /* initial_host */);
-  GetMutableRemoteDevice(test_devices()[0])
-      ->software_features[multidevice::SoftwareFeature::kWifiSyncHost] =
-      multidevice::SoftwareFeatureState::kNotSupported;
-
-  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
-  // setup flow has been completed on the local device.
-  SetHostInDeviceSyncClient(test_devices()[0]);
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
-      test_devices()[0]);
-  EXPECT_EQ(
-      test_pref_service()->GetInteger(kPendingWifiSyncRequestEnabledPrefName),
-      kPendingNone);
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       SetPendingEnableOnVerify_HostRemoved) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  CreateDelegate(absl::nullopt /* initial_host */);
-
-  // kHostSetLocallyButWaitingForBackendConfirmation is only possible if the
-  // setup flow has been completed on the local device.
-  SetHostInDeviceSyncClient(test_devices()[0]);
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kHostSetLocallyButWaitingForBackendConfirmation,
-      test_devices()[0]);
-  EXPECT_EQ(
-      test_pref_service()->GetInteger(kPendingWifiSyncRequestEnabledPrefName),
-      kSetPendingEnableOnVerify);
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-
-  // Host is added but not verified.
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kHostSetButNotYetVerified, test_devices()[0]);
-  EXPECT_EQ(
-      test_pref_service()->GetInteger(kPendingWifiSyncRequestEnabledPrefName),
-      kSetPendingEnableOnVerify);
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-
-  // Host is removed before it was verified. This simulates the user going
-  // through the forget phone flow before the phone was able to be verified.
-  // Wifi Sync should stop the enable attempt because it requires a paired host
-  // device that transitions from unverified to verified.
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kEligibleHostExistsButNoHostSet, absl::nullopt);
-  EXPECT_EQ(
-      test_pref_service()->GetInteger(kPendingWifiSyncRequestEnabledPrefName),
-      kPendingNone);
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       SetPendingEnableOnVerify_InitialPendingRequest) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kHostVerified, test_devices()[0]);
-  CreateDelegate(
-      test_devices()[0] /* initial_host */,
-      kSetPendingEnableOnVerify /* initial_pending_wifi_sync_request */);
-
-  EXPECT_TRUE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-  InvokePendingSetWifiSyncHostNetworkRequestCallback(
-      device_sync::mojom::NetworkRequestResult::kSuccess,
-      false /* expected_to_notify_observer_and_start_retry_timer */);
-  EXPECT_EQ(0, GetSetHostNetworkRequestCallbackQueueSize());
-  SetWifiSyncHostInDeviceSyncClient(test_devices()[0], true /* enabled */);
-
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kEnabled);
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       Notification_ShownOnFirstUnlockAfterPhoneEnabled) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kHostVerified, test_devices()[0]);
-  CreateDelegate(test_devices()[0] /* initial_host */,
-                 kPendingNone /* initial_pending_wifi_sync_request */);
-
-  EXPECT_FALSE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  // Simulate lock/unlock
-  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);
-  session_manager()->SetSessionState(session_manager::SessionState::ACTIVE);
-
-  FlushDelegateNotifier();
-
-  // Shown on first unlock.
-  EXPECT_EQ(1u, fake_account_status_change_delegate()
-                    ->num_eligible_for_wifi_sync_events_handled());
-
-  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);
-  session_manager()->SetSessionState(session_manager::SessionState::ACTIVE);
-
-  FlushDelegateNotifier();
-
-  // Not shown on second unlock.
-  EXPECT_EQ(1u, fake_account_status_change_delegate()
-                    ->num_eligible_for_wifi_sync_events_handled());
-}
-
-TEST_P(MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-       Notification_NotShownIfAlreadyEnabled) {
-  SetFeatureFlags(GetParam() /* use_v1_devicesync */,
-                  true /* enable_wifi_sync */);
-  fake_host_status_provider()->SetHostWithStatus(
-      mojom::HostStatus::kHostVerified, test_devices()[0]);
-  CreateDelegate(test_devices()[0] /* initial_host */,
-                 kPendingNone /* initial_pending_wifi_sync_request */);
-  SetIsWifiSyncEnabled(true);
-
-  EXPECT_TRUE(delegate()->IsWifiSyncEnabled());
-  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
-                multidevice::SoftwareFeature::kWifiSyncHost),
-            multidevice::SoftwareFeatureState::kSupported);
-
-  // Simulate lock/unlock
-  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);
-  session_manager()->SetSessionState(session_manager::SessionState::ACTIVE);
-
-  FlushDelegateNotifier();
-
-  EXPECT_EQ(0u, fake_account_status_change_delegate()
-                    ->num_eligible_for_wifi_sync_events_handled());
-}
-
-// Runs tests twice; once with v1 DeviceSync enabled and once with it disabled.
-// TODO(https://crbug.com/1019206): Remove when v1 DeviceSync is disabled,
-// when all devices should have an Instance ID.
-INSTANTIATE_TEST_SUITE_P(All,
-                         MultiDeviceSetupWifiSyncFeatureManagerImplTest,
-                         ::testing::Bool());
-
-}  // namespace multidevice_setup
-
-}  // namespace chromeos
diff --git a/chromeos/services/multidevice_setup/wifi_sync_notification_controller.cc b/chromeos/services/multidevice_setup/wifi_sync_notification_controller.cc
new file mode 100644
index 0000000..2fef931
--- /dev/null
+++ b/chromeos/services/multidevice_setup/wifi_sync_notification_controller.cc
@@ -0,0 +1,179 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/multidevice_setup/wifi_sync_notification_controller.h"
+
+#include <memory>
+
+#include "base/memory/ptr_util.h"
+#include "base/power_monitor/power_monitor.h"
+#include "chromeos/components/multidevice/logging/logging.h"
+#include "chromeos/components/multidevice/remote_device_ref.h"
+#include "chromeos/components/multidevice/software_feature.h"
+#include "chromeos/components/multidevice/software_feature_state.h"
+#include "chromeos/services/device_sync/public/cpp/device_sync_client.h"
+#include "chromeos/services/multidevice_setup/account_status_change_delegate_notifier.h"
+#include "chromeos/services/multidevice_setup/global_state_feature_manager.h"
+#include "chromeos/services/multidevice_setup/host_status_provider.h"
+#include "chromeos/services/multidevice_setup/public/cpp/prefs.h"
+#include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/session_manager/core/session_manager.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace chromeos {
+
+namespace multidevice_setup {
+
+const char kCanShowWifiSyncAnnouncementPrefName[] =
+    "multidevice_setup.can_show_wifi_sync_announcement";
+
+// static
+WifiSyncNotificationController::Factory*
+    WifiSyncNotificationController::Factory::test_factory_ = nullptr;
+
+// static
+std::unique_ptr<WifiSyncNotificationController>
+WifiSyncNotificationController::Factory::Create(
+    GlobalStateFeatureManager* wifi_sync_feature_manager,
+    HostStatusProvider* host_status_provider,
+    PrefService* pref_service,
+    device_sync::DeviceSyncClient* device_sync_client,
+    AccountStatusChangeDelegateNotifier* delegate_notifier) {
+  if (test_factory_) {
+    return test_factory_->CreateInstance(wifi_sync_feature_manager,
+                                         host_status_provider, pref_service,
+                                         device_sync_client, delegate_notifier);
+  }
+
+  return base::WrapUnique(new WifiSyncNotificationController(
+      wifi_sync_feature_manager, host_status_provider, pref_service,
+      device_sync_client, delegate_notifier));
+}
+
+// static
+void WifiSyncNotificationController::Factory::SetFactoryForTesting(
+    Factory* test_factory) {
+  test_factory_ = test_factory;
+}
+
+WifiSyncNotificationController::Factory::~Factory() = default;
+
+void WifiSyncNotificationController::RegisterPrefs(
+    PrefRegistrySimple* registry) {
+  registry->RegisterBooleanPref(kCanShowWifiSyncAnnouncementPrefName, true);
+}
+
+WifiSyncNotificationController::WifiSyncNotificationController(
+    GlobalStateFeatureManager* wifi_sync_feature_manager,
+    HostStatusProvider* host_status_provider,
+    PrefService* pref_service,
+    device_sync::DeviceSyncClient* device_sync_client,
+    AccountStatusChangeDelegateNotifier* delegate_notifier)
+    : wifi_sync_feature_manager_(wifi_sync_feature_manager),
+      host_status_provider_(host_status_provider),
+      pref_service_(pref_service),
+      device_sync_client_(device_sync_client),
+      delegate_notifier_(delegate_notifier) {
+  if (pref_service_->GetBoolean(kCanShowWifiSyncAnnouncementPrefName)) {
+    session_manager::SessionManager::Get()->AddObserver(this);
+    base::PowerMonitor::AddPowerSuspendObserver(this);
+    did_register_session_observers_ = true;
+  }
+}
+
+WifiSyncNotificationController::~WifiSyncNotificationController() {
+  if (did_register_session_observers_) {
+    session_manager::SessionManager::Get()->RemoveObserver(this);
+    base::PowerMonitor::RemovePowerSuspendObserver(this);
+  }
+}
+
+void WifiSyncNotificationController::OnSessionStateChanged() {
+  ShowAnnouncementNotificationIfEligible();
+}
+
+void WifiSyncNotificationController::OnResume() {
+  ShowAnnouncementNotificationIfEligible();
+}
+
+void WifiSyncNotificationController::ShowAnnouncementNotificationIfEligible() {
+  // Show the announcement notification when the device is unlocked and
+  // eligible for wi-fi sync.  This is done on unlock/resume to avoid showing
+  // it on the first sign-in when it would distract from showoff and other
+  // announcements.
+
+  if (session_manager::SessionManager::Get()->IsUserSessionBlocked()) {
+    return;
+  }
+
+  if (!IsFeatureAllowed(mojom::Feature::kWifiSync, pref_service_)) {
+    return;
+  }
+
+  if (!pref_service_->GetBoolean(kCanShowWifiSyncAnnouncementPrefName)) {
+    return;
+  }
+
+  if (!IsWifiSyncSupported()) {
+    return;
+  }
+
+  if (host_status_provider_->GetHostWithStatus().host_status() !=
+          mojom::HostStatus::kHostVerified ||
+      wifi_sync_feature_manager_->IsFeatureEnabled()) {
+    pref_service_->SetBoolean(kCanShowWifiSyncAnnouncementPrefName, false);
+    return;
+  }
+
+  if (!delegate_notifier_->delegate()) {
+    return;
+  }
+
+  delegate_notifier_->delegate()->OnBecameEligibleForWifiSync();
+  pref_service_->SetBoolean(kCanShowWifiSyncAnnouncementPrefName, false);
+}
+
+bool WifiSyncNotificationController::IsWifiSyncSupported() {
+  HostStatusProvider::HostStatusWithDevice host_with_status =
+      host_status_provider_->GetHostWithStatus();
+  if (host_with_status.host_status() != mojom::HostStatus::kHostVerified) {
+    return false;
+  }
+
+  absl::optional<multidevice::RemoteDeviceRef> host_device =
+      host_with_status.host_device();
+  if (!host_device) {
+    PA_LOG(ERROR) << "WifiSyncNotificationController::" << __func__
+                  << ": Host device unexpectedly null.";
+    return false;
+  }
+
+  if (host_device->GetSoftwareFeatureState(
+          multidevice::SoftwareFeature::kWifiSyncHost) ==
+      multidevice::SoftwareFeatureState::kNotSupported) {
+    return false;
+  }
+
+  absl::optional<multidevice::RemoteDeviceRef> local_device =
+      device_sync_client_->GetLocalDeviceMetadata();
+  if (!local_device) {
+    PA_LOG(ERROR) << "WifiSyncNotificationController::" << __func__
+                  << ": Local device unexpectedly null.";
+    return false;
+  }
+
+  if (local_device->GetSoftwareFeatureState(
+          multidevice::SoftwareFeature::kWifiSyncClient) ==
+      multidevice::SoftwareFeatureState::kNotSupported) {
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace multidevice_setup
+
+}  // namespace chromeos
diff --git a/chromeos/services/multidevice_setup/wifi_sync_notification_controller.h b/chromeos/services/multidevice_setup/wifi_sync_notification_controller.h
new file mode 100644
index 0000000..f10a35d9
--- /dev/null
+++ b/chromeos/services/multidevice_setup/wifi_sync_notification_controller.h
@@ -0,0 +1,99 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_SERVICES_MULTIDEVICE_SETUP_WIFI_SYNC_NOTIFICATION_CONTROLLER_H_
+#define CHROMEOS_SERVICES_MULTIDEVICE_SETUP_WIFI_SYNC_NOTIFICATION_CONTROLLER_H_
+
+#include <memory>
+
+#include "base/memory/weak_ptr.h"
+#include "base/power_monitor/power_observer.h"
+#include "chromeos/services/device_sync/public/cpp/device_sync_client.h"
+#include "components/session_manager/core/session_manager_observer.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace chromeos {
+
+namespace multidevice_setup {
+
+// Pref to track whether the announcement notification can be shown the next
+// time the device is unlocked with a verified host and wi-fi sync supported but
+// disabled.
+extern const char kCanShowWifiSyncAnnouncementPrefName[];
+
+class AccountStatusChangeDelegateNotifier;
+class GlobalStateFeatureManager;
+class HostStatusProvider;
+
+// Controls the setup notification for Wifi Sync.
+class WifiSyncNotificationController
+    : public base::PowerSuspendObserver,
+      public session_manager::SessionManagerObserver {
+ public:
+  class Factory {
+   public:
+    static std::unique_ptr<WifiSyncNotificationController> Create(
+        GlobalStateFeatureManager* wifi_sync_feature_manager,
+        HostStatusProvider* host_status_provider,
+        PrefService* pref_service,
+        device_sync::DeviceSyncClient* device_sync_client,
+        AccountStatusChangeDelegateNotifier* delegate_notifier);
+    static void SetFactoryForTesting(Factory* test_factory);
+
+   protected:
+    virtual ~Factory();
+    virtual std::unique_ptr<WifiSyncNotificationController> CreateInstance(
+        GlobalStateFeatureManager* wifi_sync_feature_manager,
+        HostStatusProvider* host_status_provider,
+        PrefService* pref_service,
+        device_sync::DeviceSyncClient* device_sync_client,
+        AccountStatusChangeDelegateNotifier* delegate_notifier) = 0;
+
+   private:
+    static Factory* test_factory_;
+  };
+
+  static void RegisterPrefs(PrefRegistrySimple* registry);
+
+  ~WifiSyncNotificationController() override;
+  WifiSyncNotificationController(const WifiSyncNotificationController&) =
+      delete;
+  WifiSyncNotificationController& operator=(
+      const WifiSyncNotificationController&) = delete;
+
+ private:
+  WifiSyncNotificationController(
+      GlobalStateFeatureManager* wifi_sync_feature_manager,
+      HostStatusProvider* host_status_provider,
+      PrefService* pref_service,
+      device_sync::DeviceSyncClient* device_sync_client,
+      AccountStatusChangeDelegateNotifier* delegate_notifier);
+
+  // SessionManagerObserver:
+  void OnSessionStateChanged() override;
+
+  // PowerSuspendObserver:
+  void OnResume() override;
+
+  void ShowAnnouncementNotificationIfEligible();
+  bool IsWifiSyncSupported();
+
+  GlobalStateFeatureManager* wifi_sync_feature_manager_;
+  HostStatusProvider* host_status_provider_;
+  PrefService* pref_service_;
+  device_sync::DeviceSyncClient* device_sync_client_;
+  AccountStatusChangeDelegateNotifier* delegate_notifier_;
+
+  bool did_register_session_observers_ = false;
+
+  base::WeakPtrFactory<WifiSyncNotificationController> weak_ptr_factory_{this};
+};
+
+}  // namespace multidevice_setup
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_MULTIDEVICE_SETUP_WIFI_SYNC_NOTIFICATION_CONTROLLER_H_
diff --git a/chromeos/services/multidevice_setup/wifi_sync_notification_controller_unittest.cc b/chromeos/services/multidevice_setup/wifi_sync_notification_controller_unittest.cc
new file mode 100644
index 0000000..bc9fa295
--- /dev/null
+++ b/chromeos/services/multidevice_setup/wifi_sync_notification_controller_unittest.cc
@@ -0,0 +1,251 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/multidevice_setup/wifi_sync_notification_controller.h"
+
+#include <memory>
+
+#include "ash/constants/ash_features.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "chromeos/components/multidevice/remote_device_test_util.h"
+#include "chromeos/components/multidevice/software_feature.h"
+#include "chromeos/components/multidevice/software_feature_state.h"
+#include "chromeos/services/device_sync/public/cpp/fake_device_sync_client.h"
+#include "chromeos/services/multidevice_setup/fake_account_status_change_delegate.h"
+#include "chromeos/services/multidevice_setup/fake_account_status_change_delegate_notifier.h"
+#include "chromeos/services/multidevice_setup/fake_global_state_feature_manager.h"
+#include "chromeos/services/multidevice_setup/fake_host_status_provider.h"
+#include "chromeos/services/multidevice_setup/public/cpp/prefs.h"
+#include "chromeos/services/multidevice_setup/public/mojom/multidevice_setup.mojom.h"
+#include "components/session_manager/core/session_manager.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace chromeos {
+
+namespace multidevice_setup {
+
+namespace {
+
+const size_t kNumTestDevices = 4;
+
+}  // namespace
+
+class MultiDeviceSetupWifiSyncNotificationControllerTest
+    : public testing::Test {
+ public:
+  MultiDeviceSetupWifiSyncNotificationControllerTest(
+      const MultiDeviceSetupWifiSyncNotificationControllerTest&) = delete;
+  MultiDeviceSetupWifiSyncNotificationControllerTest& operator=(
+      const MultiDeviceSetupWifiSyncNotificationControllerTest&) = delete;
+
+ protected:
+  MultiDeviceSetupWifiSyncNotificationControllerTest()
+      : test_devices_(
+            multidevice::CreateRemoteDeviceRefListForTest(kNumTestDevices)) {}
+  ~MultiDeviceSetupWifiSyncNotificationControllerTest() override = default;
+
+  // testing::Test:
+  void SetUp() override {
+    scoped_feature_list_.InitAndEnableFeature(
+        chromeos::features::kWifiSyncAndroid);
+    SetWifiSyncSupportedInDeviceSyncClient();
+
+    fake_host_status_provider_ = std::make_unique<FakeHostStatusProvider>();
+
+    test_pref_service_ =
+        std::make_unique<sync_preferences::TestingPrefServiceSyncable>();
+    WifiSyncNotificationController::RegisterPrefs(
+        test_pref_service_->registry());
+    // Allow Wifi Sync by policy
+    test_pref_service_->registry()->RegisterBooleanPref(
+        kWifiSyncAllowedPrefName, true);
+    session_manager_ = std::make_unique<session_manager::SessionManager>();
+    fake_device_sync_client_ =
+        std::make_unique<device_sync::FakeDeviceSyncClient>();
+    fake_device_sync_client_->set_synced_devices(test_devices_);
+    fake_account_status_change_delegate_ =
+        std::make_unique<FakeAccountStatusChangeDelegate>();
+    fake_account_status_change_delegate_notifier_ =
+        std::make_unique<FakeAccountStatusChangeDelegateNotifier>();
+    fake_account_status_change_delegate_notifier_
+        ->SetAccountStatusChangeDelegateRemote(
+            fake_account_status_change_delegate_->GenerateRemote());
+    fake_account_status_change_delegate_notifier_->FlushForTesting();
+    multidevice::RemoteDeviceRef local_device =
+        multidevice::CreateRemoteDeviceRefForTest();
+    GetMutableRemoteDevice(local_device)
+        ->software_features[multidevice::SoftwareFeature::kWifiSyncClient] =
+        multidevice::SoftwareFeatureState::kSupported;
+    fake_device_sync_client_->set_local_device_metadata(local_device);
+  }
+
+  void TearDown() override {}
+
+  void SetHostInDeviceSyncClient(
+      const absl::optional<multidevice::RemoteDeviceRef>& host_device) {
+    for (const auto& remote_device : test_devices_) {
+      bool should_be_host =
+          host_device != absl::nullopt &&
+          ((!remote_device.instance_id().empty() &&
+            host_device->instance_id() == remote_device.instance_id()) ||
+           (!remote_device.GetDeviceId().empty() &&
+            host_device->GetDeviceId() == remote_device.GetDeviceId()));
+
+      GetMutableRemoteDevice(remote_device)
+          ->software_features
+              [multidevice::SoftwareFeature::kBetterTogetherHost] =
+          should_be_host ? multidevice::SoftwareFeatureState::kEnabled
+                         : multidevice::SoftwareFeatureState::kSupported;
+    }
+    fake_device_sync_client_->NotifyNewDevicesSynced();
+  }
+
+  void SetWifiSyncSupportedInDeviceSyncClient() {
+    for (const auto& remote_device : test_devices_) {
+      GetMutableRemoteDevice(remote_device)
+          ->software_features[multidevice::SoftwareFeature::kWifiSyncHost] =
+          multidevice::SoftwareFeatureState::kSupported;
+    }
+  }
+
+  void CreateDelegate(
+      const absl::optional<multidevice::RemoteDeviceRef>& initial_host) {
+    SetHostInDeviceSyncClient(initial_host);
+    SetHostWithStatus(initial_host);
+
+    feature_manager_ = std::make_unique<FakeGlobalStateFeatureManager>();
+    notification_controller_ = WifiSyncNotificationController::Factory::Create(
+        feature_manager_.get(), fake_host_status_provider_.get(),
+        test_pref_service_.get(), fake_device_sync_client_.get(),
+        fake_account_status_change_delegate_notifier_.get());
+  }
+
+  void SetHostWithStatus(
+      const absl::optional<multidevice::RemoteDeviceRef>& host_device) {
+    mojom::HostStatus host_status =
+        (host_device == absl::nullopt ? mojom::HostStatus::kNoEligibleHosts
+                                      : mojom::HostStatus::kHostVerified);
+    fake_host_status_provider_->SetHostWithStatus(host_status, host_device);
+  }
+
+  void SetIsFeatureEnabled(bool enabled) {
+    feature_manager_->SetIsFeatureEnabled(enabled);
+
+    HostStatusProvider::HostStatusWithDevice host_with_status =
+        fake_host_status_provider_->GetHostWithStatus();
+    if (host_with_status.host_status() != mojom::HostStatus::kHostVerified) {
+      return;
+    }
+
+    multidevice::RemoteDeviceRef host_device = *host_with_status.host_device();
+
+    bool enabled_on_backend =
+        (host_device.GetSoftwareFeatureState(
+             multidevice::SoftwareFeature::kWifiSyncHost) ==
+         multidevice::SoftwareFeatureState::kEnabled);
+    bool pending_request_state_same_as_backend =
+        (enabled == enabled_on_backend);
+
+    if (pending_request_state_same_as_backend) {
+      return;
+    }
+  }
+
+  FakeHostStatusProvider* fake_host_status_provider() {
+    return fake_host_status_provider_.get();
+  }
+
+  FakeAccountStatusChangeDelegate* fake_account_status_change_delegate() {
+    return fake_account_status_change_delegate_.get();
+  }
+
+  void FlushDelegateNotifier() {
+    fake_account_status_change_delegate_notifier_->FlushForTesting();
+  }
+
+  const multidevice::RemoteDeviceRefList& test_devices() const {
+    return test_devices_;
+  }
+
+  session_manager::SessionManager* session_manager() {
+    return session_manager_.get();
+  }
+
+ private:
+  base::test::TaskEnvironment task_environment_;
+  multidevice::RemoteDeviceRefList test_devices_;
+
+  std::unique_ptr<session_manager::SessionManager> session_manager_;
+  std::unique_ptr<FakeHostStatusProvider> fake_host_status_provider_;
+  std::unique_ptr<sync_preferences::TestingPrefServiceSyncable>
+      test_pref_service_;
+  std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
+  std::unique_ptr<FakeAccountStatusChangeDelegateNotifier>
+      fake_account_status_change_delegate_notifier_;
+  std::unique_ptr<FakeAccountStatusChangeDelegate>
+      fake_account_status_change_delegate_;
+
+  std::unique_ptr<GlobalStateFeatureManager> feature_manager_;
+  std::unique_ptr<WifiSyncNotificationController> notification_controller_;
+
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(MultiDeviceSetupWifiSyncNotificationControllerTest,
+       Notification_ShownOnFirstUnlockAfterPhoneEnabled) {
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kHostVerified, test_devices()[0]);
+  CreateDelegate(test_devices()[0] /* initial_host */);
+
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
+                multidevice::SoftwareFeature::kWifiSyncHost),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  // Simulate lock/unlock
+  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);
+  session_manager()->SetSessionState(session_manager::SessionState::ACTIVE);
+
+  FlushDelegateNotifier();
+
+  // Shown on first unlock.
+  EXPECT_EQ(1u, fake_account_status_change_delegate()
+                    ->num_eligible_for_wifi_sync_events_handled());
+
+  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);
+  session_manager()->SetSessionState(session_manager::SessionState::ACTIVE);
+
+  FlushDelegateNotifier();
+
+  // Not shown on second unlock.
+  EXPECT_EQ(1u, fake_account_status_change_delegate()
+                    ->num_eligible_for_wifi_sync_events_handled());
+}
+
+TEST_F(MultiDeviceSetupWifiSyncNotificationControllerTest,
+       Notification_NotShownIfAlreadyEnabled) {
+  fake_host_status_provider()->SetHostWithStatus(
+      mojom::HostStatus::kHostVerified, test_devices()[0]);
+  CreateDelegate(test_devices()[0] /* initial_host */);
+  SetIsFeatureEnabled(true);
+
+  EXPECT_EQ(test_devices()[0].GetSoftwareFeatureState(
+                multidevice::SoftwareFeature::kWifiSyncHost),
+            multidevice::SoftwareFeatureState::kSupported);
+
+  // Simulate lock/unlock
+  session_manager()->SetSessionState(session_manager::SessionState::LOCKED);
+  session_manager()->SetSessionState(session_manager::SessionState::ACTIVE);
+
+  FlushDelegateNotifier();
+
+  EXPECT_EQ(0u, fake_account_status_change_delegate()
+                    ->num_eligible_for_wifi_sync_events_handled());
+}
+
+}  // namespace multidevice_setup
+
+}  // namespace chromeos
diff --git a/chromeos/services/secure_channel/wire_message.cc b/chromeos/services/secure_channel/wire_message.cc
index 03c8ecb..1e07889 100644
--- a/chromeos/services/secure_channel/wire_message.cc
+++ b/chromeos/services/secure_channel/wire_message.cc
@@ -122,7 +122,8 @@
   // of the header. Because this value is received over the network, we must
   // convert from big endian to host byte order.
   base::BigEndianReader reader(
-      serialized_message.data() + kNumBytesInHeaderProtocolVersion,
+      reinterpret_cast<const uint8_t*>(serialized_message.data()) +
+          kNumBytesInHeaderProtocolVersion,
       serialized_message.size() - kNumBytesInHeaderProtocolVersion);
 
   size_t expected_message_length;
diff --git a/chromeos/tast_control.gni b/chromeos/tast_control.gni
index 00dd721..786ea13 100644
--- a/chromeos/tast_control.gni
+++ b/chromeos/tast_control.gni
@@ -45,6 +45,9 @@
   # crbug.com/1263234
   "quicksettings.ManagedDeviceInfo",
 
+  # crbug.com/1268959
+  "quicksettings.OpenSettings",
+
   # b/201197372
   "crostini.AppEmacs",
 
@@ -82,6 +85,9 @@
 
   # https://crbug.com/1269075: Flaky.
   "shelf.AutoHideSmoke.clamshell_mode",
+
+  # https://crbug.com/1269124: Flaky.
+  "quicksettings.LockScreen.no_battery",
 ]
 
 # To disable a specific test in lacros_all_tast_tests, add it the following
diff --git a/components/BUILD.gn b/components/BUILD.gn
index e896ae5e..8b3147c 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -342,6 +342,7 @@
       "//components/translate/content/browser:unit_tests",
       "//components/translate/content/renderer:unit_tests",
       "//components/ukm/content:unit_tests",
+      "//components/url_rewrite:unit_tests",
       "//components/value_store:unit_tests",
       "//components/visitedlink/test:unit_tests",
       "//components/web_cache/browser:unit_tests",
diff --git a/components/app_restore/app_restore_utils.cc b/components/app_restore/app_restore_utils.cc
index 5ce6a03..68ced6d 100644
--- a/components/app_restore/app_restore_utils.cc
+++ b/components/app_restore/app_restore_utils.cc
@@ -5,6 +5,7 @@
 #include "components/app_restore/app_restore_utils.h"
 
 #include "ash/constants/app_types.h"
+#include "ash/constants/ash_features.h"
 #include "base/bind.h"
 #include "components/app_restore/desk_template_read_handler.h"
 #include "components/app_restore/features.h"
@@ -21,7 +22,7 @@
 // Always use the full restore ARC data if ARC apps for desks templates is not
 // enabled.
 bool ShouldUseFullRestoreArcData() {
-  return features::IsArcAppsForDesksTemplatesEnabled()
+  return ash::features::AreDesksTemplatesEnabled()
              ? full_restore::FullRestoreReadHandler::GetInstance()
                    ->IsFullRestoreRunning()
              : true;
diff --git a/components/app_restore/desk_template_read_handler.cc b/components/app_restore/desk_template_read_handler.cc
index fbfba20..aead62f 100644
--- a/components/app_restore/desk_template_read_handler.cc
+++ b/components/app_restore/desk_template_read_handler.cc
@@ -5,12 +5,12 @@
 #include "components/app_restore/desk_template_read_handler.h"
 
 #include "ash/constants/app_types.h"
+#include "ash/constants/ash_features.h"
 #include "base/bind.h"
 #include "base/files/file_path.h"
 #include "base/no_destructor.h"
 #include "components/app_restore/app_launch_info.h"
 #include "components/app_restore/app_restore_data.h"
-#include "components/app_restore/features.h"
 #include "components/app_restore/restore_data.h"
 #include "components/app_restore/window_info.h"
 #include "components/app_restore/window_properties.h"
@@ -43,7 +43,7 @@
 
   restore_data_ = std::move(restore_data);
 
-  if (!features::IsArcAppsForDesksTemplatesEnabled())
+  if (!ash::features::AreDesksTemplatesEnabled())
     return;
 
   arc_read_handler_.reset();
diff --git a/components/app_restore/features.cc b/components/app_restore/features.cc
index 0a43463..9b114cc 100644
--- a/components/app_restore/features.cc
+++ b/components/app_restore/features.cc
@@ -4,19 +4,6 @@
 
 #include "components/app_restore/features.h"
 
-namespace app_restore {
-namespace features {
-
-const base::Feature kArcAppsForDesksTemplates{
-    "ArcAppsForDesksTemplates", base::FEATURE_DISABLED_BY_DEFAULT};
-
-bool IsArcAppsForDesksTemplatesEnabled() {
-  return base::FeatureList::IsEnabled(kArcAppsForDesksTemplates);
-}
-
-}  // namespace features
-}  // namespace app_restore
-
 namespace full_restore {
 namespace features {
 
diff --git a/components/app_restore/features.h b/components/app_restore/features.h
index 4c76951..7528a13 100644
--- a/components/app_restore/features.h
+++ b/components/app_restore/features.h
@@ -8,18 +8,6 @@
 #include "base/component_export.h"
 #include "base/feature_list.h"
 
-namespace app_restore {
-namespace features {
-
-// Enables saving and launching ARC++ apps for desks templates.
-COMPONENT_EXPORT(APP_RESTORE)
-extern const base::Feature kArcAppsForDesksTemplates;
-
-COMPONENT_EXPORT(APP_RESTORE) bool IsArcAppsForDesksTemplatesEnabled();
-
-}  // namespace features
-}  // namespace app_restore
-
 namespace full_restore {
 namespace features {
 
diff --git a/components/arc/BUILD.gn b/components/arc/BUILD.gn
index 5ea3b0d2..3dab5eb 100644
--- a/components/arc/BUILD.gn
+++ b/components/arc/BUILD.gn
@@ -50,20 +50,6 @@
     "intent_helper/link_handler_model.cc",
     "intent_helper/link_handler_model.h",
     "intent_helper/open_url_delegate.h",
-    "keyboard_shortcut/arc_keyboard_shortcut_bridge.cc",
-    "keyboard_shortcut/arc_keyboard_shortcut_bridge.h",
-    "lock_screen/arc_lock_screen_bridge.cc",
-    "lock_screen/arc_lock_screen_bridge.h",
-    "memory/arc_memory_bridge.cc",
-    "memory/arc_memory_bridge.h",
-    "memory_pressure/arc_memory_pressure_bridge.cc",
-    "memory_pressure/arc_memory_pressure_bridge.h",
-    "metrics/arc_metrics_service.cc",
-    "metrics/arc_metrics_service.h",
-    "metrics/stability_metrics_manager.cc",
-    "metrics/stability_metrics_manager.h",
-    "midis/arc_midis_bridge.cc",
-    "midis/arc_midis_bridge.h",
     "net/always_on_vpn_manager.cc",
     "net/always_on_vpn_manager.h",
     "net/arc_net_host_impl.cc",
diff --git a/components/arc/intent_helper/link_handler_model.cc b/components/arc/intent_helper/link_handler_model.cc
index 53c4b874..f2c39d7 100644
--- a/components/arc/intent_helper/link_handler_model.cc
+++ b/components/arc/intent_helper/link_handler_model.cc
@@ -7,12 +7,12 @@
 #include <utility>
 
 #include "ash/components/arc/metrics/arc_metrics_constants.h"
+#include "ash/components/arc/metrics/arc_metrics_service.h"
 #include "base/bind.h"
 #include "base/memory/ptr_util.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/arc/intent_helper/arc_intent_helper_bridge.h"
-#include "components/arc/metrics/arc_metrics_service.h"
 #include "components/arc/session/arc_bridge_service.h"
 #include "components/arc/session/arc_service_manager.h"
 #include "components/google/core/common/google_util.h"
diff --git a/components/embedder_support/user_agent_utils.cc b/components/embedder_support/user_agent_utils.cc
index 678a975..447a607 100644
--- a/components/embedder_support/user_agent_utils.cc
+++ b/components/embedder_support/user_agent_utils.cc
@@ -242,6 +242,16 @@
   return version_info::GetProductNameAndVersionForUserAgent();
 }
 
+std::string GetFullUserAgent() {
+  std::string product = GetProduct();
+#if defined(OS_ANDROID)
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kUseMobileUserAgent))
+    product += " Mobile";
+#endif
+  return content::BuildUserAgentFromProduct(product);
+}
+
 std::string GetUserAgent() {
   base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
   if (command_line->HasSwitch(kUserAgent)) {
@@ -254,12 +264,7 @@
   if (base::FeatureList::IsEnabled(blink::features::kReduceUserAgent))
     return GetReducedUserAgent();
 
-  std::string product = GetProduct();
-#if defined(OS_ANDROID)
-  if (command_line->HasSwitch(switches::kUseMobileUserAgent))
-    product += " Mobile";
-#endif
-  return content::BuildUserAgentFromProduct(product);
+  return GetFullUserAgent();
 }
 
 std::string GetReducedUserAgent() {
diff --git a/components/embedder_support/user_agent_utils.h b/components/embedder_support/user_agent_utils.h
index 6fcbffb..cf8eb83 100644
--- a/components/embedder_support/user_agent_utils.h
+++ b/components/embedder_support/user_agent_utils.h
@@ -25,13 +25,16 @@
 // Returns the product used in building the user-agent.
 std::string GetProduct();
 
-// Returns the user agent string for Chrome. If the ReduceUserAgent
-// feature is enabled, this will return |GetReducedUserAgent|
-std::string GetUserAgent();
+// Returns the user agent string for Chrome.
+std::string GetFullUserAgent();
 
 // Returns the reduced user agent string for Chrome.
 std::string GetReducedUserAgent();
 
+// Returns the full or "reduced" user agent string, depending on the
+// UserAgentReduction enterprise policy and blink::features::kReduceUserAgent
+std::string GetUserAgent();
+
 // Returns UserAgentMetadata per the default policy.
 // This override is currently used in fuchsia, where the enterprise policy
 // is not relevant.
diff --git a/components/gcm_driver/crypto/message_payload_parser.cc b/components/gcm_driver/crypto/message_payload_parser.cc
index a147baa..175c2d8 100644
--- a/components/gcm_driver/crypto/message_payload_parser.cc
+++ b/components/gcm_driver/crypto/message_payload_parser.cc
@@ -37,7 +37,8 @@
   salt_ = std::string(message.substr(0, kSaltSize));
   message.remove_prefix(kSaltSize);
 
-  base::ReadBigEndian(message.data(), &record_size_);
+  base::ReadBigEndian(reinterpret_cast<const uint8_t*>(message.data()),
+                      &record_size_);
   message.remove_prefix(sizeof(record_size_));
 
   if (record_size_ < kMinimumRecordSize) {
@@ -46,7 +47,8 @@
   }
 
   uint8_t public_key_length;
-  base::ReadBigEndian(message.data(), &public_key_length);
+  base::ReadBigEndian(reinterpret_cast<const uint8_t*>(message.data()),
+                      &public_key_length);
   message.remove_prefix(sizeof(public_key_length));
 
   if (public_key_length != kUncompressedPointSize) {
diff --git a/components/history/core/browser/sync/typed_url_sync_metadata_database.cc b/components/history/core/browser/sync/typed_url_sync_metadata_database.cc
index 241e3d0..5b51accc 100644
--- a/components/history/core/browser/sync/typed_url_sync_metadata_database.cc
+++ b/components/history/core/browser/sync/typed_url_sync_metadata_database.cc
@@ -101,7 +101,8 @@
     const std::string& storage_key) {
   URLID storage_key_int = 0;
   DCHECK_EQ(storage_key.size(), sizeof(storage_key_int));
-  base::ReadBigEndian(storage_key.data(), &storage_key_int);
+  base::ReadBigEndian(reinterpret_cast<const uint8_t*>(storage_key.data()),
+                      &storage_key_int);
   // Make sure storage_key_int is set.
   DCHECK_NE(storage_key_int, 0);
   return storage_key_int;
diff --git a/components/optimization_guide/content/browser/page_content_annotations_model_manager.cc b/components/optimization_guide/content/browser/page_content_annotations_model_manager.cc
index 8fb829a..242f4f61 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_model_manager.cc
+++ b/components/optimization_guide/content/browser/page_content_annotations_model_manager.cc
@@ -237,6 +237,16 @@
           absl::nullopt);
 }
 
+void PageContentAnnotationsModelManager::SetUpPageVisibilityModel(
+    OptimizationGuideModelProvider* optimization_guide_model_provider) {
+  page_visibility_model_executor_ =
+      std::make_unique<PageVisibilityModelExecutor>(
+          optimization_guide_model_provider,
+          base::ThreadPool::CreateSequencedTaskRunner(
+              {base::MayBlock(), base::TaskPriority::BEST_EFFORT}),
+          absl::nullopt);
+}
+
 void PageContentAnnotationsModelManager::ExecutePageTopicsModel(
     const std::string& text,
     std::unique_ptr<history::VisitContentModelAnnotations> current_annotations,
@@ -511,6 +521,19 @@
     return;
   }
 
+  if (job->type() == AnnotationType::kContentVisibility) {
+    if (!page_visibility_model_executor_) {
+      job->FillWithNullOutputs();
+      job->OnComplete();
+      job.reset();
+      std::move(on_job_complete_callback).Run();
+      return;
+    }
+    page_visibility_model_executor_->ExecuteJob(
+        std::move(on_job_complete_callback), std::move(job));
+    return;
+  }
+
   // TODO(crbug/1249632): Actually run the model instead.
   content::GetUIThreadTaskRunner({})->PostTask(
       FROM_HERE,
diff --git a/components/optimization_guide/content/browser/page_content_annotations_model_manager.h b/components/optimization_guide/content/browser/page_content_annotations_model_manager.h
index 5b365a49..33b6229 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_model_manager.h
+++ b/components/optimization_guide/content/browser/page_content_annotations_model_manager.h
@@ -12,6 +12,7 @@
 #include "components/optimization_guide/core/page_content_annotation_job.h"
 #include "components/optimization_guide/core/page_content_annotations_common.h"
 #include "components/optimization_guide/core/page_topics_model_executor.h"
+#include "components/optimization_guide/core/page_visibility_model_executor.h"
 #include "components/optimization_guide/proto/page_topics_model_metadata.pb.h"
 #include "net/base/priority_queue.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -152,6 +153,12 @@
   void SetUpPageTopicsV2Model(
       OptimizationGuideModelProvider* optimization_guide_model_provider);
 
+  // Set up the machinery for execution of the page visibility model. This
+  // should only be run at construction.
+  // TODO(crbug/1266504): Actually call this based on a separate feature flag.
+  void SetUpPageVisibilityModel(
+      OptimizationGuideModelProvider* optimization_guide_model_provider);
+
   // Requests to execute the page topics model with |text|, populate
   // |current_annotations| with detected topics on success, and proceed to
   // execute any subsequent models.
@@ -213,6 +220,9 @@
   std::unique_ptr<PageTopicsModelExecutor>
       on_demand_page_topics_model_executor_;
 
+  // The model executor responsible for executing the page visibility model.
+  std::unique_ptr<PageVisibilityModelExecutor> page_visibility_model_executor_;
+
   // The model executor responsible for executing the page entities model.
   //
   // Can be nullptr if the page entities model will not be running for the
diff --git a/components/optimization_guide/content/browser/page_content_annotations_model_manager_unittest.cc b/components/optimization_guide/content/browser/page_content_annotations_model_manager_unittest.cc
index ccd8ff7..00ee252 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_model_manager_unittest.cc
+++ b/components/optimization_guide/content/browser/page_content_annotations_model_manager_unittest.cc
@@ -142,6 +142,28 @@
     RunUntilIdle();
   }
 
+  void SendPageVisibilityModelToExecutor(
+      const absl::optional<proto::Any>& model_metadata) {
+    model_manager()->SetUpPageVisibilityModel(model_observer_tracker());
+
+    base::FilePath source_root_dir;
+    base::PathService::Get(base::DIR_SOURCE_ROOT, &source_root_dir);
+    base::FilePath model_file_path =
+        source_root_dir.AppendASCII("components")
+            .AppendASCII("test")
+            .AppendASCII("data")
+            .AppendASCII("optimization_guide")
+            .AppendASCII("bert_page_topics_model.tflite");
+    std::unique_ptr<ModelInfo> model_info =
+        TestModelInfoBuilder()
+            .SetModelFilePath(model_file_path)
+            .SetModelMetadata(model_metadata)
+            .Build();
+    model_manager()->page_visibility_model_executor_->OnModelUpdated(
+        proto::OPTIMIZATION_TARGET_PAGE_VISIBILITY, *model_info);
+    RunUntilIdle();
+  }
+
   void SetPageEntitiesModelExecutor(
       const base::flat_map<std::string, std::vector<ScoredEntityMetadata>>&
           entries,
@@ -503,8 +525,6 @@
                             AnnotationType::kPageTopics);
   run_loop.Run();
 
-  // TODO(crbug/1249632): Check the corresponding output once the model is being
-  // run.
   ASSERT_EQ(result.size(), 1U);
   EXPECT_EQ(result[0].input(), "input");
   EXPECT_EQ(result[0].type(), AnnotationType::kPageTopics);
@@ -531,8 +551,6 @@
                             AnnotationType::kPageTopics);
   run_loop.Run();
 
-  // TODO(crbug/1249632): Check the corresponding output once the model is being
-  // run.
   ASSERT_EQ(result.size(), 1U);
   EXPECT_EQ(result[0].input(), "input");
   EXPECT_EQ(result[0].type(), AnnotationType::kPageTopics);
@@ -568,6 +586,45 @@
 
 TEST_F(PageContentAnnotationsModelManagerTest,
        BatchAnnotate_ContentVisibility) {
+  proto::Any any_metadata;
+  any_metadata.set_type_url(
+      "type.googleapis.com/com.foo.PageTopicsModelMetadata");
+  proto::PageTopicsModelMetadata page_topics_model_metadata;
+  page_topics_model_metadata.set_version(123);
+  page_topics_model_metadata.mutable_output_postprocessing_params()
+      ->mutable_visibility_params()
+      ->set_category_name("DO NOT EVALUATE");
+  page_topics_model_metadata.SerializeToString(any_metadata.mutable_value());
+  SendPageVisibilityModelToExecutor(any_metadata);
+
+  base::RunLoop run_loop;
+  std::vector<BatchAnnotationResult> result;
+  BatchAnnotationCallback callback = base::BindOnce(
+      [](base::RunLoop* run_loop,
+         std::vector<BatchAnnotationResult>* out_result,
+         const std::vector<BatchAnnotationResult>& in_result) {
+        *out_result = in_result;
+        run_loop->Quit();
+      },
+      &run_loop, &result);
+
+  // Running the actual model can take a while.
+  base::test::ScopedRunLoopTimeout scoped_timeout(FROM_HERE, base::Seconds(60));
+
+  model_manager()->Annotate(std::move(callback), {"input"},
+                            AnnotationType::kContentVisibility);
+  run_loop.Run();
+
+  ASSERT_EQ(result.size(), 1U);
+  EXPECT_EQ(result[0].input(), "input");
+  EXPECT_EQ(result[0].topics(), absl::nullopt);
+  EXPECT_EQ(result[0].entities(), absl::nullopt);
+  EXPECT_EQ(result[0].visibility_score(), absl::make_optional(-1.0));
+}
+
+TEST_F(PageContentAnnotationsModelManagerTest,
+       BatchAnnotate_ContentVisibilityNotAvailable) {
+  // Note that |SendPageVisibilityModelToExecutor| is not called.
   base::RunLoop run_loop;
   std::vector<BatchAnnotationResult> result;
   BatchAnnotationCallback callback = base::BindOnce(
@@ -583,8 +640,6 @@
                             AnnotationType::kContentVisibility);
   run_loop.Run();
 
-  // TODO(crbug/1249632): Check the corresponding output once the model is being
-  // run.
   ASSERT_EQ(result.size(), 1U);
   EXPECT_EQ(result[0].input(), "input");
   EXPECT_EQ(result[0].topics(), absl::nullopt);
diff --git a/components/optimization_guide/core/BUILD.gn b/components/optimization_guide/core/BUILD.gn
index 096e55a..1876c8e 100644
--- a/components/optimization_guide/core/BUILD.gn
+++ b/components/optimization_guide/core/BUILD.gn
@@ -121,6 +121,8 @@
       "page_entities_model_executor.h",
       "page_topics_model_executor.cc",
       "page_topics_model_executor.h",
+      "page_visibility_model_executor.cc",
+      "page_visibility_model_executor.h",
     ]
   }
 
@@ -256,6 +258,7 @@
       "model_validator_unittest.cc",
       "page_content_annotation_job_executor_unittest.cc",
       "page_topics_model_executor_unittest.cc",
+      "page_visibility_model_executor_unittest.cc",
       "test_model_executor.cc",
       "test_model_executor.h",
       "test_model_handler.h",
diff --git a/components/optimization_guide/core/execution_status.cc b/components/optimization_guide/core/execution_status.cc
index 1da6829..83bd577 100644
--- a/components/optimization_guide/core/execution_status.cc
+++ b/components/optimization_guide/core/execution_status.cc
@@ -14,8 +14,6 @@
       return "Success";
     case ExecutionStatus::kPending:
       return "Pending";
-    case ExecutionStatus::kErrorInternalError:
-      return "ErrorInternalError";
     case ExecutionStatus::kErrorModelFileNotAvailable:
       return "ErrorModelFileNotAvailable";
     case ExecutionStatus::kErrorModelFileNotValid:
diff --git a/components/optimization_guide/core/execution_status.h b/components/optimization_guide/core/execution_status.h
index f28a83a..c64a1c7 100644
--- a/components/optimization_guide/core/execution_status.h
+++ b/components/optimization_guide/core/execution_status.h
@@ -22,21 +22,17 @@
   // Execution is still pending.
   kPending = 2,
 
-  // Execution failed for some reason internal to Opt Guide. These failures
-  // should not happen and result in a DCHECK in non-production builds.
-  kErrorInternalError = 3,
-
   // Execution failed because the model file is not available.
-  kErrorModelFileNotAvailable = 4,
+  kErrorModelFileNotAvailable = 3,
 
   // Execution failed because the model file could not be loaded into TFLite.
-  kErrorModelFileNotValid = 5,
+  kErrorModelFileNotValid = 4,
 
   // Execution failed because the input was empty or otherwise invalid.
-  kErrorEmptyOrInvalidInput = 6,
+  kErrorEmptyOrInvalidInput = 5,
 
   // Execution failed because of an unknown error.
-  kErrorUnknown = 7,
+  kErrorUnknown = 6,
 
   kMaxValue = kErrorUnknown,
 };
diff --git a/components/optimization_guide/core/page_visibility_model_executor.cc b/components/optimization_guide/core/page_visibility_model_executor.cc
new file mode 100644
index 0000000..3263172
--- /dev/null
+++ b/components/optimization_guide/core/page_visibility_model_executor.cc
@@ -0,0 +1,85 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/optimization_guide/core/page_visibility_model_executor.h"
+
+#include "components/optimization_guide/core/optimization_guide_model_provider.h"
+#include "components/optimization_guide/proto/models.pb.h"
+#include "components/optimization_guide/proto/page_topics_model_metadata.pb.h"
+
+namespace optimization_guide {
+
+PageVisibilityModelExecutor::PageVisibilityModelExecutor(
+    OptimizationGuideModelProvider* model_provider,
+    scoped_refptr<base::SequencedTaskRunner> background_task_runner,
+    const absl::optional<proto::Any>& model_metadata)
+    : BertModelHandler(model_provider,
+                       background_task_runner,
+                       proto::OPTIMIZATION_TARGET_PAGE_VISIBILITY,
+                       model_metadata) {}
+PageVisibilityModelExecutor::~PageVisibilityModelExecutor() = default;
+
+void PageVisibilityModelExecutor::ExecuteOnSingleInput(
+    AnnotationType annotation_type,
+    const std::string& input,
+    base::OnceCallback<void(const BatchAnnotationResult&)> callback) {
+  ExecuteModelWithInput(
+      base::BindOnce(&PageVisibilityModelExecutor::
+                         PostprocessCategoriesToBatchAnnotationResult,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback),
+                     annotation_type, input),
+      input);
+}
+
+void PageVisibilityModelExecutor::PostprocessCategoriesToBatchAnnotationResult(
+    base::OnceCallback<void(const BatchAnnotationResult&)> callback,
+    AnnotationType annotation_type,
+    const std::string& input,
+    const absl::optional<std::vector<tflite::task::core::Category>>& output) {
+  DCHECK_EQ(annotation_type, AnnotationType::kContentVisibility);
+
+  absl::optional<double> visibility_score;
+  if (output) {
+    visibility_score = ExtractContentVisibilityFromModelOutput(*output);
+  }
+  std::move(callback).Run(BatchAnnotationResult::CreateContentVisibilityResult(
+      input, visibility_score));
+}
+
+absl::optional<double>
+PageVisibilityModelExecutor::ExtractContentVisibilityFromModelOutput(
+    const std::vector<tflite::task::core::Category>& model_output) const {
+  absl::optional<proto::PageTopicsModelMetadata> model_metadata =
+      ParsedSupportedFeaturesForLoadedModel<proto::PageTopicsModelMetadata>();
+  if (!model_metadata) {
+    return absl::nullopt;
+  }
+
+  if (!model_metadata->output_postprocessing_params().has_visibility_params()) {
+    return absl::nullopt;
+  }
+
+  if (!model_metadata->output_postprocessing_params()
+           .visibility_params()
+           .has_category_name()) {
+    return absl::nullopt;
+  }
+
+  std::string visibility_category_name =
+      model_metadata->output_postprocessing_params()
+          .visibility_params()
+          .category_name();
+
+  for (const auto& category : model_output) {
+    if (category.class_name == visibility_category_name) {
+      return 1.0 - category.score;
+    }
+  }
+
+  // -1 is a sentinel value that means the visibility of the page was not
+  // evaluated.
+  return -1.0;
+}
+
+}  // namespace optimization_guide
diff --git a/components/optimization_guide/core/page_visibility_model_executor.h b/components/optimization_guide/core/page_visibility_model_executor.h
new file mode 100644
index 0000000..a8cd0ea
--- /dev/null
+++ b/components/optimization_guide/core/page_visibility_model_executor.h
@@ -0,0 +1,54 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_OPTIMIZATION_GUIDE_CORE_PAGE_VISIBILITY_MODEL_EXECUTOR_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_PAGE_VISIBILITY_MODEL_EXECUTOR_H_
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "components/optimization_guide/core/bert_model_handler.h"
+#include "components/optimization_guide/core/page_content_annotation_job.h"
+#include "components/optimization_guide/core/page_content_annotation_job_executor.h"
+#include "components/optimization_guide/core/page_content_annotations_common.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace optimization_guide {
+
+// A BERT-based mode executor for page visibility annotations.
+class PageVisibilityModelExecutor : public PageContentAnnotationJobExecutor,
+                                    public BertModelHandler {
+ public:
+  PageVisibilityModelExecutor(
+      OptimizationGuideModelProvider* model_provider,
+      scoped_refptr<base::SequencedTaskRunner> background_task_runner,
+      const absl::optional<proto::Any>& model_metadata);
+  ~PageVisibilityModelExecutor() override;
+
+  // PageContentAnnotationJobExecutor:
+  void ExecuteOnSingleInput(
+      AnnotationType annotation_type,
+      const std::string& input,
+      base::OnceCallback<void(const BatchAnnotationResult&)> callback) override;
+
+  // Creates a BatchAnnotationResult from the output of the model, calling
+  // |ExtractContentVisibilityFromModelOutput| in the process.
+  // Public for testing.
+  void PostprocessCategoriesToBatchAnnotationResult(
+      base::OnceCallback<void(const BatchAnnotationResult&)> callback,
+      AnnotationType annotation_type,
+      const std::string& input,
+      const absl::optional<std::vector<tflite::task::core::Category>>& output);
+
+  // Extracts the visibility score from the output of the model.
+  // Public for testing.
+  absl::optional<double> ExtractContentVisibilityFromModelOutput(
+      const std::vector<tflite::task::core::Category>& model_output) const;
+
+ private:
+  base::WeakPtrFactory<PageVisibilityModelExecutor> weak_ptr_factory_{this};
+};
+
+}  // namespace optimization_guide
+
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_PAGE_VISIBILITY_MODEL_EXECUTOR_H_
diff --git a/components/optimization_guide/core/page_visibility_model_executor_unittest.cc b/components/optimization_guide/core/page_visibility_model_executor_unittest.cc
new file mode 100644
index 0000000..8bede16
--- /dev/null
+++ b/components/optimization_guide/core/page_visibility_model_executor_unittest.cc
@@ -0,0 +1,250 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/optimization_guide/core/page_visibility_model_executor.h"
+
+#include "base/containers/flat_map.h"
+#include "base/path_service.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/optimization_guide/core/optimization_guide_model_provider.h"
+#include "components/optimization_guide/core/page_entities_model_executor.h"
+#include "components/optimization_guide/core/test_model_info_builder.h"
+#include "components/optimization_guide/core/test_optimization_guide_model_provider.h"
+#include "components/optimization_guide/proto/models.pb.h"
+#include "components/optimization_guide/proto/page_topics_model_metadata.pb.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace optimization_guide {
+
+class ModelObserverTracker : public TestOptimizationGuideModelProvider {
+ public:
+  void AddObserverForOptimizationTargetModel(
+      proto::OptimizationTarget target,
+      const absl::optional<proto::Any>& model_metadata,
+      OptimizationTargetModelObserver* observer) override {
+    registered_model_metadata_.insert_or_assign(target, model_metadata);
+  }
+
+  bool DidRegisterForTarget(
+      proto::OptimizationTarget target,
+      absl::optional<proto::Any>* out_model_metadata) const {
+    auto it = registered_model_metadata_.find(target);
+    if (it == registered_model_metadata_.end())
+      return false;
+    *out_model_metadata = registered_model_metadata_.at(target);
+    return true;
+  }
+
+ private:
+  base::flat_map<proto::OptimizationTarget, absl::optional<proto::Any>>
+      registered_model_metadata_;
+};
+
+class PageVisibilityModelExecutorTest : public testing::Test {
+ public:
+  PageVisibilityModelExecutorTest() {
+    scoped_feature_list_.InitAndEnableFeature(
+        features::kPageContentAnnotations);
+  }
+  ~PageVisibilityModelExecutorTest() override = default;
+
+  void SetUp() override {
+    model_observer_tracker_ = std::make_unique<ModelObserverTracker>();
+    model_executor_ = std::make_unique<PageVisibilityModelExecutor>(
+        model_observer_tracker_.get(),
+        task_environment_.GetMainThreadTaskRunner(),
+        /*model_metadata=*/absl::nullopt);
+  }
+
+  void TearDown() override {
+    model_executor_.reset();
+    model_observer_tracker_.reset();
+    RunUntilIdle();
+  }
+
+  void SendPageVisibilityModelToExecutor(
+      const absl::optional<proto::Any>& model_metadata) {
+    base::FilePath source_root_dir;
+    base::PathService::Get(base::DIR_SOURCE_ROOT, &source_root_dir);
+    base::FilePath model_file_path =
+        source_root_dir.AppendASCII("components")
+            .AppendASCII("test")
+            .AppendASCII("data")
+            .AppendASCII("optimization_guide")
+            .AppendASCII("bert_page_topics_model.tflite");
+    std::unique_ptr<ModelInfo> model_info =
+        TestModelInfoBuilder()
+            .SetModelFilePath(model_file_path)
+            .SetModelMetadata(model_metadata)
+            .Build();
+    model_executor()->OnModelUpdated(proto::OPTIMIZATION_TARGET_PAGE_VISIBILITY,
+                                     *model_info);
+    RunUntilIdle();
+  }
+
+  ModelObserverTracker* model_observer_tracker() const {
+    return model_observer_tracker_.get();
+  }
+
+  PageVisibilityModelExecutor* model_executor() const {
+    return model_executor_.get();
+  }
+
+  void RunUntilIdle() { task_environment_.RunUntilIdle(); }
+
+ private:
+  base::test::TaskEnvironment task_environment_;
+  base::test::ScopedFeatureList scoped_feature_list_;
+  std::unique_ptr<ModelObserverTracker> model_observer_tracker_;
+  std::unique_ptr<PageVisibilityModelExecutor> model_executor_;
+};
+
+TEST_F(PageVisibilityModelExecutorTest, NoModelMetadataNoOutput) {
+  // Note that |SendPageVisibilityModelToExecutor| is not called so no metadata
+  // has been loaded.
+
+  std::vector<tflite::task::core::Category> model_output = {
+      {"VISIBILITY_HERE", 0.3},
+  };
+
+  absl::optional<double> score =
+      model_executor()->ExtractContentVisibilityFromModelOutput(model_output);
+  EXPECT_FALSE(score);
+}
+
+TEST_F(PageVisibilityModelExecutorTest, NoParamsNoOutput) {
+  proto::PageTopicsModelMetadata model_metadata;
+  model_metadata.set_version(123);
+
+  proto::Any any_metadata;
+  any_metadata.set_type_url(
+      "type.googleapis.com/com.foo.PageTopicsModelMetadata");
+  model_metadata.SerializeToString(any_metadata.mutable_value());
+  SendPageVisibilityModelToExecutor(any_metadata);
+
+  std::vector<tflite::task::core::Category> model_output = {
+      {"VISIBILITY_HERE", 0.3},
+  };
+
+  absl::optional<double> score =
+      model_executor()->ExtractContentVisibilityFromModelOutput(model_output);
+  EXPECT_FALSE(score);
+}
+
+TEST_F(PageVisibilityModelExecutorTest, VisibilityNotEvaluated) {
+  proto::PageTopicsModelMetadata model_metadata;
+  model_metadata.set_version(123);
+  model_metadata.mutable_output_postprocessing_params()
+      ->mutable_visibility_params()
+      ->set_category_name("VISIBILITY_HERE");
+
+  proto::Any any_metadata;
+  any_metadata.set_type_url(
+      "type.googleapis.com/com.foo.PageTopicsModelMetadata");
+  model_metadata.SerializeToString(any_metadata.mutable_value());
+  SendPageVisibilityModelToExecutor(any_metadata);
+
+  std::vector<tflite::task::core::Category> model_output = {
+      {"something else", 0.3},
+  };
+
+  absl::optional<double> score =
+      model_executor()->ExtractContentVisibilityFromModelOutput(model_output);
+  ASSERT_TRUE(score);
+  EXPECT_THAT(*score, testing::DoubleEq(-1));
+}
+
+TEST_F(PageVisibilityModelExecutorTest, SuccessCase) {
+  proto::PageTopicsModelMetadata model_metadata;
+  model_metadata.set_version(123);
+  model_metadata.mutable_output_postprocessing_params()
+      ->mutable_visibility_params()
+      ->set_category_name("VISIBILITY_HERE");
+
+  proto::Any any_metadata;
+  any_metadata.set_type_url(
+      "type.googleapis.com/com.foo.PageTopicsModelMetadata");
+  model_metadata.SerializeToString(any_metadata.mutable_value());
+  SendPageVisibilityModelToExecutor(any_metadata);
+
+  std::vector<tflite::task::core::Category> model_output = {
+      {"VISIBILITY_HERE", 0.3},
+      {"0", 0.4},
+      {"1", 0.5},
+  };
+
+  absl::optional<double> score =
+      model_executor()->ExtractContentVisibilityFromModelOutput(model_output);
+  ASSERT_TRUE(score);
+  EXPECT_THAT(*score, testing::DoubleEq(0.7));
+}
+
+TEST_F(PageVisibilityModelExecutorTest,
+       PostprocessCategoriesToBatchAnnotationResult) {
+  proto::PageTopicsModelMetadata model_metadata;
+  model_metadata.set_version(123);
+  model_metadata.mutable_output_postprocessing_params()
+      ->mutable_visibility_params()
+      ->set_category_name("VISIBILITY_HERE");
+
+  proto::Any any_metadata;
+  any_metadata.set_type_url(
+      "type.googleapis.com/com.foo.PageTopicsModelMetadata");
+  model_metadata.SerializeToString(any_metadata.mutable_value());
+  SendPageVisibilityModelToExecutor(any_metadata);
+
+  std::vector<tflite::task::core::Category> model_output = {
+      {"0", 0.3},
+      {"1", 0.25},
+      {"2", 0.4},
+      {"3", 0.05},
+      {"VISIBILITY_HERE", 0.4},
+  };
+
+  BatchAnnotationResult viz_result =
+      BatchAnnotationResult::CreateEmptyAnnotationsResult("");
+  model_executor()->PostprocessCategoriesToBatchAnnotationResult(
+      base::BindOnce(
+          [](BatchAnnotationResult* out_result,
+             const BatchAnnotationResult& in_result) {
+            *out_result = in_result;
+          },
+          &viz_result),
+      AnnotationType::kContentVisibility, "input", model_output);
+  EXPECT_EQ(viz_result,
+            BatchAnnotationResult::CreateContentVisibilityResult("input", 0.6));
+}
+
+TEST_F(PageVisibilityModelExecutorTest,
+       NullPostprocessCategoriesToBatchAnnotationResult) {
+  proto::PageTopicsModelMetadata model_metadata;
+  model_metadata.set_version(123);
+  model_metadata.mutable_output_postprocessing_params()
+      ->mutable_visibility_params()
+      ->set_category_name("VISIBILITY_HERE");
+
+  proto::Any any_metadata;
+  any_metadata.set_type_url(
+      "type.googleapis.com/com.foo.PageTopicsModelMetadata");
+  model_metadata.SerializeToString(any_metadata.mutable_value());
+  SendPageVisibilityModelToExecutor(any_metadata);
+
+  BatchAnnotationResult viz_result =
+      BatchAnnotationResult::CreateEmptyAnnotationsResult("");
+  model_executor()->PostprocessCategoriesToBatchAnnotationResult(
+      base::BindOnce(
+          [](BatchAnnotationResult* out_result,
+             const BatchAnnotationResult& in_result) {
+            *out_result = in_result;
+          },
+          &viz_result),
+      AnnotationType::kContentVisibility, "input", absl::nullopt);
+  EXPECT_EQ(viz_result, BatchAnnotationResult::CreateContentVisibilityResult(
+                            "input", absl::nullopt));
+}
+
+}  // namespace optimization_guide
\ No newline at end of file
diff --git a/components/optimization_guide/proto/models.proto b/components/optimization_guide/proto/models.proto
index 3647542f..c69cb12 100644
--- a/components/optimization_guide/proto/models.proto
+++ b/components/optimization_guide/proto/models.proto
@@ -290,6 +290,8 @@
   MODEL_TYPE_TFLITE_2_4 = 4;
   // TensorflowLite version 2.7.*. This is where regular ~HEAD rolls started.
   MODEL_TYPE_TFLITE_2_7 = 5;
+  // A model using only operations that are supported by TensorflowLite 2.8.0.
+  MODEL_TYPE_TFLITE_2_8 = 6;
 }
 
 // A set of model features and the host that it applies to.
diff --git a/components/page_load_metrics/browser/observers/use_counter/ukm_features.cc b/components/page_load_metrics/browser/observers/use_counter/ukm_features.cc
index 5f43bfee..5cadb2bb 100644
--- a/components/page_load_metrics/browser/observers/use_counter/ukm_features.cc
+++ b/components/page_load_metrics/browser/observers/use_counter/ukm_features.cc
@@ -31,9 +31,6 @@
           WebFeature::kNavigatorVibrateSubFrame,
           WebFeature::kTouchEventPreventedNoTouchAction,
           WebFeature::kTouchEventPreventedForcedDocumentPassiveNoTouchAction,
-          WebFeature::kApplicationCacheInstalledButNoManifest,
-          WebFeature::kApplicationCacheManifestSelectInsecureOrigin,
-          WebFeature::kApplicationCacheManifestSelectSecureOrigin,
           WebFeature::kMixedContentAudio,
           WebFeature::kMixedContentImage,
           WebFeature::kMixedContentVideo,
diff --git a/components/password_manager/ios/password_manager_client_bridge.h b/components/password_manager/ios/password_manager_client_bridge.h
index 96912d7..c9a8a58 100644
--- a/components/password_manager/ios/password_manager_client_bridge.h
+++ b/components/password_manager/ios/password_manager_client_bridge.h
@@ -7,6 +7,7 @@
 
 #import <Foundation/Foundation.h>
 #include <memory>
+#include <string>
 
 #include "components/password_manager/core/browser/leak_detection_dialog_utils.h"
 
@@ -53,9 +54,10 @@
 // This also causes the UI to be dismissed.
 - (void)removePasswordInfoBarManualFallback:(BOOL)manual;
 
-// Shows Password Breach for |URL| and |leakType|.
+// Shows Password Breach for |URL|, |leakType|, and |username|.
 - (void)showPasswordBreachForLeakType:(CredentialLeakType)leakType
-                                  URL:(const GURL&)URL;
+                                  URL:(const GURL&)URL
+                             username:(const std::u16string&)username;
 
 // Shows Password Protection warning with |warningText|. |completion| should be
 // called when the UI is dismissed with the user's |action|.
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index 5e90fed8..76cde26 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -28834,7 +28834,49 @@
 
       The <ph name="ENTERPRISE_CONNECTOR_MINIMUM_DATA_SIZE">minimum_data_size</ph> indicates the minimum amount of data in bytes that triggers the pattern check. This means that a clipboard write from a blocked URL would be allowed if the size of the copied data is smaller than the value specified in this field. The default value is 100 bytes if the field is unset.
       '''
-    }
+    },
+    {
+      'name': 'UserAgentReduction',
+      'owners': ['abeyad@chromium.org', 'aarontag@chromium.org', 'miketaylr@chromium.org'],
+      'type': 'int-enum',
+      'schema': {
+        'type': 'integer',
+        'enum': [0,1,2],
+      },
+      'items': [
+        {
+          'name': 'Default',
+          'value': 0,
+          'caption': '''User Agent reduction will be controllable via Field-Trials and Origin-Trials.''',
+        },
+        {
+          'name': 'ForceDisabled',
+          'value': 1,
+          'caption': '''User Agent reduction diabled, and not enabled by Field-Trials or Origin-Trials.''',
+        },
+        {
+          'name': 'ForceEnabled',
+          'value': 2,
+          'caption': '''User Agent reduction will be enabled for all origins.''',
+        },
+      ],
+      'supported_on': ['chrome_os:98-', 'chrome.*:98-', 'android:98-'],
+      'default': 0,
+      'example_value': 0,
+      'features': {
+        'per_profile': True,
+        'dynamic_refresh': True,
+      },
+      'id': 929,
+      'tags': ['website-sharing'],
+      'caption': '''Enable or disable the <ph name="USER_AGENT_REDUCTION_FEATURE_NAME">User-Agent Reduction</ph>.''',
+      'desc': '''The <ph name="USER_AGENT_HEADER_NAME">User-Agent</ph> HTTP request header is scheduled to be reduced. In order to facilitate testing and compatibility, this policy can enable the reduction feature for all websites, or disable the ability for origin trials or field trials to enable the feature.
+
+      To learn more about the <ph name="USER_AGENT_REDUCTION_FEATURE_NAME">User-Agent Reduction</ph> and its timeline, read here:
+
+      https://blog.chromium.org/2021/09/user-agent-reduction-origin-trial-and-dates.html
+      '''
+    },
   ],
   'messages': {
     # Messages that are not associated to any policies.
@@ -29807,6 +29849,6 @@
   'placeholders': [],
   'deleted_policy_ids': [114, 115, 204, 205, 206, 412, 476, 544, 546, 562, 569, 578, 583, 585, 586, 587, 588, 589, 590, 591, 600, 668, 669, 872],
   'deleted_atomic_policy_group_ids': [19],
-  'highest_id_currently_used': 928,
+  'highest_id_currently_used': 929,
   'highest_atomic_group_id_currently_used': 41
 }
diff --git a/components/policy/test_support/BUILD.gn b/components/policy/test_support/BUILD.gn
index 173d599..b8d7860 100644
--- a/components/policy/test_support/BUILD.gn
+++ b/components/policy/test_support/BUILD.gn
@@ -5,10 +5,7 @@
 static_library("test_support") {
   testonly = true
 
-  data = [
-    "asn1der.py",
-    "policy_testserver.py",
-  ]
+  data = [ "policy_testserver.py" ]
 
   sources = [
     "client_storage.cc",
diff --git a/components/policy/test_support/OWNERS b/components/policy/test_support/OWNERS
index a60e0da6..76cec38b 100644
--- a/components/policy/test_support/OWNERS
+++ b/components/policy/test_support/OWNERS
@@ -1,4 +1,3 @@
 # policy_testserver.py is used in tast and autotest integration tests.
 
 per-file policy_testserver.py=ftirelo@chromium.org, pmarko@chromium.org, vsavu@google.com
-per-file asn1der.py=ftirelo@chromium.org, pmarko@chromium.org, vsavu@google.com
diff --git a/components/policy/test_support/asn1der.py b/components/policy/test_support/asn1der.py
deleted file mode 100644
index 00aca5c..0000000
--- a/components/policy/test_support/asn1der.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# Copyright (c) 2011 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""Helper module for ASN.1/DER encoding."""
-
-import binascii
-import struct
-
-# Tags as defined by ASN.1.
-INTEGER = 2
-BIT_STRING = 3
-NULL = 5
-OBJECT_IDENTIFIER = 6
-SEQUENCE = 0x30
-
-def Data(tag, data):
-  """Generic type-length-value encoder.
-
-  Args:
-    tag: the tag.
-    data: the data for the given tag.
-  Returns:
-    encoded TLV value.
-  """
-  if len(data) < 128:
-    return struct.pack(">BB", tag, len(data)) + data;
-  assert len(data) <= 0xffff;
-  return struct.pack(">BBH", tag, 0x82, len(data)) + data;
-
-def Integer(value):
-  """Encodes an integer.
-
-  Args:
-    value: the long value.
-  Returns:
-    encoded TLV value.
-  """
-  data = '%x' % value
-  if (len(data) % 2 == 1):
-    # Odd number of non-zero bytes - pad out our data to a full number of bytes.
-    data = '0' + data
-
-  # If the high bit is set, need to prepend a null byte to denote a positive
-  # number.
-  if (int(data[0], 16) >= 8):
-    data = '00' + data
-
-  return Data(INTEGER, binascii.unhexlify(data))
-
-def Bitstring(value):
-  """Encodes a bit string.
-
-  Args:
-    value: a string holding the binary data.
-  Returns:
-    encoded TLV value.
-  """
-  return Data(BIT_STRING, b'\x00' + value)
-
-def Sequence(values):
-  """Encodes a sequence of other values.
-
-  Args:
-    values: the list of values, must be strings holding already encoded data.
-  Returns:
-    encoded TLV value.
-  """
-  return Data(SEQUENCE, b''.join(values))
diff --git a/components/policy/test_support/policy_testserver.py b/components/policy/test_support/policy_testserver.py
index d123cbe0..06980ee 100644
--- a/components/policy/test_support/policy_testserver.py
+++ b/components/policy/test_support/policy_testserver.py
@@ -65,6 +65,8 @@
 
 import base64
 from six.moves import BaseHTTPServer
+from cryptography.hazmat.primitives.asymmetric import padding, rsa
+from cryptography.hazmat.primitives import hashes, serialization
 import glob
 import google.protobuf.text_format
 import hashlib
@@ -76,15 +78,10 @@
 import six
 import sys
 import time
-import tlslite
-import tlslite.api
-import tlslite.utils
-import tlslite.utils.cryptomath
 from six.moves import urllib
 from six.moves.urllib import request as urllib_request
 from six.moves.urllib import parse as urlparse
 
-import asn1der
 import testserver_base
 
 import device_management_backend_pb2 as dm
@@ -113,9 +110,6 @@
 except ImportError:
   crypto = None
 
-# ASN.1 object identifier for PKCS#1/RSA.
-PKCS1_RSA_OID = b'\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01'
-
 # List of machines that trigger the server to send kiosk enrollment response
 # for the register request.
 KIOSK_MACHINE_IDS = [ 'KIOSK' ]
@@ -1342,8 +1336,8 @@
 
     # Sign the serialized policy data
     if msg.signature_type == dm.PolicyFetchRequest.SHA1_RSA:
-      response.policy_data_signature = bytes(
-          signing_key['private_key'].hashAndSign(response.policy_data))
+      response.policy_data_signature = signing_key['private_key'].sign(
+          response.policy_data, padding.PKCS1v15(), hashes.SHA1())
       if msg.public_key_version != signing_key_version:
         response.new_public_key = signing_key['public_key']
 
@@ -1359,8 +1353,8 @@
                 verification_sig)
 
         if client_key is not None:
-          response.new_public_key_signature = bytes(
-              client_key['private_key'].hashAndSign(response.new_public_key))
+          response.new_public_key_signature = client_key['private_key'].sign(
+              response.new_public_key, padding.PKCS1v15(), hashes.SHA1())
 
     return (200, response.SerializeToString())
 
@@ -1562,15 +1556,14 @@
           print('Failed to load private key from %s' % key_path)
           continue
         try:
-          # Decode with replacement characters to avoid decode errors if was
-          # actually DER.
-          key = tlslite.api.parsePEMKey(key_str.decode('utf-8', 'replace'),
-                                        private=True)
-        except SyntaxError:
-          key = tlslite.utils.python_rsakey.Python_RSAKey._parsePKCS8(
-              bytearray(key_str))
+          key = serialization.load_pem_private_key(key_str, password=None)
+        except ValueError:
+          key = serialization.load_der_private_key(key_str, password=None)
 
         assert key is not None
+        if not isinstance(key, rsa.RSAPrivateKey):
+          raise TypeError('Unexpected key type')
+
         key_info = { 'private_key' : key }
 
         # Now try to read in a signature, if one exists.
@@ -1585,9 +1578,9 @@
       # Use the canned private keys if none were passed from the command line.
       for signing_key in SIGNING_KEYS:
         decoded_key = base64.b64decode(signing_key['key']);
-        key = tlslite.utils.python_rsakey.Python_RSAKey._parsePKCS8(
-            bytearray(decoded_key))
+        key = serialization.load_der_private_key(decoded_key, password=None)
         assert key is not None
+        assert isinstance(key, rsa.RSAPrivateKey)
         # Grab the signature dictionary for this key and decode all of the
         # signatures.
         signature_dict = signing_key['signatures']
@@ -1599,15 +1592,9 @@
 
     # Derive the public keys from the private keys.
     for entry in self.keys:
-      key = entry['private_key']
-
-      algorithm = asn1der.Sequence(
-          [ asn1der.Data(asn1der.OBJECT_IDENTIFIER, PKCS1_RSA_OID),
-            asn1der.Data(asn1der.NULL, b'') ])
-      rsa_pubkey = asn1der.Sequence([ asn1der.Integer(key.n),
-                                      asn1der.Integer(key.e) ])
-      pubkey = asn1der.Sequence([ algorithm, asn1der.Bitstring(rsa_pubkey) ])
-      entry['public_key'] = pubkey
+      entry['public_key'] = entry['private_key'].public_key().public_bytes(
+          encoding=serialization.Encoding.DER,
+          format=serialization.PublicFormat.SubjectPublicKeyInfo)
 
     try:
       self.ReadClientStateFile()
diff --git a/components/reporting/compression/decompression.cc b/components/reporting/compression/decompression.cc
index 71b9298b..b8e1f38 100644
--- a/components/reporting/compression/decompression.cc
+++ b/components/reporting/compression/decompression.cc
@@ -38,7 +38,6 @@
   switch (compression_information.compression_algorithm()) {
     case CompressionInformation::COMPRESSION_NONE: {
       // Don't decompress, simply return serialized record
-      LOG(ERROR) << "RETURN RAW RECORD";
       return record;
     }
     case CompressionInformation::COMPRESSION_SNAPPY: {
diff --git a/components/reporting/storage/storage.cc b/components/reporting/storage/storage.cc
index f38e473..a09426a 100644
--- a/components/reporting/storage/storage.cc
+++ b/components/reporting/storage/storage.cc
@@ -721,27 +721,28 @@
   ASSIGN_OR_ONCE_CALLBACK_AND_RETURN(scoped_refptr<StorageQueue> queue,
                                      completion_cb, GetQueue(priority));
 
-  KeyDelivery::RequestCallback action = base::BindOnce(
-      [](scoped_refptr<StorageQueue> queue, Record record,
-         base::OnceCallback<void(Status)> completion_cb, Status status) {
-        if (!status.ok()) {
-          std::move(completion_cb).Run(status);
-          return;
-        }
-        queue->Write(std::move(record), std::move(completion_cb));
-      },
-      queue, std::move(record), std::move(completion_cb));
-
   if (EncryptionModuleInterface::is_enabled() &&
       !encryption_module_->has_encryption_key()) {
     // Key was not found at startup time. Note that if the key is outdated,
     // we still can't use it, and won't load it now. So this processing can
     // only happen after Storage is initialized (until the first successful
-    // delivery of a key).
+    // delivery of a key). After that we will resume the write into the queue.
+    KeyDelivery::RequestCallback action = base::BindOnce(
+        [](scoped_refptr<StorageQueue> queue, Record record,
+           base::OnceCallback<void(Status)> completion_cb, Status status) {
+          if (!status.ok()) {
+            std::move(completion_cb).Run(status);
+            return;
+          }
+          queue->Write(std::move(record), std::move(completion_cb));
+        },
+        queue, std::move(record), std::move(completion_cb));
     key_delivery_->Request(std::move(action));
-  } else {
-    std::move(action).Run(Status::StatusOK());
+    return;
   }
+
+  // Otherwise we can write into the queue right away.
+  queue->Write(std::move(record), std::move(completion_cb));
 }
 
 void Storage::Confirm(Priority priority,
@@ -779,7 +780,7 @@
       signed_encryption_key.public_asymmetric_key(),
       signed_encryption_key.public_key_id(),
       base::BindOnce(
-          [](Storage* storage, Status status) {
+          [](scoped_refptr<Storage> storage, Status status) {
             if (!status.ok()) {
               LOG(WARNING) << "Encryption key update failed, status=" << status;
               storage->key_delivery_->OnCompletion(status);
@@ -788,7 +789,7 @@
             // Encryption key updated successfully.
             storage->key_delivery_->OnCompletion(Status::StatusOK());
           },
-          base::Unretained(this)));
+          base::WrapRefCounted(this)));
 
   // Serialize whole signed_encryption_key to a new file, discard the old
   // one(s). Do it on a thread which may block doing file operations.
diff --git a/components/reporting/storage/storage_configuration.cc b/components/reporting/storage/storage_configuration.cc
index 8167ba4e..89126edd 100644
--- a/components/reporting/storage/storage_configuration.cc
+++ b/components/reporting/storage/storage_configuration.cc
@@ -13,6 +13,6 @@
 StorageOptions::~StorageOptions() = default;
 
 QueueOptions::QueueOptions(const StorageOptions& storage_options)
-      : storage_options_(storage_options) {}
+    : storage_options_(storage_options) {}
 QueueOptions::QueueOptions(const QueueOptions& options) = default;
 }  // namespace reporting
diff --git a/components/reporting/storage/storage_configuration.h b/components/reporting/storage/storage_configuration.h
index 183598d0..789db0a 100644
--- a/components/reporting/storage/storage_configuration.h
+++ b/components/reporting/storage/storage_configuration.h
@@ -140,7 +140,7 @@
   // When file exceeds this size, the new file is created
   // for further records. Note that each file must have at least
   // one record before it is closed, regardless of that record size.
-  uint64_t max_single_file_size_ = 1 * 1024LL * 1024LL; // 1 MiB
+  uint64_t max_single_file_size_ = 1 * 1024LL * 1024LL;  // 1 MiB
 };
 
 }  // namespace reporting
diff --git a/components/reporting/storage/storage_queue.cc b/components/reporting/storage/storage_queue.cc
index 0912af1..00ea9c6b 100644
--- a/components/reporting/storage/storage_queue.cc
+++ b/components/reporting/storage/storage_queue.cc
@@ -1286,8 +1286,8 @@
           base::Unretained(storage_queue_->write_contexts_queue_.front()));
     }
 
-    // If no uploader is needed, we are done.
-    if (!async_start_upload_cb_) {
+    // If uploads are not immediate, we are done.
+    if (!storage_queue_->options_.upload_period().is_zero()) {
       return;
     }
 
@@ -1453,11 +1453,6 @@
     storage_queue_->write_contexts_queue_.pop_front();
     in_contexts_queue_ = storage_queue_->write_contexts_queue_.end();
 
-    // Prepare uploader, if need to run it after Write.
-    if (storage_queue_->options_.upload_period().is_zero()) {
-      async_start_upload_cb_ = storage_queue_->async_start_upload_cb_;
-    }
-
     DCHECK(!buffer_.empty());
     StatusOr<scoped_refptr<SingleFile>> assign_result =
         storage_queue_->AssignLastFile(buffer_.size());
@@ -1502,9 +1497,6 @@
   // executed. Empty until encryption is done.
   std::string buffer_;
 
-  // Upload provider.
-  AsyncStartUploaderCb async_start_upload_cb_;
-
   SEQUENCE_CHECKER(write_sequence_checker_);
 };
 
diff --git a/components/reporting/storage/storage_queue_unittest.cc b/components/reporting/storage/storage_queue_unittest.cc
index 7a8c5a4..fa9ce8d 100644
--- a/components/reporting/storage/storage_queue_unittest.cc
+++ b/components/reporting/storage/storage_queue_unittest.cc
@@ -40,6 +40,7 @@
 
 using ::testing::_;
 using ::testing::Between;
+using ::testing::DoAll;
 using ::testing::Eq;
 using ::testing::Invoke;
 using ::testing::Return;
@@ -108,7 +109,7 @@
         : base::RefCountedDeleteOnSequence<::testing::NiceMock<MockUpload>>(
               sequenced_task_runner) {
       DETACH_FROM_SEQUENCE(mock_uploader_checker_);
-      upload_progress_.assign("Start\n");
+      upload_progress_.assign("\nStart\n");
     }
     MockUpload(const MockUpload& other) = delete;
     MockUpload& operator=(const MockUpload& other) = delete;
@@ -156,6 +157,7 @@
       upload_progress_.append("Complete: ")
           .append(status.ToString())
           .append("\n");
+      LOG(ERROR) << "TestUploader: " << upload_progress_ << "End\n";
       UploadComplete(uploader_id, status);
     }
 
@@ -183,7 +185,6 @@
    protected:
     virtual ~MockUpload() {
       DCHECK_CALLED_ON_VALID_SEQUENCE(mock_uploader_checker_);
-      LOG(ERROR) << "TestUploader: " << upload_progress_ << "End\n";
     }
 
    private:
@@ -210,19 +211,21 @@
     explicit TestUploader(StorageQueueTest* self)
         : uploader_id_(next_uploader_id.fetch_add(1)),
           last_record_digest_map_(&self->last_record_digest_map_),
-          sequenced_task_runner_(self->sequenced_task_runner_),
+          main_thread_task_runner_(self->main_thread_task_runner_),
           mock_upload_(base::MakeRefCounted<::testing::NiceMock<MockUpload>>(
-              sequenced_task_runner_)) {
+              main_thread_task_runner_)) {
       DETACH_FROM_SEQUENCE(test_uploader_checker_);
     }
 
     ~TestUploader() override {
       DCHECK_CALLED_ON_VALID_SEQUENCE(test_uploader_checker_);
+      DCHECK(!mock_upload_);
     }
 
     void ProcessRecord(EncryptedRecord encrypted_record,
                        base::OnceCallback<void(bool)> processed_cb) override {
       DCHECK_CALLED_ON_VALID_SEQUENCE(test_uploader_checker_);
+      DCHECK(mock_upload_);
       auto sequence_information = encrypted_record.sequence_information();
       // Decompress encrypted_wrapped_record if is was compressed.
       WrappedRecord wrapped_record;
@@ -250,10 +253,11 @@
                     uint64_t count,
                     base::OnceCallback<void(bool)> processed_cb) override {
       DCHECK_CALLED_ON_VALID_SEQUENCE(test_uploader_checker_);
+      DCHECK(mock_upload_);
       // Verify generation match.
       if (generation_id_.has_value() &&
           generation_id_.value() != sequence_information.generation_id()) {
-        sequenced_task_runner_->PostTask(
+        main_thread_task_runner_->PostTask(
             FROM_HERE,
             base::BindOnce(
                 [](SequenceInformation sequence_information,
@@ -285,7 +289,7 @@
                          sequence_information.generation_id()),
           absl::nullopt);
 
-      sequenced_task_runner_->PostTask(
+      main_thread_task_runner_->PostTask(
           FROM_HERE,
           base::BindOnce(
               [](uint64_t count, SequenceInformation sequence_information,
@@ -307,9 +311,12 @@
 
     void Completed(Status status) override {
       DCHECK_CALLED_ON_VALID_SEQUENCE(test_uploader_checker_);
-      sequenced_task_runner_->PostTask(
-          FROM_HERE, base::BindOnce(&MockUpload::DoUploadComplete, mock_upload_,
-                                    uploader_id_, status));
+      DCHECK(mock_upload_);
+      main_thread_task_runner_->PostTask(
+          FROM_HERE,
+          base::BindOnce(&MockUpload::DoUploadComplete,
+                         std::move(mock_upload_),  // No longer needed.
+                         uploader_id_, status));
     }
 
     // Helper class for setting up mock uploader expectations of a successful
@@ -330,8 +337,10 @@
                     UploadComplete(Eq(uploader_id_), Eq(status)))
             .InSequence(uploader_->test_upload_sequence_,
                         uploader_->test_encounter_sequence_)
-            .WillOnce(WithoutArgs(
-                Invoke(waiter_, &test::TestCallbackWaiter::Signal)));
+            .WillOnce(DoAll(
+                WithoutArgs(Invoke(waiter_, &test::TestCallbackWaiter::Signal)),
+                WithoutArgs(
+                    Invoke([]() { LOG(ERROR) << "Completion signaled"; }))));
         return std::move(uploader_);
       }
 
@@ -410,7 +419,7 @@
     void ScheduleVerifyRecord(SequenceInformation sequence_information,
                               WrappedRecord wrapped_record,
                               base::OnceCallback<void(bool)> processed_cb) {
-      sequenced_task_runner_->PostTask(
+      main_thread_task_runner_->PostTask(
           FROM_HERE,
           base::BindOnce(&TestUploader::VerifyRecord, base::Unretained(this),
                          std::move(sequence_information),
@@ -495,7 +504,8 @@
     absl::optional<int64_t> generation_id_;
     LastRecordDigestMap* const last_record_digest_map_;
 
-    scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_;
+    // Single task runner where all EXPECTs will happen.
+    const scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
 
     scoped_refptr<::testing::NiceMock<MockUpload>> mock_upload_;
 
@@ -575,7 +585,7 @@
   void AsyncStartMockUploader(
       UploaderInterface::UploadReason reason,
       UploaderInterface::UploaderInterfaceResultCb start_uploader_cb) {
-    sequenced_task_runner_->PostTask(
+    main_thread_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(
             [](UploaderInterface::UploadReason reason,
@@ -609,6 +619,7 @@
   Status WriteRecord(const Record record) {
     EXPECT_TRUE(storage_queue_) << "StorageQueue not created yet";
     test::TestEvent<Status> write_event;
+    LOG(ERROR) << "Write data='" << record.data() << "'";
     storage_queue_->Write(std::move(record), write_event.cb());
     return write_event.result();
   }
@@ -621,6 +632,10 @@
   void ConfirmOrDie(absl::optional<std::int64_t> sequencing_id,
                     bool force = false) {
     test::TestEvent<Status> c;
+    LOG(ERROR) << "Confirm force=" << force << " seq="
+               << (sequencing_id.has_value()
+                       ? base::NumberToString(sequencing_id.value())
+                       : "null");
     storage_queue_->Confirm(sequencing_id, force, c.cb());
     const Status c_result = c.result();
     ASSERT_OK(c_result) << c_result;
@@ -629,10 +644,9 @@
   std::string dm_token_;
   base::test::TaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
-  const scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_{
-      base::ThreadPool::CreateSequencedTaskRunner(
-          {base::TaskPriority::BEST_EFFORT,
-           base::TaskShutdownBehavior::BLOCK_SHUTDOWN, base::MayBlock()})};
+  // Single task runner where all EXPECTs will happen - main thread.
+  const scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_{
+      base::ThreadTaskRunnerHandle::Get()};
 
   base::test::ScopedFeatureList scoped_feature_list_;
   base::ScopedTempDir location_;
diff --git a/components/reporting/storage/storage_unittest.cc b/components/reporting/storage/storage_unittest.cc
index f64464a..7590fcc 100644
--- a/components/reporting/storage/storage_unittest.cc
+++ b/components/reporting/storage/storage_unittest.cc
@@ -42,6 +42,7 @@
 
 using ::testing::_;
 using ::testing::Between;
+using ::testing::DoAll;
 using ::testing::Eq;
 using ::testing::HasSubstr;
 using ::testing::Invoke;
@@ -235,7 +236,7 @@
               sequenced_task_runner),
           key_generation_(std::move(key_generation)) {
       DETACH_FROM_SEQUENCE(mock_uploader_checker_);
-      upload_progress_.assign("Start\n");
+      upload_progress_.assign("\nStart\n");
     }
     MockUpload(const MockUpload& other) = delete;
     MockUpload& operator=(const MockUpload& other) = delete;
@@ -292,6 +293,7 @@
       upload_progress_.append("Complete: ")
           .append(status.ToString())
           .append("\n");
+      LOG(ERROR) << "TestUploader: " << upload_progress_ << "End\n";
       UploadComplete(uploader_id, status);
     }
 
@@ -319,7 +321,6 @@
    protected:
     virtual ~MockUpload() {
       DCHECK_CALLED_ON_VALID_SEQUENCE(mock_uploader_checker_);
-      LOG(ERROR) << "TestUploader: " << upload_progress_ << "End\n";
     }
 
    private:
@@ -349,9 +350,9 @@
     explicit TestUploader(StorageTest* self)
         : uploader_id_(next_uploader_id.fetch_add(1)),
           last_record_digest_map_(&self->last_record_digest_map_),
-          sequenced_task_runner_(self->sequenced_task_runner_),
+          main_thread_task_runner_(self->main_thread_task_runner_),
           mock_upload_(base::MakeRefCounted<::testing::NiceMock<MockUpload>>(
-              sequenced_task_runner_,
+              main_thread_task_runner_,
               base::BindOnce(&Storage::UpdateEncryptionKey,
                              base::Unretained(self->storage_.get()),
                              self->signed_encryption_key_))),
@@ -360,12 +361,14 @@
     }
 
     ~TestUploader() override {
+      DCHECK(!mock_upload_);
       DCHECK_CALLED_ON_VALID_SEQUENCE(test_uploader_checker_);
     }
 
     void ProcessRecord(EncryptedRecord encrypted_record,
                        base::OnceCallback<void(bool)> processed_cb) override {
       DCHECK_CALLED_ON_VALID_SEQUENCE(test_uploader_checker_);
+      DCHECK(mock_upload_);
       auto sequence_information = encrypted_record.sequence_information();
       if (!encrypted_record.has_encryption_info()) {
         // Wrapped record is not encrypted.
@@ -402,10 +405,11 @@
                     uint64_t count,
                     base::OnceCallback<void(bool)> processed_cb) override {
       DCHECK_CALLED_ON_VALID_SEQUENCE(test_uploader_checker_);
+      DCHECK(mock_upload_);
       // Verify generation match.
       if (generation_id_.has_value() &&
           generation_id_.value() != sequence_information.generation_id()) {
-        sequenced_task_runner_->PostTask(
+        main_thread_task_runner_->PostTask(
             FROM_HERE,
             base::BindOnce(
                 [](SequenceInformation sequence_information,
@@ -439,7 +443,7 @@
                           sequence_information.generation_id()),
           absl::nullopt);
 
-      sequenced_task_runner_->PostTask(
+      main_thread_task_runner_->PostTask(
           FROM_HERE,
           base::BindOnce(
               [](uint64_t count, SequenceInformation sequence_information,
@@ -462,9 +466,12 @@
 
     void Completed(Status status) override {
       DCHECK_CALLED_ON_VALID_SEQUENCE(test_uploader_checker_);
-      sequenced_task_runner_->PostTask(
-          FROM_HERE, base::BindOnce(&MockUpload::DoUploadComplete, mock_upload_,
-                                    uploader_id_, status));
+      DCHECK(mock_upload_);
+      main_thread_task_runner_->PostTask(
+          FROM_HERE,
+          base::BindOnce(&MockUpload::DoUploadComplete,
+                         std::move(mock_upload_),  // No longer needed.
+                         uploader_id_, status));
     }
 
     // Helper class for setting up mock uploader expectations of a successful
@@ -478,7 +485,8 @@
             uploader_(std::make_unique<TestUploader>(self)),
             uploader_id_(uploader_->uploader_id_),
             waiter_(waiter) {}
-
+      SetUp(const SetUp& other) = delete;
+      SetUp& operator=(const SetUp& other) = delete;
       ~SetUp() { CHECK(!uploader_) << "Missed 'Complete' call"; }
 
       std::unique_ptr<TestUploader> Complete(
@@ -492,8 +500,10 @@
                     UploadComplete(Eq(uploader_id_), Eq(status)))
             .InSequence(uploader_->test_upload_sequence_,
                         uploader_->test_encounter_sequence_)
-            .WillOnce(WithoutArgs(
-                Invoke(waiter_, &test::TestCallbackWaiter::Signal)));
+            .WillOnce(DoAll(
+                WithoutArgs(Invoke(waiter_, &test::TestCallbackWaiter::Signal)),
+                WithoutArgs(
+                    Invoke([]() { LOG(ERROR) << "Completion signaled"; }))));
         return std::move(uploader_);
       }
 
@@ -565,7 +575,8 @@
      public:
       explicit SetEmpty(StorageTest* self)
           : uploader_(std::make_unique<TestUploader>(self)) {}
-
+      SetEmpty(const SetEmpty& other) = delete;
+      SetEmpty& operator=(const SetEmpty& other) = delete;
       ~SetEmpty() { CHECK(!uploader_) << "Missed 'Complete' call"; }
 
       std::unique_ptr<TestUploader> Complete() {
@@ -592,7 +603,8 @@
      public:
       explicit SetKeyDelivery(StorageTest* self)
           : uploader_(std::make_unique<TestUploader>(self)) {}
-
+      SetKeyDelivery(const SetKeyDelivery& other) = delete;
+      SetKeyDelivery& operator=(const SetKeyDelivery& other) = delete;
       ~SetKeyDelivery() { CHECK(!uploader_) << "Missed 'Complete' call"; }
 
       std::unique_ptr<TestUploader> Complete() {
@@ -620,7 +632,7 @@
     void ScheduleVerifyRecord(SequenceInformation sequence_information,
                               WrappedRecord wrapped_record,
                               base::OnceCallback<void(bool)> processed_cb) {
-      sequenced_task_runner_->PostTask(
+      main_thread_task_runner_->PostTask(
           FROM_HERE,
           base::BindOnce(&TestUploader::VerifyRecord, base::Unretained(this),
                          std::move(sequence_information),
@@ -672,12 +684,12 @@
           if (it == last_record_digest_map_->end()) {
             // Previous record has not been seen yet, reschedule. This can
             // happen because decryption is done asynchronously and only sets on
-            // sequenced_task_runner_ after it. As a result, later record may
-            // get decrypted early and be posted to sequenced_task_runner_ for
+            // main_thread_task_runner_ after it. As a result, later record may
+            // get decrypted early and be posted to main_thread_task_runner_ for
             // verification before its predecessor. Rescheduling will move it
             // back in the sequence. Rescheduling may happen multiple times, but
             // once the earlier record is decrypted, it will be also posted to
-            // sequenced_task_runner_ and get its digest recorded, making it
+            // main_thread_task_runner_ and get its digest recorded, making it
             // ready for the current one. This is not an efficient method, but
             // is simple and good enough for the test.
             ScheduleVerifyRecord(std::move(sequence_information),
@@ -723,7 +735,8 @@
     absl::optional<int64_t> generation_id_;
     LastRecordDigestMap* const last_record_digest_map_;
 
-    scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_;
+    // Single task runner where all EXPECTs will happen.
+    const scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_;
 
     scoped_refptr<::testing::NiceMock<MockUpload>> mock_upload_;
 
@@ -816,7 +829,7 @@
   void AsyncStartMockUploader(
       UploaderInterface::UploadReason reason,
       UploaderInterface::UploaderInterfaceResultCb start_uploader_cb) {
-    sequenced_task_runner_->PostTask(
+    main_thread_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(
             [](UploaderInterface::UploadReason reason,
@@ -855,6 +868,8 @@
     record.set_data(std::string(data));
     record.set_destination(UPLOAD_EVENTS);
     record.set_dm_token("DM TOKEN");
+    LOG(ERROR) << "Write priority=" << priority << " data='" << record.data()
+               << "'";
     storage_->Write(priority, std::move(record), w.cb());
     return w.result();
   }
@@ -868,6 +883,11 @@
                     absl::optional<std::int64_t> sequencing_id,
                     bool force = false) {
     test::TestEvent<Status> c;
+    LOG(ERROR) << "Confirm priority=" << priority << " force=" << force
+               << " seq="
+               << (sequencing_id.has_value()
+                       ? base::NumberToString(sequencing_id.value())
+                       : "null");
     storage_->Confirm(priority, sequencing_id, force, c.cb());
     const Status c_result = c.result();
     ASSERT_OK(c_result) << c_result;
@@ -922,10 +942,9 @@
 
   base::test::TaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
-  const scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_{
-      base::ThreadPool::CreateSequencedTaskRunner(
-          {base::TaskPriority::BEST_EFFORT,
-           base::TaskShutdownBehavior::BLOCK_SHUTDOWN, base::MayBlock()})};
+  // Single task runner where all EXPECTs will happen - main thread.
+  const scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_{
+      base::ThreadTaskRunnerHandle::Get()};
 
   base::test::ScopedFeatureList scoped_feature_list_;
 
diff --git a/components/reporting/util/status.h b/components/reporting/util/status.h
index f1477aea..d432941d 100644
--- a/components/reporting/util/status.h
+++ b/components/reporting/util/status.h
@@ -16,7 +16,6 @@
 namespace reporting {
 namespace error {
 // These values must match error codes defined in google/rpc/code.proto
-// (https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto)
 // This also must match the order EnterpriseCloudReportingStatusCode at
 // tools/metrics/histograms/enums.xml and the integer of option shouldn't be
 // changed.
diff --git a/components/reporting/util/test_support_callbacks.h b/components/reporting/util/test_support_callbacks.h
index ca2a808d..4422e67 100644
--- a/components/reporting/util/test_support_callbacks.h
+++ b/components/reporting/util/test_support_callbacks.h
@@ -144,7 +144,7 @@
 //
 // Usage:
 // {
-//   TestCallbackAutoWaiter waiter;  // Implicitely Attach(1);
+//   TestCallbackAutoWaiter waiter;  // Implicitly Attach(1);
 //   ...
 //   Launch async activity, which will eventually do waiter.Signal();
 //   ...
diff --git a/components/segmentation_platform/internal/database/signal_key_internal.cc b/components/segmentation_platform/internal/database/signal_key_internal.cc
index 437b3b3..01341b69 100644
--- a/components/segmentation_platform/internal/database/signal_key_internal.cc
+++ b/components/segmentation_platform/internal/database/signal_key_internal.cc
@@ -53,7 +53,7 @@
     ClearKey(output);
     return false;
   }
-  base::BigEndianReader reader(input.data(), input.size());
+  auto reader = base::BigEndianReader::FromStringPiece(input);
   reader.ReadBytes(&output->prefix.kind, sizeof(output->prefix.kind));
   reader.Skip(sizeof(SignalKeyInternal::Prefix::padding));
   reader.ReadU64(&output->prefix.name_hash);
@@ -89,7 +89,7 @@
     ClearKeyPrefix(output);
     return false;
   }
-  base::BigEndianReader reader(input.data(), input.size());
+  auto reader = base::BigEndianReader::FromStringPiece(input);
   reader.ReadBytes(&output->kind, sizeof(output->kind));
   reader.Skip(sizeof(SignalKeyInternal::Prefix::padding));
   reader.ReadU64(&output->name_hash);
diff --git a/components/services/storage/indexed_db/scopes/leveldb_scopes_coding.cc b/components/services/storage/indexed_db/scopes/leveldb_scopes_coding.cc
index 4270425..d9576bec 100644
--- a/components/services/storage/indexed_db/scopes/leveldb_scopes_coding.cc
+++ b/components/services/storage/indexed_db/scopes/leveldb_scopes_coding.cc
@@ -100,7 +100,8 @@
         result << "<Invalid Seq Num>";
         break;
       }
-      base::ReadBigEndian(key_after_type.data(), &seq_num);
+      base::ReadBigEndian(
+          reinterpret_cast<const uint8_t*>(key_after_type.data()), &seq_num);
       result << seq_num;
       break;
     }
diff --git a/components/speech/chunked_byte_buffer.cc b/components/speech/chunked_byte_buffer.cc
index 39be3e7c..38bdd87 100644
--- a/components/speech/chunked_byte_buffer.cc
+++ b/components/speech/chunked_byte_buffer.cc
@@ -120,8 +120,7 @@
 size_t ChunkedByteBuffer::Chunk::ExpectedContentLength() const {
   DCHECK_EQ(header.size(), kHeaderLength);
   uint32_t content_length = 0;
-  base::ReadBigEndian(reinterpret_cast<const char*>(&header[0]),
-                      &content_length);
+  base::ReadBigEndian(&header[0], &content_length);
   return static_cast<size_t>(content_length);
 }
 
diff --git a/components/sync_user_events/user_event_sync_bridge.cc b/components/sync_user_events/user_event_sync_bridge.cc
index c51ddbc..0201c6d 100644
--- a/components/sync_user_events/user_event_sync_bridge.cc
+++ b/components/sync_user_events/user_event_sync_bridge.cc
@@ -46,7 +46,8 @@
 
 int64_t GetEventTimeFromStorageKey(const std::string& storage_key) {
   int64_t event_time;
-  base::ReadBigEndian(&storage_key[0], &event_time);
+  base::ReadBigEndian(reinterpret_cast<const uint8_t*>(&storage_key[0]),
+                      &event_time);
   return event_time;
 }
 
diff --git a/components/test/data/payments/payment_request_retry.html b/components/test/data/payments/payment_request_retry.html
index 71e2dd11..d57aaf2a 100644
--- a/components/test/data/payments/payment_request_retry.html
+++ b/components/test/data/payments/payment_request_retry.html
@@ -13,6 +13,7 @@
 </head>
 <body>
 <div><button onclick="buy()" id="buy">Retry test</button></div>
+<div><button onclick="buyWithUrlMethod()" id="buyWithUrlMethod">Retry test (with Url Method)</button></div>
 <pre id="result"></pre>
 <script src="util.js"></script>
 <script src="retry_helper.js"></script>
diff --git a/components/test/data/payments/retry.js b/components/test/data/payments/retry.js
index 6bc51c3..849766f0 100644
--- a/components/test/data/payments/retry.js
+++ b/components/test/data/payments/retry.js
@@ -11,13 +11,31 @@
  * Launches the PaymentRequest UI
  */
 function buy() { // eslint-disable-line no-unused-vars
+  buyWithMethod([{supportedMethods: 'basic-card'}]);
+}
+
+/**
+ * Launches the PaymentRequest UI
+ */
+function buyWithUrlMethod() { // eslint-disable-line no-unused-vars
+  buyWithMethod([
+    {supportedMethods: 'https://bobpay.com'},
+    {supportedMethods: 'https://kylepay.com/webpay'},
+  ]);
+}
+
+/**
+ * Launches the PaymentRequest UI
+ * @param {string} method The payment method to request
+ */
+function buyWithMethod(method) { // eslint-disable-line no-unused-vars
   var options = {
     requestPayerEmail: true,
     requestPayerName: true,
     requestPayerPhone: true,
     requestShipping: true,
   };
-  getPaymentResponse(options)
+  getPaymentResponseWithMethod(options, method)
       .then(function(response) {
         gPaymentResponse = response;
         var eventPromise = new Promise(function(resolve) {
diff --git a/components/test/data/payments/retry_helper.js b/components/test/data/payments/retry_helper.js
index 00e189b2..be7b013 100644
--- a/components/test/data/payments/retry_helper.js
+++ b/components/test/data/payments/retry_helper.js
@@ -12,7 +12,20 @@
  * @return {Promise<PaymentResponse>} Payment response
  */
 function getPaymentResponse(options) { // eslint-disable-line no-unused-vars
-  var methodData = [{supportedMethods: 'basic-card'}];
+  return getPaymentResponseWithMethod(
+      options, [{supportedMethods: 'basic-card'}]);
+}
+
+/**
+ * Pops up a payment sheet, allowing options to be
+ * passed in if particular values are needed.
+ *
+ * @param {PaymentOptions?} options Payment options
+ * @param {Array<Object>} methodData An array of payment method objects used as
+ *        the first parameter of the PaymentRequest API.
+ * @return {Promise<PaymentResponse>} Payment response
+ */
+function getPaymentResponseWithMethod(options, methodData) { // eslint-disable-line no-unused-vars, max-len
   var details = {
     total: {
       label: 'Total',
diff --git a/components/test/data/payments/show_promise/helper.js b/components/test/data/payments/show_promise/helper.js
new file mode 100644
index 0000000..26cec24
--- /dev/null
+++ b/components/test/data/payments/show_promise/helper.js
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2021 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**
+ * Launch PaymentRequest with a show promise that resolves with an empty
+ * dictionary. The payment method to be used is 'basic-card'.
+ */
+function buy() { // eslint-disable-line no-unused-vars
+    buyWithMethods('basic-card');
+}
+
+/**
+ * Launch PaymentRequest with a show promise that resolves with an empty
+ * dictionary. The payment method to be used is the current url of the page.
+ */
+function buyWithCurrentUrlMethod() { // eslint-disable-line no-unused-vars
+    buyWithMethods(window.location.href);
+}
+
+/**
+ * Launch PaymentRequest with a show promise that resolves with an empty
+ * dictionary. The payment method to be used is 'https://bobpay.com'.
+ */
+function buyWithUrlMethod() { // eslint-disable-line no-unused-vars
+    buyWithMethods('https://bobpay.com');
+}
diff --git a/components/test/data/payments/show_promise/invalid_details.html b/components/test/data/payments/show_promise/invalid_details.html
index 01b0fd51..3cefa571 100644
--- a/components/test/data/payments/show_promise/invalid_details.html
+++ b/components/test/data/payments/show_promise/invalid_details.html
@@ -16,6 +16,7 @@
   <div><button onclick="buy()" id="buy">Buy</button></div>
   <pre id="result"></pre>
   <script src="../util.js"></script>
+  <script src="helper.js"></script>
   <script src="app_installer.js"></script>
   <script src="invalid_details.js"></script>
 </body>
diff --git a/components/test/data/payments/show_promise/invalid_details.js b/components/test/data/payments/show_promise/invalid_details.js
index 3e2cdb02..065f53e2 100644
--- a/components/test/data/payments/show_promise/invalid_details.js
+++ b/components/test/data/payments/show_promise/invalid_details.js
@@ -6,16 +6,11 @@
 
 /**
  * Launch PaymentRequest with a show promise that resolve with invalid details.
- * @param {boolean} useUrlPaymentMethod - Whether URL payment method should be
- * used. Useful for payment handlers, which cannot use basic-card payment
- * method. By default, basic-card payment method is used.
+ * @param {string} supportedMethods The payment method that is supported by this
+ *        request.
  */
-function buy(useUrlPaymentMethod) { // eslint-disable-line no-unused-vars
+function buyWithMethods(supportedMethods) { // eslint-disable-line no-unused-vars, max-len
   try {
-    let supportedMethods = 'basic-card';
-    if (useUrlPaymentMethod) {
-      supportedMethods = window.location.href;
-    }
     new PaymentRequest([{supportedMethods}], {
       total: {
         label: 'PENDING TOTAL',
diff --git a/components/test/data/payments/show_promise/resolve_with_empty_dictionary.html b/components/test/data/payments/show_promise/resolve_with_empty_dictionary.html
index 4b33f3b..d794be6 100644
--- a/components/test/data/payments/show_promise/resolve_with_empty_dictionary.html
+++ b/components/test/data/payments/show_promise/resolve_with_empty_dictionary.html
@@ -14,8 +14,11 @@
 </head>
 <body>
   <div><button onclick="buy()" id="buy">Buy</button></div>
+  <div><button onclick="buyWithCurrentUrlMethod()" id="buyWithCurrentUrlMethod">Buy using the current URL as the method</button></div>
+  <div><button onclick="buyWithUrlMethod()" id="buyWithUrlMethod">Buy with URL method</button></div>
   <pre id="result"></pre>
   <script src="../util.js"></script>
+  <script src="helper.js"></script>
   <script src="app_installer.js"></script>
   <script src="resolve_with_empty_dictionary.js"></script>
 </body>
diff --git a/components/test/data/payments/show_promise/resolve_with_empty_dictionary.js b/components/test/data/payments/show_promise/resolve_with_empty_dictionary.js
index e8e6386..66c5fc2 100644
--- a/components/test/data/payments/show_promise/resolve_with_empty_dictionary.js
+++ b/components/test/data/payments/show_promise/resolve_with_empty_dictionary.js
@@ -7,16 +7,11 @@
 /**
  * Launch PaymentRequest with a show promise that resolves with an empty
  * dictionary.
- * @param {boolean} useUrlPaymentMethod - Whether URL payment method should be
- * used. Useful for payment handlers, which cannot use basic-card payment
- * method. By default, basic-card payment method is used.
+ * @param {string} supportedMethods The payment method that is supported by this
+ *        request.
  */
-function buy(useUrlPaymentMethod) { // eslint-disable-line no-unused-vars
+function buyWithMethods(supportedMethods) { // eslint-disable-line no-unused-vars, max-len
   try {
-    let supportedMethods = 'basic-card';
-    if (useUrlPaymentMethod) {
-      supportedMethods = window.location.href;
-    }
     var request = new PaymentRequest(
         [{supportedMethods}], {
           total: {
diff --git a/components/test/data/payments/show_promise/resolve_with_empty_lists.html b/components/test/data/payments/show_promise/resolve_with_empty_lists.html
index 3ed0bb3..482a981 100644
--- a/components/test/data/payments/show_promise/resolve_with_empty_lists.html
+++ b/components/test/data/payments/show_promise/resolve_with_empty_lists.html
@@ -16,6 +16,7 @@
   <div><button onclick="buy()" id="buy">Buy</button></div>
   <pre id="result"></pre>
   <script src="../util.js"></script>
+  <script src="helper.js"></script>
   <script src="app_installer.js"></script>
   <script src="resolve_with_empty_lists.js"></script>
 </body>
diff --git a/components/test/data/payments/show_promise/resolve_with_empty_lists.js b/components/test/data/payments/show_promise/resolve_with_empty_lists.js
index 955cb84a..76ce27a 100644
--- a/components/test/data/payments/show_promise/resolve_with_empty_lists.js
+++ b/components/test/data/payments/show_promise/resolve_with_empty_lists.js
@@ -7,16 +7,11 @@
 /**
  * Launch PaymentRequest by resolving the promised passed into the shoe() method
  * with empty lists of display items, modifiers, and shipping options.
- * @param {boolean} useUrlPaymentMethod - Whether URL payment method should be
- * used. Useful for payment handlers, which cannot use basic-card payment
- * method. By default, basic-card payment method is used.
+ * @param {string} supportedMethods The payment method that is supported by this
+ *        request.
  */
-function buy(useUrlPaymentMethod) { // eslint-disable-line no-unused-vars
+function buyWithMethods(supportedMethods) { // eslint-disable-line no-unused-vars, max-len
   try {
-    let supportedMethods = 'basic-card';
-    if (useUrlPaymentMethod) {
-      supportedMethods = window.location.href;
-    }
     var request = new PaymentRequest(
         [{supportedMethods}], {
           total: {label: 'Total', amount: {currency: 'USD', value: '1.00'}},
diff --git a/components/test/data/payments/show_promise/single_option_shipping.html b/components/test/data/payments/show_promise/single_option_shipping.html
index 274bf8f76..ec5542b 100644
--- a/components/test/data/payments/show_promise/single_option_shipping.html
+++ b/components/test/data/payments/show_promise/single_option_shipping.html
@@ -16,6 +16,7 @@
   <div><button onclick="buy()" id="buy">Buy</button></div>
   <pre id="result"></pre>
   <script src="../util.js"></script>
+  <script src="helper.js"></script>
   <script src="app_installer.js"></script>
   <script src="single_option_shipping.js"></script>
 </body>
diff --git a/components/test/data/payments/show_promise/single_option_shipping.js b/components/test/data/payments/show_promise/single_option_shipping.js
index 3fd7a65..179e6ed 100644
--- a/components/test/data/payments/show_promise/single_option_shipping.js
+++ b/components/test/data/payments/show_promise/single_option_shipping.js
@@ -7,16 +7,11 @@
 /**
  * Launch PaymentRequest with a show promise and a single pre-selected option
  * for shipping worldwide.
- * @param {boolean} useUrlPaymentMethod - Whether URL payment method should be
- * used. Useful for payment handlers, which cannot use basic-card payment
- * method. By default, basic-card payment method is used.
+ * @param {string} supportedMethods The payment method that is supported by this
+ *        request.
  */
-function buy(useUrlPaymentMethod) { // eslint-disable-line no-unused-vars
+function buyWithMethods(supportedMethods) { // eslint-disable-line no-unused-vars, max-len
   try {
-    let supportedMethods = 'basic-card';
-    if (useUrlPaymentMethod) {
-      supportedMethods = window.location.href;
-    }
     new PaymentRequest(
         [{supportedMethods}],
         {
diff --git a/components/test/data/payments/show_promise/single_option_shipping_with_update.html b/components/test/data/payments/show_promise/single_option_shipping_with_update.html
index 3edda88f..f4d8468 100644
--- a/components/test/data/payments/show_promise/single_option_shipping_with_update.html
+++ b/components/test/data/payments/show_promise/single_option_shipping_with_update.html
@@ -16,6 +16,7 @@
   <div><button onclick="buy()" id="buy">Buy</button></div>
   <pre id="result"></pre>
   <script src="../util.js"></script>
+  <script src="helper.js"></script>
   <script src="app_installer.js"></script>
   <script src="single_option_shipping_with_update.js"></script>
 </body>
diff --git a/components/test/data/payments/show_promise/single_option_shipping_with_update.js b/components/test/data/payments/show_promise/single_option_shipping_with_update.js
index b419c1f..f374ccd 100644
--- a/components/test/data/payments/show_promise/single_option_shipping_with_update.js
+++ b/components/test/data/payments/show_promise/single_option_shipping_with_update.js
@@ -8,15 +8,10 @@
  * Launch PaymentRequest with a show promise and a single pre-selected option
  * for shipping worldwide and a handler for shipping address change events that
  * does not change anything.
- * @param {DOMString} useUrlPaymentMethod - Whether window.location.href should
- * be used as the payment method. Useful for testing service workers, which do
- * not support basic-card payment method.
+ * @param {string} supportedMethods The payment method that is supported by this
+ *        request.
  */
-function buy(useUrlPaymentMethod) { // eslint-disable-line no-unused-vars
-  let paymentMethod = 'basic-card';
-  if (useUrlPaymentMethod) {
-    paymentMethod = window.location.href;
-  }
+function buyWithMethods(supportedMethods) { // eslint-disable-line no-unused-vars, max-len
   var finalizedDetails = {
     total: {label: 'Total', amount: {currency: 'USD', value: '1.00'}},
     shippingOptions: [{
@@ -29,7 +24,7 @@
 
   try {
     var request = new PaymentRequest(
-        [{supportedMethods: paymentMethod}], {
+        [{supportedMethods}], {
           total: {
             label: 'PENDING TOTAL',
             amount: {currency: 'USD', value: '99.99'},
@@ -62,3 +57,4 @@
     print(error.message);
   }
 }
+
diff --git a/components/test/data/payments/show_promise/us_only_shipping.html b/components/test/data/payments/show_promise/us_only_shipping.html
index f659a42..32c717d 100644
--- a/components/test/data/payments/show_promise/us_only_shipping.html
+++ b/components/test/data/payments/show_promise/us_only_shipping.html
@@ -16,6 +16,7 @@
   <div><button onclick="buy()" id="buy">Buy</button></div>
   <pre id="result"></pre>
   <script src="../util.js"></script>
+  <script src="helper.js"></script>
   <script src="app_installer.js"></script>
   <script src="us_only_shipping.js"></script>
 </body>
diff --git a/components/test/data/payments/show_promise/us_only_shipping.js b/components/test/data/payments/show_promise/us_only_shipping.js
index 13cf50b..391f15f1 100644
--- a/components/test/data/payments/show_promise/us_only_shipping.js
+++ b/components/test/data/payments/show_promise/us_only_shipping.js
@@ -6,14 +6,10 @@
 
 /**
  * Launch PaymentRequest with a show promise and US-only shipping.
- * @param {bool} useWindowUrlPaymentMethod - Whether the window URL is used as
- * the payment method name. If false, then 'basic-card' is used instead.
+ * @param {string} supportedMethods The payment method that is supported by this
+ *        request.
  */
-function buy(useWindowUrlPaymentMethod) { // eslint-disable-line no-unused-vars
-  supportedMethods = 'basic-card';
-  if (useWindowUrlPaymentMethod) {
-    supportedMethods = window.location.href;
-  }
+function buyWithMethods(supportedMethods) { // eslint-disable-line no-unused-vars, max-len
   var detailsForUSAddress = {
     shippingOptions: [{
       id: '1',
diff --git a/components/url_formatter/spoof_checks/idn_spoof_checker.cc b/components/url_formatter/spoof_checks/idn_spoof_checker.cc
index 9b9c03f..90981674 100644
--- a/components/url_formatter/spoof_checks/idn_spoof_checker.cc
+++ b/components/url_formatter/spoof_checks/idn_spoof_checker.cc
@@ -4,6 +4,7 @@
 
 #include "components/url_formatter/spoof_checks/idn_spoof_checker.h"
 
+#include "base/bits.h"
 #include "base/check_op.h"
 #include "base/containers/contains.h"
 #include "base/logging.h"
@@ -26,15 +27,6 @@
 
 namespace {
 
-uint8_t BitLength(uint32_t input) {
-  uint8_t number_of_bits = 0;
-  while (input != 0) {
-    number_of_bits++;
-    input >>= 1;
-  }
-  return number_of_bits;
-}
-
 class TopDomainPreloadDecoder : public net::extras::PreloadDecoder {
  public:
   using net::extras::PreloadDecoder::PreloadDecoder;
@@ -46,8 +38,9 @@
                  bool* out_found) override {
     // Make sure the assigned bit length is enough to encode all SkeletonType
     // values.
-    DCHECK_EQ(kSkeletonTypeBitLength,
-              BitLength(url_formatter::SkeletonType::kMaxValue));
+    DCHECK_EQ(
+        kSkeletonTypeBitLength,
+        base::bits::Log2Floor(url_formatter::SkeletonType::kMaxValue) + 1);
 
     bool is_same_skeleton;
 
diff --git a/components/url_formatter/spoof_checks/top_domains/trie_entry.cc b/components/url_formatter/spoof_checks/top_domains/trie_entry.cc
index a503aa2..6ba13198 100644
--- a/components/url_formatter/spoof_checks/top_domains/trie_entry.cc
+++ b/components/url_formatter/spoof_checks/top_domains/trie_entry.cc
@@ -3,21 +3,13 @@
 // found in the LICENSE file.
 
 #include "components/url_formatter/spoof_checks/top_domains/trie_entry.h"
+#include "base/bits.h"
 #include "base/strings/string_util.h"
 #include "net/tools/huffman_trie/trie/trie_bit_buffer.h"
 #include "net/tools/huffman_trie/trie/trie_writer.h"
 
 namespace url_formatter {
 
-uint8_t BitLength(uint32_t input) {
-  uint8_t number_of_bits = 0;
-  while (input != 0) {
-    number_of_bits++;
-    input >>= 1;
-  }
-  return number_of_bits;
-}
-
 namespace top_domains {
 
 TopDomainTrieEntry::TopDomainTrieEntry(
@@ -39,7 +31,7 @@
   // Make sure the assigned bit length is enough to encode all SkeletonType
   // values.
   DCHECK_EQ(kSkeletonTypeBitLength,
-            BitLength(url_formatter::SkeletonType::kMaxValue));
+            base::bits::Log2Floor(url_formatter::SkeletonType::kMaxValue) + 1);
 
   if (entry_->skeleton == entry_->top_domain) {
     writer->WriteBit(1);
diff --git a/components/url_rewrite/BUILD.gn b/components/url_rewrite/BUILD.gn
new file mode 100644
index 0000000..f5bb6d0
--- /dev/null
+++ b/components/url_rewrite/BUILD.gn
@@ -0,0 +1,11 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("unit_tests") {
+  testonly = true
+  deps = [
+    "//components/url_rewrite/browser:unit_tests",
+    "//components/url_rewrite/common:unit_tests",
+  ]
+}
diff --git a/components/url_rewrite/OWNERS b/components/url_rewrite/OWNERS
new file mode 100644
index 0000000..56041f4
--- /dev/null
+++ b/components/url_rewrite/OWNERS
@@ -0,0 +1,4 @@
+fdegans@chromium.org
+
+# Backup reviewers.
+file://build/fuchsia/OWNERS
diff --git a/components/url_rewrite/README.md b/components/url_rewrite/README.md
new file mode 100644
index 0000000..54f8fa7
--- /dev/null
+++ b/components/url_rewrite/README.md
@@ -0,0 +1,8 @@
+The URL Rewrite component provides utilities that allow adjusting URLRequest
+based on the provided URL rewrite rules defined in UrlRequestRewrite mojom.
+
+The UrlRequestRulesReceiver mojom interface is used by browser process to set
+renderer process rewrite rules.
+
+The URLLoaderThrottle implementation applies the rewrite rules to the
+URLRequest.
diff --git a/components/url_rewrite/browser/BUILD.gn b/components/url_rewrite/browser/BUILD.gn
new file mode 100644
index 0000000..f04b43b7
--- /dev/null
+++ b/components/url_rewrite/browser/BUILD.gn
@@ -0,0 +1,31 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//testing/test.gni")
+
+source_set("browser") {
+  public = [ "url_request_rewrite_rules_validation.h" ]
+  sources = [ "url_request_rewrite_rules_validation.cc" ]
+  deps = [
+    "//base",
+    "//net",
+    "//services/network/public/cpp:cpp_base",
+    "//third_party/blink/public/common",
+    "//url",
+  ]
+  public_deps = [ "//components/url_rewrite/common" ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [ "url_request_rewrite_rules_validation_unittest.cc" ]
+  deps = [
+    ":browser",
+    "//base/test:test_support",
+    "//components/url_rewrite/mojom",
+    "//testing/gmock",
+    "//testing/gtest",
+    "//third_party/blink/public/common",
+  ]
+}
diff --git a/components/url_rewrite/browser/DEPS b/components/url_rewrite/browser/DEPS
new file mode 100644
index 0000000..3873976
--- /dev/null
+++ b/components/url_rewrite/browser/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+  "+net/http",
+  "+services/network/public",
+  "+url",
+]
diff --git a/fuchsia/engine/browser/url_request_rewrite_rules_validation.cc b/components/url_rewrite/browser/url_request_rewrite_rules_validation.cc
similarity index 97%
rename from fuchsia/engine/browser/url_request_rewrite_rules_validation.cc
rename to components/url_rewrite/browser/url_request_rewrite_rules_validation.cc
index b9dc47f..2a9cc3ba 100644
--- a/fuchsia/engine/browser/url_request_rewrite_rules_validation.cc
+++ b/components/url_rewrite/browser/url_request_rewrite_rules_validation.cc
@@ -2,11 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "fuchsia/engine/browser/url_request_rewrite_rules_validation.h"
+#include "components/url_rewrite/browser/url_request_rewrite_rules_validation.h"
 
 #include "base/ranges/algorithm.h"
 #include "base/strings/strcat.h"
 #include "net/http/http_util.h"
+#include "url/url_constants.h"
 
 namespace url_rewrite {
 namespace {
diff --git a/components/url_rewrite/browser/url_request_rewrite_rules_validation.h b/components/url_rewrite/browser/url_request_rewrite_rules_validation.h
new file mode 100644
index 0000000..7069460
--- /dev/null
+++ b/components/url_rewrite/browser/url_request_rewrite_rules_validation.h
@@ -0,0 +1,17 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_URL_REWRITE_BROWSER_URL_REQUEST_REWRITE_RULES_VALIDATION_H_
+#define COMPONENTS_URL_REWRITE_BROWSER_URL_REQUEST_REWRITE_RULES_VALIDATION_H_
+
+#include "components/url_rewrite/common/url_request_rewrite_rules.h"
+
+namespace url_rewrite {
+
+// Returns true if |rules| have valid data in them, false otherwise.
+bool ValidateRules(const mojom::UrlRequestRewriteRules* rules);
+
+}  // namespace url_rewrite
+
+#endif  // COMPONENTS_URL_REWRITE_BROWSER_URL_REQUEST_REWRITE_RULES_VALIDATION_H_
diff --git a/fuchsia/engine/browser/url_request_rewrite_rules_validation_unittest.cc b/components/url_rewrite/browser/url_request_rewrite_rules_validation_unittest.cc
similarity index 98%
rename from fuchsia/engine/browser/url_request_rewrite_rules_validation_unittest.cc
rename to components/url_rewrite/browser/url_request_rewrite_rules_validation_unittest.cc
index bc1361e..393c625 100644
--- a/fuchsia/engine/browser/url_request_rewrite_rules_validation_unittest.cc
+++ b/components/url_rewrite/browser/url_request_rewrite_rules_validation_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "fuchsia/engine/browser/url_request_rewrite_rules_validation.h"
+#include "components/url_rewrite/browser/url_request_rewrite_rules_validation.h"
 
 #include "base/run_loop.h"
 #include "base/strings/string_piece.h"
@@ -10,6 +10,7 @@
 
 namespace url_rewrite {
 namespace {
+
 mojom::UrlRequestActionPtr CreateRewriteAddHeaders(
     base::StringPiece header_name,
     base::StringPiece header_value) {
diff --git a/components/url_rewrite/common/BUILD.gn b/components/url_rewrite/common/BUILD.gn
new file mode 100644
index 0000000..4e29c73
--- /dev/null
+++ b/components/url_rewrite/common/BUILD.gn
@@ -0,0 +1,35 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//testing/test.gni")
+
+source_set("common") {
+  public = [
+    "url_loader_throttle.h",
+    "url_request_rewrite_rules.h",
+  ]
+  sources = [ "url_loader_throttle.cc" ]
+  deps = [
+    "//net",
+    "//url",
+  ]
+  public_deps = [
+    "//base",
+    "//components/url_rewrite/mojom",
+    "//third_party/blink/public/common",
+  ]
+}
+
+source_set("unit_tests") {
+  testonly = true
+  sources = [ "url_loader_throttle_unittest.cc" ]
+  deps = [
+    ":common",
+    "//base/test:test_support",
+    "//components/url_rewrite/mojom",
+    "//testing/gmock",
+    "//testing/gtest",
+    "//third_party/blink/public/common",
+  ]
+}
diff --git a/components/url_rewrite/common/DEPS b/components/url_rewrite/common/DEPS
new file mode 100644
index 0000000..027084e0
--- /dev/null
+++ b/components/url_rewrite/common/DEPS
@@ -0,0 +1,6 @@
+include_rules = [
+  "+net/base",
+  "+services/network/public",
+  "+third_party/blink/public/common",
+  "+url",
+]
diff --git a/fuchsia/engine/common/web_engine_url_loader_throttle.cc b/components/url_rewrite/common/url_loader_throttle.cc
similarity index 86%
rename from fuchsia/engine/common/web_engine_url_loader_throttle.cc
rename to components/url_rewrite/common/url_loader_throttle.cc
index 821e32bc..c13598a 100644
--- a/fuchsia/engine/common/web_engine_url_loader_throttle.cc
+++ b/components/url_rewrite/common/url_loader_throttle.cc
@@ -2,20 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "fuchsia/engine/common/web_engine_url_loader_throttle.h"
+#include "components/url_rewrite/common/url_loader_throttle.h"
 
 #include <string>
 
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
-#include "fuchsia/engine/common/cors_exempt_headers.h"
 #include "net/base/net_errors.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "url/url_constants.h"
 
+namespace url_rewrite {
 namespace {
 
-// Returnns a string representing the URL stripped of query and ref.
+// Returns a string representing the URL stripped of query and ref.
 std::string ClearUrlQueryRef(const GURL& url) {
   GURL::Replacements replacements;
   replacements.ClearQuery();
@@ -169,19 +169,22 @@
 
 }  // namespace
 
-WebEngineURLLoaderThrottle::WebEngineURLLoaderThrottle(
-    scoped_refptr<url_rewrite::UrlRequestRewriteRules> rules)
-    : rules_(rules) {
+URLLoaderThrottle::URLLoaderThrottle(
+    scoped_refptr<url_rewrite::UrlRequestRewriteRules> rules,
+    IsHeaderCorsExemptCallback is_header_cors_exempt_callback)
+    : rules_(std::move(rules)),
+      is_header_cors_exempt_callback_(
+          std::move(is_header_cors_exempt_callback)) {
   DCHECK(rules_);
+  DCHECK(is_header_cors_exempt_callback_);
 }
 
-WebEngineURLLoaderThrottle::~WebEngineURLLoaderThrottle() = default;
+URLLoaderThrottle::~URLLoaderThrottle() = default;
 
-void WebEngineURLLoaderThrottle::DetachFromCurrentSequence() {}
+void URLLoaderThrottle::DetachFromCurrentSequence() {}
 
-void WebEngineURLLoaderThrottle::WillStartRequest(
-    network::ResourceRequest* request,
-    bool* defer) {
+void URLLoaderThrottle::WillStartRequest(network::ResourceRequest* request,
+                                         bool* defer) {
   if (!IsRequestAllowed(request, rules_->data)) {
     delegate_->CancelWithError(net::ERR_ABORTED,
                                "Resource load blocked by embedder policy.");
@@ -193,14 +196,13 @@
   *defer = false;
 }
 
-bool WebEngineURLLoaderThrottle::makes_unsafe_redirect() {
+bool URLLoaderThrottle::makes_unsafe_redirect() {
   // WillStartRequest() does not make cross-scheme redirects.
   return false;
 }
 
-void WebEngineURLLoaderThrottle::ApplyRule(
-    network::ResourceRequest* request,
-    const mojom::UrlRequestRulePtr& rule) {
+void URLLoaderThrottle::ApplyRule(network::ResourceRequest* request,
+                                  const mojom::UrlRequestRulePtr& rule) {
   if (!RuleFiltersMatchRequest(request, rule))
     return;
 
@@ -208,7 +210,7 @@
     ApplyRewrite(request, rewrite);
 }
 
-void WebEngineURLLoaderThrottle::ApplyRewrite(
+void URLLoaderThrottle::ApplyRewrite(
     network::ResourceRequest* request,
     const mojom::UrlRequestActionPtr& rewrite) {
   switch (rewrite->which()) {
@@ -235,7 +237,7 @@
   NOTREACHED();  // Invalid enum value.
 }
 
-void WebEngineURLLoaderThrottle::ApplyAddHeaders(
+void URLLoaderThrottle::ApplyAddHeaders(
     network::ResourceRequest* request,
     const mojom::UrlRequestRewriteAddHeadersPtr& add_headers) {
   // Bucket each |header| into the regular/CORS-compliant header list or the
@@ -246,10 +248,12 @@
       // Skip headers already present in the request at this point.
       continue;
     }
-    if (IsHeaderCorsExempt(header->name)) {
+    if (is_header_cors_exempt_callback_.Run(header->name)) {
       request->cors_exempt_headers.SetHeader(header->name, header->value);
     } else {
       request->headers.SetHeader(header->name, header->value);
     }
   }
 }
+
+}  // namespace url_rewrite
diff --git a/components/url_rewrite/common/url_loader_throttle.h b/components/url_rewrite/common/url_loader_throttle.h
new file mode 100644
index 0000000..a92e716c
--- /dev/null
+++ b/components/url_rewrite/common/url_loader_throttle.h
@@ -0,0 +1,61 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_URL_REWRITE_COMMON_URL_LOADER_THROTTLE_H_
+#define COMPONENTS_URL_REWRITE_COMMON_URL_LOADER_THROTTLE_H_
+
+#include "components/url_rewrite/common/url_request_rewrite_rules.h"
+#include "third_party/blink/public/common/loader/url_loader_throttle.h"
+
+namespace network {
+struct ResourceRequest;
+}
+
+namespace url_rewrite {
+
+// Implements a URLLoaderThrottle that applies network request rewrites provided
+// through the |UrlRequestRewriteRules| rules.
+class URLLoaderThrottle : public blink::URLLoaderThrottle {
+ public:
+  // A callback that checks if provided header is CORS exempt. The
+  // implementation must be case-insensitive.
+  using IsHeaderCorsExemptCallback =
+      base::RepeatingCallback<bool(base::StringPiece)>;
+
+  URLLoaderThrottle(
+      scoped_refptr<url_rewrite::UrlRequestRewriteRules> rules,
+      IsHeaderCorsExemptCallback is_header_cors_exempt_callback);
+  ~URLLoaderThrottle() override;
+
+  URLLoaderThrottle(const URLLoaderThrottle&) = delete;
+  URLLoaderThrottle& operator=(const URLLoaderThrottle&) = delete;
+
+  // blink::URLLoaderThrottle implementation.
+  void DetachFromCurrentSequence() override;
+  void WillStartRequest(network::ResourceRequest* request,
+                        bool* defer) override;
+  bool makes_unsafe_redirect() override;
+
+ private:
+  // Applies transformations specified by |rule| to |request|, conditional on
+  // the matching criteria of |rule|.
+  void ApplyRule(network::ResourceRequest* request,
+                 const mojom::UrlRequestRulePtr& rule);
+
+  // Applies |rewrite| transformations to |request|.
+  void ApplyRewrite(network::ResourceRequest* request,
+                    const mojom::UrlRequestActionPtr& rewrite);
+
+  // Adds HTTP headers from |add_headers| to |request|.
+  void ApplyAddHeaders(
+      network::ResourceRequest* request,
+      const mojom::UrlRequestRewriteAddHeadersPtr& add_headers);
+
+  scoped_refptr<url_rewrite::UrlRequestRewriteRules> rules_;
+  IsHeaderCorsExemptCallback is_header_cors_exempt_callback_;
+};
+
+}  // namespace url_rewrite
+
+#endif  // COMPONENTS_URL_REWRITE_COMMON_URL_LOADER_THROTTLE_H_
diff --git a/fuchsia/engine/common/web_engine_url_loader_throttle_unittest.cc b/components/url_rewrite/common/url_loader_throttle_unittest.cc
similarity index 84%
rename from fuchsia/engine/common/web_engine_url_loader_throttle_unittest.cc
rename to components/url_rewrite/common/url_loader_throttle_unittest.cc
index f632122a..341faa4b 100644
--- a/fuchsia/engine/common/web_engine_url_loader_throttle_unittest.cc
+++ b/components/url_rewrite/common/url_loader_throttle_unittest.cc
@@ -2,18 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "fuchsia/engine/common/web_engine_url_loader_throttle.h"
+#include "components/url_rewrite/common/url_loader_throttle.h"
 
 #include <string>
 #include <utility>
 
 #include "base/strings/string_piece.h"
+#include "base/test/bind.h"
 #include "base/test/task_environment.h"
-#include "fuchsia/engine/common/cors_exempt_headers.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/loader/url_loader_throttle.h"
 
+namespace url_rewrite {
 namespace {
 
 constexpr char kMixedCaseCorsExemptHeader[] = "CoRs-ExEmPt";
@@ -24,20 +25,32 @@
 
 }  // namespace
 
-class WebEngineURLLoaderThrottleTest : public testing::Test {
+class URLLoaderThrottleTest : public testing::Test {
  public:
-  WebEngineURLLoaderThrottleTest()
+  URLLoaderThrottleTest()
       : task_environment_(base::test::TaskEnvironment::MainThreadType::IO) {}
-  ~WebEngineURLLoaderThrottleTest() override = default;
+  ~URLLoaderThrottleTest() override = default;
 
-  void SetUp() override { SetCorsExemptHeaders({}); }
+  URLLoaderThrottle::IsHeaderCorsExemptCallback CreateCorsExemptHeadersCallback(
+      std::vector<std::string> cors_exempt_headers) {
+    return base::BindLambdaForTesting(
+        [cors_exempt_headers](base::StringPiece header) {
+          LOG(INFO) << "HEADER: " << header;
+          for (const auto& exempt_header : cors_exempt_headers) {
+            if (base::EqualsCaseInsensitiveASCII(header, exempt_header)) {
+              return true;
+            }
+          }
+          return false;
+        });
+  }
 
  protected:
   base::test::SingleThreadTaskEnvironment task_environment_;
 };
 
 // Tests rules are properly applied when wildcard-filtering is used on hosts.
-TEST_F(WebEngineURLLoaderThrottleTest, WildcardHosts) {
+TEST_F(URLLoaderThrottleTest, WildcardHosts) {
   mojom::UrlRequestRewriteAddHeadersPtr add_headers =
       mojom::UrlRequestRewriteAddHeaders::New();
   add_headers->headers.push_back(mojom::UrlHeader::New("Header", "Value"));
@@ -48,13 +61,13 @@
   mojom::UrlRequestRulePtr rule = mojom::UrlRequestRule::New();
   rule->hosts_filter = absl::optional<std::vector<std::string>>({"*.test.net"});
   rule->actions = std::move(actions);
-
   mojom::UrlRequestRewriteRulesPtr rules = mojom::UrlRequestRewriteRules::New();
   rules->rules.push_back(std::move(rule));
 
-  WebEngineURLLoaderThrottle throttle(
+  URLLoaderThrottle throttle(
       base::MakeRefCounted<url_rewrite::UrlRequestRewriteRules>(
-          std::move(rules)));
+          std::move(rules)),
+      CreateCorsExemptHeadersCallback({}));
   bool defer = false;
 
   network::ResourceRequest request1;
@@ -80,12 +93,7 @@
 
 // Verifies that injected headers are correctly exempted from CORS checks if
 // their names are registered as CORS exempt.
-TEST_F(WebEngineURLLoaderThrottleTest, CorsAwareHeaders) {
-  // Use the mixed case form for CORS exempt header #1, and the uppercased form
-  // of header #2.
-  SetCorsExemptHeaders(
-      {kMixedCaseCorsExemptHeader, kUpperCaseCorsExemptHeader2});
-
+TEST_F(URLLoaderThrottleTest, CorsAwareHeaders) {
   mojom::UrlRequestRewriteAddHeadersPtr add_headers =
       mojom::UrlRequestRewriteAddHeaders::New();
   add_headers->headers.push_back(
@@ -109,9 +117,13 @@
   mojom::UrlRequestRewriteRulesPtr rules = mojom::UrlRequestRewriteRules::New();
   rules->rules.push_back(std::move(rule));
 
-  WebEngineURLLoaderThrottle throttle(
+  // Use the mixed case form for CORS exempt header #1, and the uppercased form
+  // of header #2.
+  URLLoaderThrottle throttle(
       base::MakeRefCounted<url_rewrite::UrlRequestRewriteRules>(
-          std::move(rules)));
+          std::move(rules)),
+      CreateCorsExemptHeadersCallback(
+          {kMixedCaseCorsExemptHeader, kUpperCaseCorsExemptHeader2}));
 
   network::ResourceRequest request;
   request.url = GURL("http://test.net");
@@ -135,7 +147,7 @@
 
 // Tests URL replacement rules that replace to a data URL do not append query or
 // ref from the original URL.
-TEST_F(WebEngineURLLoaderThrottleTest, DataReplacementUrl) {
+TEST_F(URLLoaderThrottleTest, DataReplacementUrl) {
   const char kCssDataURI[] = "data:text/css,";
 
   mojom::UrlRequestRewriteReplaceUrlPtr replace_url =
@@ -153,9 +165,10 @@
   mojom::UrlRequestRewriteRulesPtr rules = mojom::UrlRequestRewriteRules::New();
   rules->rules.push_back(std::move(rule));
 
-  WebEngineURLLoaderThrottle throttle(
+  URLLoaderThrottle throttle(
       base::MakeRefCounted<url_rewrite::UrlRequestRewriteRules>(
-          std::move(rules)));
+          std::move(rules)),
+      CreateCorsExemptHeadersCallback({}));
   bool defer = false;
 
   network::ResourceRequest request;
@@ -192,7 +205,7 @@
 
 // Tests that resource loads can be allowed or blocked based on the
 // UrlRequestAction policy.
-TEST_F(WebEngineURLLoaderThrottleTest, AllowAndDeny) {
+TEST_F(URLLoaderThrottleTest, AllowAndDeny) {
   mojom::UrlRequestRewriteRulesPtr rules = mojom::UrlRequestRewriteRules::New();
 
   {
@@ -209,9 +222,10 @@
     rules->rules.push_back(std::move(rule));
   }
 
-  WebEngineURLLoaderThrottle throttle(
+  URLLoaderThrottle throttle(
       base::MakeRefCounted<url_rewrite::UrlRequestRewriteRules>(
-          std::move(rules)));
+          std::move(rules)),
+      CreateCorsExemptHeadersCallback({}));
   bool defer = false;
 
   TestThrottleDelegate delegate;
@@ -231,3 +245,5 @@
   EXPECT_EQ(delegate.cancel_reason(),
             "Resource load blocked by embedder policy.");
 }
+
+}  // namespace url_rewrite
diff --git a/components/url_rewrite/common/url_request_rewrite_rules.h b/components/url_rewrite/common/url_request_rewrite_rules.h
new file mode 100644
index 0000000..d95a00d6
--- /dev/null
+++ b/components/url_rewrite/common/url_request_rewrite_rules.h
@@ -0,0 +1,18 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_URL_REWRITE_COMMON_URL_REQUEST_REWRITE_RULES_H_
+#define COMPONENTS_URL_REWRITE_COMMON_URL_REQUEST_REWRITE_RULES_H_
+
+#include "base/memory/ref_counted.h"
+#include "components/url_rewrite/mojom/url_request_rewrite.mojom.h"
+
+namespace url_rewrite {
+
+using UrlRequestRewriteRules =
+    base::RefCountedData<mojom::UrlRequestRewriteRulesPtr>;
+
+}
+
+#endif  // COMPONENTS_URL_REWRITE_COMMON_URL_REQUEST_REWRITE_RULES_H_
diff --git a/components/url_rewrite/mojom/BUILD.gn b/components/url_rewrite/mojom/BUILD.gn
new file mode 100644
index 0000000..d92f546
--- /dev/null
+++ b/components/url_rewrite/mojom/BUILD.gn
@@ -0,0 +1,13 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//mojo/public/tools/bindings/mojom.gni")
+
+mojom("mojom") {
+  sources = [ "url_request_rewrite.mojom" ]
+  public_deps = [
+    "//mojo/public/mojom/base",
+    "//url/mojom:url_mojom_gurl",
+  ]
+}
diff --git a/components/url_rewrite/mojom/DEPS b/components/url_rewrite/mojom/DEPS
new file mode 100644
index 0000000..7831fa4
--- /dev/null
+++ b/components/url_rewrite/mojom/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "+mojo/public/cpp/bindings",
+  "+url/mojo",
+]
diff --git a/components/url_rewrite/mojom/OWNERS b/components/url_rewrite/mojom/OWNERS
new file mode 100644
index 0000000..08850f4
--- /dev/null
+++ b/components/url_rewrite/mojom/OWNERS
@@ -0,0 +1,2 @@
+per-file *.mojom=set noparent
+per-file *.mojom=file://ipc/SECURITY_OWNERS
diff --git a/fuchsia/engine/url_request_rewrite.mojom b/components/url_rewrite/mojom/url_request_rewrite.mojom
similarity index 98%
rename from fuchsia/engine/url_request_rewrite.mojom
rename to components/url_rewrite/mojom/url_request_rewrite.mojom
index ba122090..967bd64b 100644
--- a/fuchsia/engine/url_request_rewrite.mojom
+++ b/components/url_rewrite/mojom/url_request_rewrite.mojom
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-module mojom;
+module url_rewrite.mojom;
 
 import "mojo/public/mojom/base/time.mojom";
 import "url/mojom/url.mojom";
diff --git a/components/url_rewrite/renderer/BUILD.gn b/components/url_rewrite/renderer/BUILD.gn
new file mode 100644
index 0000000..32f9cf7
--- /dev/null
+++ b/components/url_rewrite/renderer/BUILD.gn
@@ -0,0 +1,14 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+source_set("renderer") {
+  public = [ "url_request_rules_receiver.h" ]
+  sources = [ "url_request_rules_receiver.cc" ]
+  public_deps = [
+    "//base",
+    "//components/url_rewrite/common",
+    "//content/public/renderer",
+    "//mojo/public/cpp/bindings",
+  ]
+}
diff --git a/components/url_rewrite/renderer/DEPS b/components/url_rewrite/renderer/DEPS
new file mode 100644
index 0000000..936c1a8cb
--- /dev/null
+++ b/components/url_rewrite/renderer/DEPS
@@ -0,0 +1,5 @@
+include_rules = [
+  "+content/public/renderer",
+  "+mojo/public/cpp/bindings",
+  "+third_party/blink/public/common",
+]
diff --git a/fuchsia/engine/renderer/url_request_rules_receiver.cc b/components/url_rewrite/renderer/url_request_rules_receiver.cc
similarity index 85%
rename from fuchsia/engine/renderer/url_request_rules_receiver.cc
rename to components/url_rewrite/renderer/url_request_rules_receiver.cc
index 9317e7a..456de35e 100644
--- a/fuchsia/engine/renderer/url_request_rules_receiver.cc
+++ b/components/url_rewrite/renderer/url_request_rules_receiver.cc
@@ -2,14 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "fuchsia/engine/renderer/url_request_rules_receiver.h"
+#include "components/url_rewrite/renderer/url_request_rules_receiver.h"
 
 #include "content/public/renderer/render_frame.h"
-#include "fuchsia/engine/renderer/web_engine_content_renderer_client.h"
-#include "fuchsia/engine/url_request_rewrite.mojom.h"
-#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
 
+namespace url_rewrite {
+
 UrlRequestRulesReceiver::UrlRequestRulesReceiver(
     content::RenderFrame* render_frame) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -47,3 +46,5 @@
   cached_rules_ = base::MakeRefCounted<url_rewrite::UrlRequestRewriteRules>(
       std::move(rules));
 }
+
+}  // namespace url_rewrite
diff --git a/fuchsia/engine/renderer/url_request_rules_receiver.h b/components/url_rewrite/renderer/url_request_rules_receiver.h
similarity index 71%
rename from fuchsia/engine/renderer/url_request_rules_receiver.h
rename to components/url_rewrite/renderer/url_request_rules_receiver.h
index 14afc0ea..f6fa0b8 100644
--- a/fuchsia/engine/renderer/url_request_rules_receiver.h
+++ b/components/url_rewrite/renderer/url_request_rules_receiver.h
@@ -2,22 +2,23 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef FUCHSIA_ENGINE_RENDERER_URL_REQUEST_RULES_RECEIVER_H_
-#define FUCHSIA_ENGINE_RENDERER_URL_REQUEST_RULES_RECEIVER_H_
+#ifndef COMPONENTS_URL_REWRITE_RENDERER_URL_REQUEST_RULES_RECEIVER_H_
+#define COMPONENTS_URL_REWRITE_RENDERER_URL_REQUEST_RULES_RECEIVER_H_
 
 #include "base/sequence_checker.h"
-#include "fuchsia/engine/common/url_request_rewrite_rules.h"
+#include "components/url_rewrite/common/url_request_rewrite_rules.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
-#include "services/service_manager/public/cpp/binder_registry.h"
 
 namespace content {
 class RenderFrame;
 }  // namespace content
 
-// Provides rewriting rules for network requests. Owned by
-// WebEngineRenderFrameObserver, this object will be destroyed on RenderFrame
-// destruction. This class should only be used on the IO thread.
+namespace url_rewrite {
+
+// Provides rewriting rules for network requests. This object must be
+// destroyed on RenderFrame destruction. This class should only be used on the
+// IO thread.
 class UrlRequestRulesReceiver : public mojom::UrlRequestRulesReceiver {
  public:
   explicit UrlRequestRulesReceiver(content::RenderFrame* render_frame);
@@ -42,4 +43,6 @@
   SEQUENCE_CHECKER(sequence_checker_);
 };
 
-#endif  // FUCHSIA_ENGINE_RENDERER_URL_REQUEST_RULES_RECEIVER_H_
+}  // namespace url_rewrite
+
+#endif  // COMPONENTS_URL_REWRITE_RENDERER_URL_REQUEST_RULES_RECEIVER_H_
diff --git a/components/viz/common/BUILD.gn b/components/viz/common/BUILD.gn
index 2ce3503..45bc802 100644
--- a/components/viz/common/BUILD.gn
+++ b/components/viz/common/BUILD.gn
@@ -293,6 +293,8 @@
     "surfaces/surface_info.h",
     "surfaces/surface_range.cc",
     "surfaces/surface_range.h",
+    "surfaces/video_capture_target.cc",
+    "surfaces/video_capture_target.h",
     "switches.cc",
     "switches.h",
     "traced_value.cc",
diff --git a/components/viz/common/surfaces/video_capture_target.cc b/components/viz/common/surfaces/video_capture_target.cc
new file mode 100644
index 0000000..0c67db64
--- /dev/null
+++ b/components/viz/common/surfaces/video_capture_target.cc
@@ -0,0 +1,47 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/viz/common/surfaces/video_capture_target.h"
+
+#include "base/logging.h"
+
+namespace viz {
+
+namespace {
+bool SubTargetIsValid(const VideoCaptureSubTarget& sub_target) {
+  if (absl::holds_alternative<absl::monostate>(sub_target)) {
+    return true;
+  }
+  if (const auto* crop_id = absl::get_if<RegionCaptureCropId>(&sub_target)) {
+    return !crop_id->is_zero();
+  }
+  const auto* capture_id = absl::get_if<SubtreeCaptureId>(&sub_target);
+  DCHECK(capture_id);
+  return capture_id->is_valid();
+}
+
+}  // namespace
+
+VideoCaptureTarget::VideoCaptureTarget(FrameSinkId frame_sink_id)
+    : VideoCaptureTarget(frame_sink_id, VideoCaptureSubTarget()) {}
+VideoCaptureTarget::VideoCaptureTarget(FrameSinkId frame_sink_id,
+                                       VideoCaptureSubTarget sub_target)
+    : frame_sink_id(frame_sink_id),
+      sub_target(SubTargetIsValid(sub_target) ? sub_target
+                                              : VideoCaptureSubTarget{}) {
+  DCHECK(this->frame_sink_id.is_valid());
+}
+
+VideoCaptureTarget::VideoCaptureTarget() = default;
+VideoCaptureTarget::VideoCaptureTarget(const VideoCaptureTarget& other) =
+    default;
+VideoCaptureTarget::VideoCaptureTarget(VideoCaptureTarget&& other) = default;
+VideoCaptureTarget& VideoCaptureTarget::operator=(
+    const VideoCaptureTarget& other) = default;
+VideoCaptureTarget& VideoCaptureTarget::operator=(VideoCaptureTarget&& other) =
+    default;
+
+VideoCaptureTarget::~VideoCaptureTarget() = default;
+
+}  // namespace viz
diff --git a/components/viz/common/surfaces/video_capture_target.h b/components/viz/common/surfaces/video_capture_target.h
new file mode 100644
index 0000000..4a1e537
--- /dev/null
+++ b/components/viz/common/surfaces/video_capture_target.h
@@ -0,0 +1,65 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_VIZ_COMMON_SURFACES_VIDEO_CAPTURE_TARGET_H_
+#define COMPONENTS_VIZ_COMMON_SURFACES_VIDEO_CAPTURE_TARGET_H_
+
+#include "base/token.h"
+#include "components/viz/common/surfaces/frame_sink_id.h"
+#include "components/viz/common/surfaces/region_capture_bounds.h"
+#include "components/viz/common/surfaces/subtree_capture_id.h"
+#include "components/viz/common/viz_common_export.h"
+#include "third_party/abseil-cpp/absl/types/variant.h"
+
+namespace viz {
+
+// Note about sub targets: subtree-capture and region-capture are mutually
+// exclusive. This is trivially guaranteed by subtree-capture only being
+// supported on window-capture, and region-capture only being supported on
+// tab-capture.
+using VideoCaptureSubTarget =
+    absl::variant<absl::monostate, SubtreeCaptureId, RegionCaptureCropId>;
+
+// All of the information necessary to select a target for capture.
+// If constructed, the |frame_sink_id| must be valid and |sub_target|
+// is optional. If not provided, this target is the root render pass of
+// the frame sink. If |sub_target| is a SubtreeCaptureId, this target is
+// a layer subtree under the root render pass. Else, if |sub_target| is
+// a RegionCaptureCropId, this target is the root render pass but only
+// a subset of the pixels are selected for capture.
+struct VIZ_COMMON_EXPORT VideoCaptureTarget {
+  explicit VideoCaptureTarget(FrameSinkId frame_sink_id);
+
+  // If an invalid sub target is provided, it will be internally converted to an
+  // absl::monostate, equivalent to calling the single argument constructor
+  // above.
+  VideoCaptureTarget(FrameSinkId frame_sink_id,
+                     VideoCaptureSubTarget capture_sub_target);
+
+  VideoCaptureTarget();
+  ~VideoCaptureTarget();
+  VideoCaptureTarget(const VideoCaptureTarget& other);
+  VideoCaptureTarget(VideoCaptureTarget&& other);
+  VideoCaptureTarget& operator=(const VideoCaptureTarget& other);
+  VideoCaptureTarget& operator=(VideoCaptureTarget&& other);
+
+  inline bool operator==(const VideoCaptureTarget& other) const {
+    return frame_sink_id == other.frame_sink_id &&
+           sub_target == other.sub_target;
+  }
+
+  inline bool operator!=(const VideoCaptureTarget& other) const {
+    return !(*this == other);
+  }
+
+  // The target frame sink id. Must be valid.
+  FrameSinkId frame_sink_id;
+
+  // The sub target.
+  VideoCaptureSubTarget sub_target;
+};
+
+}  // namespace viz
+
+#endif  // COMPONENTS_VIZ_COMMON_SURFACES_VIDEO_CAPTURE_TARGET_H_
diff --git a/components/viz/host/client_frame_sink_video_capturer.cc b/components/viz/host/client_frame_sink_video_capturer.cc
index ac652ed..b2b21d9 100644
--- a/components/viz/host/client_frame_sink_video_capturer.cc
+++ b/components/viz/host/client_frame_sink_video_capturer.cc
@@ -72,15 +72,11 @@
 }
 
 void ClientFrameSinkVideoCapturer::ChangeTarget(
-    const absl::optional<FrameSinkId>& frame_sink_id,
-    mojom::SubTargetPtr sub_target) {
+    const absl::optional<VideoCaptureTarget>& target) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  target_ = frame_sink_id;
-  sub_target_ = std::move(sub_target);
-
-  capturer_remote_->ChangeTarget(frame_sink_id,
-                                 sub_target_ ? sub_target_.Clone() : nullptr);
+  target_ = target;
+  capturer_remote_->ChangeTarget(target);
 }
 
 void ClientFrameSinkVideoCapturer::Start(
@@ -196,8 +192,7 @@
   if (auto_throttling_enabled_)
     capturer_remote_->SetAutoThrottlingEnabled(*auto_throttling_enabled_);
   if (target_) {
-    capturer_remote_->ChangeTarget(
-        target_, sub_target_ ? sub_target_->Clone() : nullptr);
+    capturer_remote_->ChangeTarget(target_.value());
   }
   for (Overlay* overlay : overlays_)
     overlay->EstablishConnection(capturer_remote_.get());
diff --git a/components/viz/host/client_frame_sink_video_capturer.h b/components/viz/host/client_frame_sink_video_capturer.h
index 6d319eae..210e8784 100644
--- a/components/viz/host/client_frame_sink_video_capturer.h
+++ b/components/viz/host/client_frame_sink_video_capturer.h
@@ -13,8 +13,7 @@
 #include "base/sequence_checker.h"
 #include "base/time/time.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
-#include "components/viz/common/surfaces/region_capture_bounds.h"
-#include "components/viz/common/surfaces/subtree_capture_id.h"
+#include "components/viz/common/surfaces/video_capture_target.h"
 #include "components/viz/host/viz_host_export.h"
 #include "media/base/video_types.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -87,8 +86,7 @@
                                 const gfx::Size& max_size,
                                 bool use_fixed_aspect_ratio);
   void SetAutoThrottlingEnabled(bool enabled);
-  void ChangeTarget(const absl::optional<FrameSinkId>& frame_sink_id,
-                    mojom::SubTargetPtr sub_target);
+  void ChangeTarget(const absl::optional<VideoCaptureTarget>& target);
   void Stop();
   void RequestRefreshFrame();
 
@@ -155,8 +153,7 @@
   absl::optional<base::TimeDelta> min_size_change_period_;
   absl::optional<ResolutionConstraints> resolution_constraints_;
   absl::optional<bool> auto_throttling_enabled_;
-  absl::optional<FrameSinkId> target_;
-  mojom::SubTargetPtr sub_target_;
+  absl::optional<VideoCaptureTarget> target_;
   // Overlays are owned by the callers of CreateOverlay().
   std::vector<Overlay*> overlays_;
   bool is_started_ = false;
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn
index 972d8fc..f9d0c20 100644
--- a/components/viz/service/BUILD.gn
+++ b/components/viz/service/BUILD.gn
@@ -227,6 +227,7 @@
 
     # Note that dependency on //gpu/ipc/client is for GpuMemoryBufferImpl. This
     # dependency should not be in public_deps.
+    "//components/viz/common",
     "//gpu/ipc/client",
     "//gpu/ipc/common:common",
     "//gpu/ipc/common:surface_handle_type",
diff --git a/components/viz/service/display_embedder/output_presenter_fuchsia.cc b/components/viz/service/display_embedder/output_presenter_fuchsia.cc
index 24796ac0b..320cfb5 100644
--- a/components/viz/service/display_embedder/output_presenter_fuchsia.cc
+++ b/components/viz/service/display_embedder/output_presenter_fuchsia.cc
@@ -56,7 +56,7 @@
     return scoped_overlay_read_access_->GetNativePixmap();
   }
 
-  // Should be called after BeginPresent() to get fences for frame.
+  // Must be called after BeginPresent() to get fences for frame.
   void TakePresentationFences(
       std::vector<gfx::GpuFenceHandle>& read_begin_fences,
       std::vector<gfx::GpuFenceHandle>& read_end_fences);
@@ -104,12 +104,13 @@
 }
 
 void PresenterImageFuchsia::BeginPresent() {
+  DCHECK(read_end_fence_.is_null());
+
   ++present_count_;
 
   if (present_count_ == 1) {
     DCHECK(!scoped_overlay_read_access_);
     DCHECK(read_begin_fences_.empty());
-    DCHECK(read_end_fence_.is_null());
 
     scoped_overlay_read_access_ =
         overlay_representation_->BeginScopedReadAccess(
@@ -134,6 +135,8 @@
       exernal_semaphore_pool_->GetOrCreateSemaphore().GetVkSemaphore());
   DCHECK(handle.is_valid());
   read_end_fence_.owned_event = zx::event(handle.TakeHandle());
+
+  scoped_overlay_read_access_->SetReleaseFence(read_end_fence_.Clone());
 }
 
 void PresenterImageFuchsia::EndPresent(gfx::GpuFenceHandle release_fence) {
@@ -142,9 +145,6 @@
   --present_count_;
   if (!present_count_) {
     DCHECK(scoped_overlay_read_access_);
-    DCHECK(!read_end_fence_.is_null());
-
-    scoped_overlay_read_access_->SetReleaseFence(std::move(read_end_fence_));
     scoped_overlay_read_access_.reset();
   }
 }
@@ -166,7 +166,7 @@
 
   DCHECK(read_end_fences.empty());
   DCHECK(!read_end_fence_.is_null());
-  read_end_fences.push_back(read_end_fence_.Clone());
+  read_end_fences.push_back(std::move(read_end_fence_));
 }
 
 }  // namespace
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
index 04df254..71c33b3 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.cc
@@ -24,6 +24,7 @@
 #include "components/viz/common/quads/compositor_render_pass.h"
 #include "components/viz/common/resources/bitmap_allocation.h"
 #include "components/viz/common/surfaces/surface_info.h"
+#include "components/viz/common/surfaces/video_capture_target.h"
 #include "components/viz/service/display/display.h"
 #include "components/viz/service/display/shared_bitmap_manager.h"
 #include "components/viz/service/frame_sinks/frame_sink_bundle_impl.h"
@@ -944,7 +945,7 @@
 }
 
 gfx::Rect CompositorFrameSinkSupport::GetCopyOutputRequestRegion(
-    const CapturableFrameSink::RegionSpecifier& specifier) const {
+    const VideoCaptureSubTarget& sub_target) const {
   if (!last_activated_surface_id_.is_valid()) {
     return {};
   }
@@ -957,21 +958,21 @@
   }
 
   // We will either have a subtree ID or a region capture crop_id, but not both.
-  if (absl::holds_alternative<RegionCaptureCropId>(specifier)) {
-    return GetCaptureBounds(absl::get<RegionCaptureCropId>(specifier));
+  if (absl::holds_alternative<RegionCaptureCropId>(sub_target)) {
+    return GetCaptureBounds(absl::get<RegionCaptureCropId>(sub_target));
   }
 
   // We can exit early if there is no subtree, otherwise we need to
   // intersect the bounds.
   const CompositorFrame& frame = current_surface->GetActiveFrame();
-  if (!absl::holds_alternative<SubtreeCaptureId>(specifier)) {
+  if (!absl::holds_alternative<SubtreeCaptureId>(sub_target)) {
     return gfx::Rect(frame.size_in_pixels());
   }
 
   // Now we know we don't have a crop_id and we do have a subtree ID.
   for (const auto& render_pass : frame.render_pass_list) {
     if (render_pass->subtree_capture_id ==
-        absl::get<SubtreeCaptureId>(specifier)) {
+        absl::get<SubtreeCaptureId>(sub_target)) {
       return render_pass->subtree_size.IsEmpty()
                  ? render_pass->output_rect
                  : gfx::Rect(render_pass->subtree_size);
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support.h b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
index 5360d22..2c2f504 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support.h
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support.h
@@ -22,6 +22,7 @@
 #include "components/viz/common/surfaces/region_capture_bounds.h"
 #include "components/viz/common/surfaces/surface_info.h"
 #include "components/viz/common/surfaces/surface_range.h"
+#include "components/viz/common/surfaces/video_capture_target.h"
 #include "components/viz/service/frame_sinks/begin_frame_tracker.h"
 #include "components/viz/service/frame_sinks/surface_resource_holder.h"
 #include "components/viz/service/frame_sinks/surface_resource_holder_client.h"
@@ -199,7 +200,7 @@
   void AttachCaptureClient(CapturableFrameSink::Client* client) override;
   void DetachCaptureClient(CapturableFrameSink::Client* client) override;
   gfx::Rect GetCopyOutputRequestRegion(
-      const CapturableFrameSink::RegionSpecifier& specifier) const override;
+      const VideoCaptureSubTarget& specifier) const override;
   void OnClientCaptureStarted() override;
   void OnClientCaptureStopped() override;
   void RequestCopyOfOutput(
diff --git a/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc b/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc
index 09bbe6a..62ec925 100644
--- a/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc
+++ b/components/viz/service/frame_sinks/compositor_frame_sink_support_unittest.cc
@@ -1632,8 +1632,8 @@
 
 TEST_F(CompositorFrameSinkSupportTest, GetCopyOutputRequestRegion) {
   // No surface with active frame.
-  EXPECT_EQ((gfx::Rect{}), support_->GetCopyOutputRequestRegion(
-                               CapturableFrameSink::RegionSpecifier()));
+  EXPECT_EQ((gfx::Rect{}),
+            support_->GetCopyOutputRequestRegion(VideoCaptureSubTarget()));
 
   // Surface with active frame but no capture identifier.
   ResourceId first_frame_ids[] = {ResourceId(1), ResourceId(2), ResourceId(3),
@@ -1641,8 +1641,7 @@
   SubmitCompositorFrameWithResources(first_frame_ids,
                                      base::size(first_frame_ids));
   EXPECT_EQ((gfx::Rect{0, 0, 20, 20}),
-            (support_->GetCopyOutputRequestRegion(
-                CapturableFrameSink::RegionSpecifier())));
+            (support_->GetCopyOutputRequestRegion(VideoCaptureSubTarget())));
 
   // Render pass with subtree size.
   const SurfaceId surface_id(support_->frame_sink_id(), local_surface_id_);
diff --git a/components/viz/service/frame_sinks/frame_sink_bundle_impl_unittest.cc b/components/viz/service/frame_sinks/frame_sink_bundle_impl_unittest.cc
index db33b44..fad5c884 100644
--- a/components/viz/service/frame_sinks/frame_sink_bundle_impl_unittest.cc
+++ b/components/viz/service/frame_sinks/frame_sink_bundle_impl_unittest.cc
@@ -120,12 +120,12 @@
   TestFrameSink(
       FrameSinkManagerImpl& manager,
       const FrameSinkId& id,
-      const absl::optional<FrameSinkId>& parent_id,
+      const FrameSinkId& parent_id,
       const absl::optional<FrameSinkBundleId>& bundle_id = absl::nullopt)
       : manager_(manager), id_(id) {
     manager_.RegisterFrameSinkId(id, /*report_activation=*/true);
-    if (parent_id) {
-      manager_.RegisterFrameSinkHierarchy(*parent_id, id);
+    if (parent_id.is_valid()) {
+      manager_.RegisterFrameSinkHierarchy(parent_id, id);
     }
     manager_.CreateCompositorFrameSink(
         id, bundle_id, frame_sink.BindNewPipeAndPassReceiver(),
diff --git a/components/viz/service/frame_sinks/frame_sink_manager_impl.cc b/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
index 9860f8ae..6fa971d 100644
--- a/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
+++ b/components/viz/service/frame_sinks/frame_sink_manager_impl.cc
@@ -403,7 +403,8 @@
   support_map_[frame_sink_id] = support;
 
   for (auto& capturer : video_capturers_) {
-    if (capturer->requested_target() == frame_sink_id)
+    if (capturer->target() &&
+        capturer->target()->frame_sink_id == frame_sink_id)
       capturer->SetResolvedTarget(support);
   }
 
@@ -423,7 +424,8 @@
     observer.OnDestroyedCompositorFrameSink(frame_sink_id);
 
   for (auto& capturer : video_capturers_) {
-    if (capturer->requested_target() == frame_sink_id)
+    if (capturer->target() &&
+        capturer->target()->frame_sink_id == frame_sink_id)
       capturer->OnTargetWillGoAway();
   }
 
diff --git a/components/viz/service/frame_sinks/video_capture/capturable_frame_sink.h b/components/viz/service/frame_sinks/video_capture/capturable_frame_sink.h
index 8fedddbc..8808169 100644
--- a/components/viz/service/frame_sinks/video_capture/capturable_frame_sink.h
+++ b/components/viz/service/frame_sinks/video_capture/capturable_frame_sink.h
@@ -7,6 +7,7 @@
 
 #include "base/time/time.h"
 #include "components/viz/common/surfaces/region_capture_bounds.h"
+#include "components/viz/common/surfaces/video_capture_target.h"
 #include "components/viz/service/surfaces/pending_copy_output_request.h"
 #include "third_party/abseil-cpp/absl/types/variant.h"
 #include "ui/gfx/geometry/size.h"
@@ -64,10 +65,8 @@
   // its associated bounds are set to empty or could not be found.
   // NOTE: only one of |subtree_id| or |crop_id| should be set and valid, not
   // both.
-  using RegionSpecifier =
-      absl::variant<absl::monostate, SubtreeCaptureId, RegionCaptureCropId>;
   virtual gfx::Rect GetCopyOutputRequestRegion(
-      const RegionSpecifier& specifier) const = 0;
+      const VideoCaptureSubTarget& sub_target) const = 0;
 
   // Called when a video capture client starts or stops capturing.
   virtual void OnClientCaptureStarted() = 0;
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
index bf70c6f..d7ad719 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc
@@ -229,26 +229,16 @@
 }
 
 void FrameSinkVideoCapturerImpl::ChangeTarget(
-    const absl::optional<FrameSinkId>& frame_sink_id,
-    mojom::SubTargetPtr sub_target) {
+    const absl::optional<VideoCaptureTarget>& target) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  target_ = target;
 
-  if (frame_sink_id) {
-    requested_target_ = *frame_sink_id;
-    region_specifier_ =
-        !sub_target ? CapturableFrameSink::RegionSpecifier()
-                    : sub_target->is_subtree_capture_id()
-                          ? sub_target->get_subtree_capture_id()
-                          : sub_target->is_region_capture_crop_id()
-                                ? sub_target->get_region_capture_crop_id()
-                                : CapturableFrameSink::RegionSpecifier();
-    SetResolvedTarget(
-        frame_sink_manager_->FindCapturableFrameSink(requested_target_));
-  } else {
-    requested_target_ = FrameSinkId();
-    region_specifier_ = CapturableFrameSink::RegionSpecifier();
-    SetResolvedTarget(nullptr);
+  CapturableFrameSink* resolved_target = nullptr;
+  if (target_) {
+    resolved_target =
+        frame_sink_manager_->FindCapturableFrameSink(target_->frame_sink_id);
   }
+  SetResolvedTarget(resolved_target);
 }
 
 void FrameSinkVideoCapturerImpl::Start(
@@ -360,8 +350,9 @@
   }
 
   // Detect whether the source size changed before attempting capture.
+  DCHECK(target_);
   const gfx::Rect capture_region =
-      resolved_target_->GetCopyOutputRequestRegion(region_specifier_);
+      resolved_target_->GetCopyOutputRequestRegion(target_->sub_target);
   if (capture_region.IsEmpty()) {
     // If the capture region is empty, it means one of two things: the first
     // frame has not been composited yet or the current region selected for
@@ -397,15 +388,16 @@
   DCHECK(!damage_rect.IsEmpty());
   DCHECK(!expected_display_time.is_null());
   DCHECK(resolved_target_);
+  DCHECK(target_);
 
   const gfx::Rect capture_region =
-      resolved_target_->GetCopyOutputRequestRegion(region_specifier_);
+      resolved_target_->GetCopyOutputRequestRegion(target_->sub_target);
   if (capture_region.IsEmpty()) {
     return;
   }
 
   if (capture_region.size() == oracle_->source_size()) {
-    if (!absl::holds_alternative<absl::monostate>(region_specifier_)) {
+    if (!absl::holds_alternative<absl::monostate>(target_->sub_target)) {
       // The damage_rect may not be in the same coordinate space when we have
       // a valid request subtree identifier, so to be safe we just invalidate
       // the entire source.
@@ -485,6 +477,7 @@
     const CompositorFrameMetadata& frame_metadata) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(resolved_target_);
+  DCHECK(target_);
 
   // Consult the oracle to determine whether this frame should be captured.
   if (oracle_->ObserveEventAndDecideCapture(event, damage_rect, event_time)) {
@@ -678,7 +671,7 @@
   // size of the capture region. If the capture region is empty, we shouldn't
   // capture.
   const gfx::Rect capture_region =
-      resolved_target_->GetCopyOutputRequestRegion(region_specifier_);
+      resolved_target_->GetCopyOutputRequestRegion(target_->sub_target);
   if (capture_region.IsEmpty()) {
     return;
   }
@@ -724,8 +717,8 @@
   }
 
   const SubtreeCaptureId subtree_id =
-      absl::holds_alternative<SubtreeCaptureId>(region_specifier_)
-          ? absl::get<SubtreeCaptureId>(region_specifier_)
+      absl::holds_alternative<SubtreeCaptureId>(target_->sub_target)
+          ? absl::get<SubtreeCaptureId>(target_->sub_target)
           : SubtreeCaptureId();
 
   resolved_target_->RequestCopyOfOutput(
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h
index 591cdf8..990ace8 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.h
@@ -21,7 +21,7 @@
 #include "base/unguessable_token.h"
 #include "components/viz/common/quads/compositor_frame_metadata.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
-#include "components/viz/common/surfaces/subtree_capture_id.h"
+#include "components/viz/common/surfaces/video_capture_target.h"
 #include "components/viz/service/frame_sinks/video_capture/capturable_frame_sink.h"
 #include "components/viz/service/frame_sinks/video_capture/in_flight_frame_delivery.h"
 #include "components/viz/service/frame_sinks/video_capture/shared_memory_video_frame_pool.h"
@@ -92,7 +92,7 @@
   // The currently-requested frame sink for capture. The frame sink manager
   // calls this when it learns of a new CapturableFrameSink to see if the target
   // can be resolved.
-  const FrameSinkId& requested_target() const { return requested_target_; }
+  const absl::optional<VideoCaptureTarget>& target() const { return target_; }
 
   // Sets the resolved target, detaching this capturer from the previous target
   // (if any), and attaching to the new target. This is called by the frame sink
@@ -115,8 +115,7 @@
                                 const gfx::Size& max_size,
                                 bool use_fixed_aspect_ratio) final;
   void SetAutoThrottlingEnabled(bool enabled) final;
-  void ChangeTarget(const absl::optional<FrameSinkId>& frame_sink_id,
-                    mojom::SubTargetPtr sub_target) final;
+  void ChangeTarget(const absl::optional<VideoCaptureTarget>& target) final;
   void Start(mojo::PendingRemote<mojom::FrameSinkVideoConsumer> consumer) final;
   void Stop() final;
   void RequestRefreshFrame() final;
@@ -261,13 +260,8 @@
   const std::unique_ptr<media::VideoCaptureOracle> oracle_;
 
   // The target requested by the client, as provided in the last call to
-  // ChangeTarget().
-  FrameSinkId requested_target_;
-
-  // A specifier that indicates what region of the layer should be captured.
-  // If not valid, then the root render pass of the target frame sink should
-  // be captured.
-  CapturableFrameSink::RegionSpecifier region_specifier_;
+  // ChangeTarget(). May be nullopt if no target is currently set.
+  absl::optional<VideoCaptureTarget> target_;
 
   // The resolved target of video capture, or null if the requested target does
   // not yet exist (or no longer exists).
diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
index 09499a3..de8ca7c 100644
--- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
+++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl_unittest.cc
@@ -20,6 +20,7 @@
 #include "components/viz/common/frame_sinks/copy_output_result.h"
 #include "components/viz/common/frame_sinks/copy_output_util.h"
 #include "components/viz/common/surfaces/subtree_capture_id.h"
+#include "components/viz/common/surfaces/video_capture_target.h"
 #include "components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_manager.h"
 #include "media/base/limits.h"
 #include "media/base/video_util.h"
@@ -258,17 +259,17 @@
   }
 
   gfx::Rect GetCopyOutputRequestRegion(
-      const CapturableFrameSink::RegionSpecifier& specifier) const override {
-    if (absl::holds_alternative<RegionCaptureCropId>(specifier)) {
+      const VideoCaptureSubTarget& sub_target) const override {
+    if (absl::holds_alternative<RegionCaptureCropId>(sub_target)) {
       current_capture_id_ = SubtreeCaptureId();
-      current_crop_id_ = absl::get<RegionCaptureCropId>(specifier);
+      current_crop_id_ = absl::get<RegionCaptureCropId>(sub_target);
       if (!current_crop_id_.is_zero()) {
         return crop_bounds_;
       }
       return {};
     }
-    if (absl::holds_alternative<SubtreeCaptureId>(specifier)) {
-      current_capture_id_ = absl::get<SubtreeCaptureId>(specifier);
+    if (absl::holds_alternative<SubtreeCaptureId>(sub_target)) {
+      current_capture_id_ = absl::get<SubtreeCaptureId>(sub_target);
       current_crop_id_ = RegionCaptureCropId();
       if (current_capture_id_.is_valid()) {
         return capture_bounds_;
@@ -582,9 +583,9 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  EXPECT_EQ(FrameSinkId(), capturer_->requested_target());
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
-  EXPECT_EQ(kFrameSinkId, capturer_->requested_target());
+  EXPECT_FALSE(capturer_->target());
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
+  EXPECT_EQ(kFrameSinkId, capturer_->target()->frame_sink_id);
   EXPECT_EQ(capturer_.get(), frame_sink_.attached_client());
 }
 
@@ -594,9 +595,9 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(nullptr));
 
-  EXPECT_EQ(FrameSinkId(), capturer_->requested_target());
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
-  EXPECT_EQ(kFrameSinkId, capturer_->requested_target());
+  EXPECT_FALSE(capturer_->target());
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
+  EXPECT_EQ(kFrameSinkId, capturer_->target()->frame_sink_id);
   EXPECT_EQ(nullptr, frame_sink_.attached_client());
 
   capturer_->SetResolvedTarget(&frame_sink_);
@@ -630,7 +631,7 @@
 
   // Now, set the target. As it resolves, the capturer will immediately attempt
   // a refresh capture, which will cancel the timer and trigger a copy request.
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
   EXPECT_EQ(1, frame_sink_.num_copy_results());
   EXPECT_FALSE(IsRefreshRetryTimerRunning());
 
@@ -646,7 +647,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
   EXPECT_FALSE(IsRefreshRetryTimerRunning());
 
   MockConsumer consumer;
@@ -741,7 +742,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
 
   NiceMock<MockConsumer> consumer;
   StartCapture(&consumer);
@@ -839,7 +840,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
 
   NiceMock<MockConsumer> consumer;
   StartCapture(&consumer);
@@ -896,7 +897,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
 
   // Start capturing to the first consumer.
   MockConsumer consumer;
@@ -983,7 +984,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
 
   MockConsumer consumer;
   const int num_refresh_frames = 2;  // Initial, plus later refresh.
@@ -1039,7 +1040,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
 
   MockConsumer consumer;
   constexpr int num_refresh_frames = 3;  // Initial, plus two refreshes after
@@ -1136,7 +1137,7 @@
 TEST_F(FrameSinkVideoCapturerTest, CompositorFrameMetadataReachesConsumer) {
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
 
   MockConsumer consumer;
   // Initial refresh frame for starting capture, plus later refresh.
@@ -1196,7 +1197,7 @@
 TEST_F(FrameSinkVideoCapturerTest, DeliversUpdateRectAndCaptureCounter) {
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
 
   MockConsumer consumer;
   StartCapture(&consumer);
@@ -1311,7 +1312,7 @@
 TEST_F(FrameSinkVideoCapturerTest, CaptureCounterSkipsWhenFramesAreDropped) {
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
 
   MockConsumer consumer;
   StartCapture(&consumer);
@@ -1363,7 +1364,7 @@
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
 
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
   EXPECT_EQ(frame_sink_.number_clients_capturing(), 0);
 
   // Start capturing. frame_sink_ should now have one client capturing.
@@ -1380,14 +1381,13 @@
   SwitchToSizeSet(kSizeSets[4]);
   EXPECT_CALL(frame_sink_manager_, FindCapturableFrameSink(kFrameSinkId))
       .WillRepeatedly(Return(&frame_sink_));
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
   EXPECT_EQ(frame_sink_.number_clients_capturing(), 0);
 
   static const auto kCropId = RegionCaptureCropId::CreateRandom();
   static const gfx::Rect kCropBounds{1, 2, 640, 478};
   frame_sink_.set_crop_bounds(kCropBounds);
-  capturer_->ChangeTarget(kFrameSinkId,
-                          mojom::SubTarget::NewRegionCaptureCropId(kCropId));
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId, kCropId));
 
   // Start capturing. frame_sink_ should now have one client capturing.
   NiceMock<MockConsumer> consumer;
@@ -1406,8 +1406,7 @@
   static const gfx::Rect kCaptureBounds{1, 2, 1024, 768};
   static const SubtreeCaptureId kCaptureId{1234567u};
   frame_sink_.set_capture_bounds(kCaptureBounds);
-  capturer_->ChangeTarget(kFrameSinkId,
-                          mojom::SubTarget::NewSubtreeCaptureId(kCaptureId));
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId, kCaptureId));
 
   // Start capturing. frame_sink_ should now have one client capturing.
   NiceMock<MockConsumer> consumer;
@@ -1427,7 +1426,7 @@
   // The default cause is a target with no sub target, passed as nullptr. Since
   // the SubTarget is a mojom variant, the default SubTarget::New() is actually
   // a zero value subtree capture identifier.
-  capturer_->ChangeTarget(kFrameSinkId, nullptr);
+  capturer_->ChangeTarget(VideoCaptureTarget(kFrameSinkId));
   EXPECT_EQ(frame_sink_.number_clients_capturing(), 0);
 
   // Start capturing. frame_sink_ should now have one client capturing.
diff --git a/components/web_package/web_bundle_builder_unittest.cc b/components/web_package/web_bundle_builder_unittest.cc
index e786fcc..034b361 100644
--- a/components/web_package/web_bundle_builder_unittest.cc
+++ b/components/web_package/web_bundle_builder_unittest.cc
@@ -47,7 +47,7 @@
                       {{":status", "200"}, {"content-type", "text/plain"}},
                       "payload");
   std::vector<uint8_t> bundle = builder.CreateBundle();
-  char written_size[8];
+  uint8_t written_size[8];
   memcpy(written_size, bundle.data() + bundle.size() - 8, 8);
   uint64_t written_size_int;
   base::ReadBigEndian(written_size, &written_size_int);
diff --git a/components/web_package/web_bundle_parser.cc b/components/web_package/web_bundle_parser.cc
index 8d7bfd7..38e4bae 100644
--- a/components/web_package/web_bundle_parser.cc
+++ b/components/web_package/web_bundle_parser.cc
@@ -197,7 +197,7 @@
     auto bytes = ReadBytes(sizeof(T));
     if (!bytes)
       return false;
-    base::ReadBigEndian(reinterpret_cast<const char*>(bytes->data()), out);
+    base::ReadBigEndian(bytes->data(), out);
     return true;
   }
 
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index a2ace07..bd2aea4 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -259,6 +259,7 @@
     "//ui/resources",
     "//ui/shell_dialogs",
     "//ui/snapshot",
+    "//ui/strings:ax_strings",
     "//ui/touch_selection",
     "//v8:v8_version",
   ]
diff --git a/content/browser/DEPS b/content/browser/DEPS
index d18c993..d21dc48e 100644
--- a/content/browser/DEPS
+++ b/content/browser/DEPS
@@ -60,6 +60,7 @@
   "+sql",
   "+ui/aura_extra",
   "+components/vector_icons",
+  "+ui/strings/grit/ax_strings.h",
   "+ui/webui",
 
   # TODO(crbug.com/1079201): Consider removing "+serivces". Each service should
diff --git a/content/browser/accessibility/accessibility_tools_utils_mac.mm b/content/browser/accessibility/accessibility_tools_utils_mac.mm
index b6eb9df..d325f5a 100644
--- a/content/browser/accessibility/accessibility_tools_utils_mac.mm
+++ b/content/browser/accessibility/accessibility_tools_utils_mac.mm
@@ -56,7 +56,8 @@
        NSAccessibilityMathUnderAttribute,
        NSAccessibilityMathOverAttribute,
        NSAccessibilityMathPostscriptsAttribute,
-       NSAccessibilityMathPrescriptsAttribute},
+       NSAccessibilityMathPrescriptsAttribute,
+       NSAccessibilityRoleDescriptionAttribute},
       NSStringComparator());
 
   return kValidAttributes.contains(base::SysUTF8ToNSString(attribute));
diff --git a/content/browser/accessibility/browser_accessibility.cc b/content/browser/accessibility/browser_accessibility.cc
index db9b06e..a78b3f1 100644
--- a/content/browser/accessibility/browser_accessibility.cc
+++ b/content/browser/accessibility/browser_accessibility.cc
@@ -27,7 +27,7 @@
 #include "ui/base/buildflags.h"
 #include "ui/gfx/geometry/rect_conversions.h"
 #include "ui/gfx/geometry/rect_f.h"
-#include "ui/strings/grit/ui_strings.h"
+#include "ui/strings/grit/ax_strings.h"
 
 namespace content {
 
diff --git a/content/browser/accessibility/browser_accessibility_android.cc b/content/browser/accessibility/browser_accessibility_android.cc
index 3884ecd..60ca848 100644
--- a/content/browser/accessibility/browser_accessibility_android.cc
+++ b/content/browser/accessibility/browser_accessibility_android.cc
@@ -23,7 +23,7 @@
 #include "ui/accessibility/ax_role_properties.h"
 #include "ui/accessibility/platform/ax_android_constants.h"
 #include "ui/accessibility/platform/ax_unique_id.h"
-#include "ui/strings/grit/ui_strings.h"
+#include "ui/strings/grit/ax_strings.h"
 
 namespace {
 
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.h b/content/browser/accessibility/browser_accessibility_cocoa.h
index 06a6d28..17b3f0b 100644
--- a/content/browser/accessibility/browser_accessibility_cocoa.h
+++ b/content/browser/accessibility/browser_accessibility_cocoa.h
@@ -176,7 +176,6 @@
 // A string indicating the role of this object as far as accessibility
 // is concerned.
 @property(nonatomic, readonly) NSString* role;
-@property(nonatomic, readonly) NSString* roleDescription;
 @property(nonatomic, readonly) NSArray* rowHeaders;
 @property(nonatomic, readonly) NSValue* rowIndexRange;
 @property(nonatomic, readonly) NSArray* rows;
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm
index 5f00a94d..d6e678a 100644
--- a/content/browser/accessibility/browser_accessibility_cocoa.mm
+++ b/content/browser/accessibility/browser_accessibility_cocoa.mm
@@ -37,7 +37,7 @@
 #include "ui/accessibility/ax_role_properties.h"
 #include "ui/accessibility/platform/ax_platform_node.h"
 #include "ui/gfx/mac/coordinate_conversion.h"
-#include "ui/strings/grit/ui_strings.h"
+#include "ui/strings/grit/ax_strings.h"
 
 #import "ui/accessibility/platform/ax_platform_node_mac.h"
 
@@ -843,7 +843,6 @@
       {NSAccessibilityPositionAttribute, @"position"},
       {NSAccessibilityRequiredAttributeChrome, @"required"},
       {NSAccessibilityRoleAttribute, @"role"},
-      {NSAccessibilityRoleDescriptionAttribute, @"roleDescription"},
       {NSAccessibilityRowHeaderUIElementsAttribute, @"rowHeaders"},
       {NSAccessibilityRowIndexRangeAttribute, @"rowIndexRange"},
       {NSAccessibilityRowsAttribute, @"rows"},
@@ -1885,155 +1884,6 @@
   return cocoa_role;
 }
 
-- (NSString*)AXRoleDescription {
-  return [self roleDescription];
-}
-
-// Returns a string indicating the role description of this object.
-- (NSString*)roleDescription {
-  if (![self instanceActive])
-    return nil;
-
-  if (_owner->GetData().GetImageAnnotationStatus() ==
-          ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation ||
-      _owner->GetData().GetImageAnnotationStatus() ==
-          ax::mojom::ImageAnnotationStatus::kSilentlyEligibleForAnnotation) {
-    return base::SysUTF16ToNSString(
-        _owner->GetLocalizedRoleDescriptionForUnlabeledImage());
-  }
-
-  if (_owner->HasStringAttribute(
-          ax::mojom::StringAttribute::kRoleDescription)) {
-    return NSStringForStringAttribute(
-        _owner, ax::mojom::StringAttribute::kRoleDescription);
-  }
-
-  NSString* role = [self role];
-  ContentClient* content_client = content::GetContentClient();
-
-  // The following descriptions are specific to webkit.
-  if ([role isEqualToString:@"AXWebArea"]) {
-    return base::SysUTF16ToNSString(
-        content_client->GetLocalizedString(IDS_AX_ROLE_WEB_AREA));
-  }
-
-  if ([role isEqualToString:@"NSAccessibilityLinkRole"]) {
-    return base::SysUTF16ToNSString(
-        content_client->GetLocalizedString(IDS_AX_ROLE_LINK));
-  }
-
-  if ([role isEqualToString:@"AXHeading"]) {
-    return base::SysUTF16ToNSString(
-        content_client->GetLocalizedString(IDS_AX_ROLE_HEADING));
-  }
-
-  if (([role isEqualToString:NSAccessibilityGroupRole] ||
-       [role isEqualToString:NSAccessibilityRadioButtonRole]) &&
-      !_owner->IsWebAreaForPresentationalIframe()) {
-    std::string role_attribute;
-    if (_owner->GetHtmlAttribute("role", &role_attribute)) {
-      ax::mojom::Role internalRole = [self internalRole];
-      if ((internalRole != ax::mojom::Role::kBlockquote &&
-           internalRole != ax::mojom::Role::kCaption &&
-           internalRole != ax::mojom::Role::kGroup &&
-           internalRole != ax::mojom::Role::kListItem &&
-           internalRole != ax::mojom::Role::kMark &&
-           internalRole != ax::mojom::Role::kParagraph) ||
-          internalRole == ax::mojom::Role::kTab) {
-        // TODO(dtseng): This is not localized; see crbug/84814.
-        return base::SysUTF8ToNSString(role_attribute);
-      }
-    }
-  }
-
-  switch ([self internalRole]) {
-    case ax::mojom::Role::kArticle:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_ARTICLE));
-    case ax::mojom::Role::kBanner:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_BANNER));
-    case ax::mojom::Role::kCheckBox:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_CHECK_BOX));
-    case ax::mojom::Role::kComment:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_COMMENT));
-    case ax::mojom::Role::kComplementary:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_COMPLEMENTARY));
-    case ax::mojom::Role::kContentInfo:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_CONTENT_INFO));
-    case ax::mojom::Role::kDescriptionList:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_DESCRIPTION_LIST));
-    case ax::mojom::Role::kDescriptionListDetail:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_DEFINITION));
-    case ax::mojom::Role::kDescriptionListTerm:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_DESCRIPTION_TERM));
-    case ax::mojom::Role::kDisclosureTriangle:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_DISCLOSURE_TRIANGLE));
-    case ax::mojom::Role::kFigure:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_FIGURE));
-    case ax::mojom::Role::kFooter:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_FOOTER));
-    case ax::mojom::Role::kForm:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_FORM));
-    case ax::mojom::Role::kHeader:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_BANNER));
-    case ax::mojom::Role::kMain:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_MAIN_CONTENT));
-    case ax::mojom::Role::kMark:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_MARK));
-    case ax::mojom::Role::kMath:
-    case ax::mojom::Role::kMathMLMath:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_MATH));
-    case ax::mojom::Role::kNavigation:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_NAVIGATIONAL_LINK));
-    case ax::mojom::Role::kRegion:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_REGION));
-    case ax::mojom::Role::kSpinButton:
-      // This control is similar to what VoiceOver calls a "stepper".
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_STEPPER));
-    case ax::mojom::Role::kStatus:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_STATUS));
-    case ax::mojom::Role::kSearchBox:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_SEARCH_BOX));
-    case ax::mojom::Role::kSuggestion:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_SUGGESTION));
-    case ax::mojom::Role::kSwitch:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_SWITCH));
-    case ax::mojom::Role::kTerm:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_DESCRIPTION_TERM));
-    case ax::mojom::Role::kToggleButton:
-      return base::SysUTF16ToNSString(
-          content_client->GetLocalizedString(IDS_AX_ROLE_TOGGLE_BUTTON));
-    default:
-      break;
-  }
-
-  return NSAccessibilityRoleDescription(role, nil);
-}
-
 - (NSArray*)rowHeaders {
   if (![self instanceActive])
     return nil;
diff --git a/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm b/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm
index 720dad4..d60a9f2 100644
--- a/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm
+++ b/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm
@@ -268,43 +268,6 @@
 }
 
 IN_PROC_BROWSER_TEST_F(BrowserAccessibilityCocoaBrowserTest,
-                       TestUnlabeledImageRoleDescription) {
-  ui::AXTreeUpdate tree;
-  tree.root_id = 1;
-  tree.nodes.resize(3);
-  tree.nodes[0].id = 1;
-  tree.nodes[0].child_ids = {2, 3};
-
-  tree.nodes[1].id = 2;
-  tree.nodes[1].role = ax::mojom::Role::kImage;
-  tree.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kRoleDescription,
-                                   "foo");
-  tree.nodes[1].SetImageAnnotationStatus(
-      ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation);
-
-  tree.nodes[2].id = 3;
-  tree.nodes[2].role = ax::mojom::Role::kImage;
-  tree.nodes[2].AddStringAttribute(ax::mojom::StringAttribute::kRoleDescription,
-                                   "bar");
-  tree.nodes[2].SetImageAnnotationStatus(
-      ax::mojom::ImageAnnotationStatus::kSilentlyEligibleForAnnotation);
-
-  std::unique_ptr<BrowserAccessibilityManagerMac> manager(
-      new BrowserAccessibilityManagerMac(tree, nullptr));
-
-  for (int child_index = 0;
-       child_index < static_cast<int>(tree.nodes[0].child_ids.size());
-       ++child_index) {
-    BrowserAccessibility* child =
-        manager->GetRoot()->PlatformGetChild(child_index);
-    base::scoped_nsobject<BrowserAccessibilityCocoa> child_obj(
-        [ToBrowserAccessibilityCocoa(child) retain]);
-
-    EXPECT_NSEQ(@"Unlabeled image", [child_obj roleDescription]);
-  }
-}
-
-IN_PROC_BROWSER_TEST_F(BrowserAccessibilityCocoaBrowserTest,
                        TestAnnotatedImageDescription) {
   std::vector<const char*> expected_descriptions;
 
diff --git a/content/browser/accessibility/dump_accessibility_scripts_browsertest.cc b/content/browser/accessibility/dump_accessibility_scripts_browsertest.cc
index 507ff77a..94c413f0 100644
--- a/content/browser/accessibility/dump_accessibility_scripts_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_scripts_browsertest.cc
@@ -278,6 +278,11 @@
   RunTypedTest<kMacMethods>("accessibility-placeholder-value.html");
 }
 
+IN_PROC_BROWSER_TEST_P(DumpAccessibilityScriptTest,
+                       AccessibilityRoleDescription) {
+  RunTypedTest<kMacMethods>("accessibility-role-description.html");
+}
+
 IN_PROC_BROWSER_TEST_P(DumpAccessibilityScriptTest, AccessibilityTitle) {
   RunTypedTest<kMacMethods>("accessibility-title.html");
 }
diff --git a/content/browser/aggregation_service/aggregation_service.h b/content/browser/aggregation_service/aggregation_service.h
index bd5116f7..76f818f 100644
--- a/content/browser/aggregation_service/aggregation_service.h
+++ b/content/browser/aggregation_service/aggregation_service.h
@@ -7,7 +7,6 @@
 
 #include "base/callback_forward.h"
 #include "content/browser/aggregation_service/aggregatable_report_assembler.h"
-#include "content/common/content_export.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace content {
@@ -17,7 +16,7 @@
 class BrowserContext;
 
 // External interface for the aggregation service.
-class CONTENT_EXPORT AggregationService {
+class AggregationService {
  public:
   using AssemblyStatus = AggregatableReportAssembler::AssemblyStatus;
   using AssemblyCallback = AggregatableReportAssembler::AssemblyCallback;
@@ -38,4 +37,4 @@
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_AGGREGATION_SERVICE_AGGREGATION_SERVICE_H_
\ No newline at end of file
+#endif  // CONTENT_BROWSER_AGGREGATION_SERVICE_AGGREGATION_SERVICE_H_
diff --git a/content/browser/aggregation_service/aggregation_service_key_storage.h b/content/browser/aggregation_service/aggregation_service_key_storage.h
index bdaa5df..d9a5244f 100644
--- a/content/browser/aggregation_service/aggregation_service_key_storage.h
+++ b/content/browser/aggregation_service/aggregation_service_key_storage.h
@@ -7,8 +7,6 @@
 
 #include <vector>
 
-#include "content/common/content_export.h"
-
 namespace base {
 class Time;
 }  // namespace base
@@ -24,7 +22,7 @@
 
 // This class provides an interface for persisting helper server public keys
 // and performing queries on it.
-class CONTENT_EXPORT AggregationServiceKeyStorage {
+class AggregationServiceKeyStorage {
  public:
   virtual ~AggregationServiceKeyStorage() = default;
 
diff --git a/content/browser/aggregation_service/aggregation_service_storage_context.h b/content/browser/aggregation_service/aggregation_service_storage_context.h
index a369d90..980e116 100644
--- a/content/browser/aggregation_service/aggregation_service_storage_context.h
+++ b/content/browser/aggregation_service/aggregation_service_storage_context.h
@@ -6,14 +6,13 @@
 #define CONTENT_BROWSER_AGGREGATION_SERVICE_AGGREGATION_SERVICE_STORAGE_CONTEXT_H_
 
 #include "base/threading/sequence_bound.h"
-#include "content/common/content_export.h"
 
 namespace content {
 
 class AggregationServiceKeyStorage;
 
 // Internal interface that provides access to the storage.
-class CONTENT_EXPORT AggregationServiceStorageContext {
+class AggregationServiceStorageContext {
  public:
   virtual ~AggregationServiceStorageContext() = default;
 
@@ -24,4 +23,4 @@
 
 }  // namespace content
 
-#endif  // CONTENT_BROWSER_AGGREGATION_SERVICE_AGGREGATION_SERVICE_STORAGE_CONTEXT_H_
\ No newline at end of file
+#endif  // CONTENT_BROWSER_AGGREGATION_SERVICE_AGGREGATION_SERVICE_STORAGE_CONTEXT_H_
diff --git a/content/browser/attribution_reporting/attribution_manager.h b/content/browser/attribution_reporting/attribution_manager.h
index a2c7794..9657779 100644
--- a/content/browser/attribution_reporting/attribution_manager.h
+++ b/content/browser/attribution_reporting/attribution_manager.h
@@ -13,7 +13,6 @@
 #include "content/browser/attribution_reporting/attribution_report.h"
 #include "content/browser/attribution_reporting/attribution_storage.h"
 #include "content/browser/attribution_reporting/sent_report_info.h"
-#include "content/common/content_export.h"
 
 namespace base {
 class Time;
@@ -32,7 +31,7 @@
 
 // Interface that mediates data flow between the network, storage layer, and
 // blink.
-class CONTENT_EXPORT AttributionManager {
+class AttributionManager {
  public:
   // Provides access to a AttributionManager implementation. This layer of
   // abstraction is to allow tests to mock out the AttributionManager without
diff --git a/content/browser/background_fetch/background_fetch_registration_service_impl.h b/content/browser/background_fetch/background_fetch_registration_service_impl.h
index 45d481a..9e8d692 100644
--- a/content/browser/background_fetch/background_fetch_registration_service_impl.h
+++ b/content/browser/background_fetch/background_fetch_registration_service_impl.h
@@ -8,12 +8,11 @@
 #include "base/memory/weak_ptr.h"
 #include "content/browser/background_fetch/background_fetch_context.h"
 #include "content/browser/background_fetch/background_fetch_registration_id.h"
-#include "content/common/content_export.h"
 #include "third_party/blink/public/mojom/background_fetch/background_fetch.mojom.h"
 
 namespace content {
 
-class CONTENT_EXPORT BackgroundFetchRegistrationServiceImpl
+class BackgroundFetchRegistrationServiceImpl
     : public blink::mojom::BackgroundFetchRegistrationService {
  public:
   static mojo::PendingRemote<blink::mojom::BackgroundFetchRegistrationService>
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h
index 7aab655..932a428 100644
--- a/content/browser/bad_message.h
+++ b/content/browser/bad_message.h
@@ -6,7 +6,6 @@
 #define CONTENT_BROWSER_BAD_MESSAGE_H_
 
 #include "base/debug/crash_logging.h"
-#include "content/common/content_export.h"
 
 namespace content {
 class BrowserMessageFilter;
@@ -295,8 +294,7 @@
 void ReceivedBadMessage(RenderProcessHost* host, BadMessageReason reason);
 
 // Equivalent to the above, but callable from any thread.
-CONTENT_EXPORT void ReceivedBadMessage(int render_process_id,
-                                       BadMessageReason reason);
+void ReceivedBadMessage(int render_process_id, BadMessageReason reason);
 
 // Called when a browser message filter receives a bad IPC message from a
 // renderer or other child process. Logs the event, records a histogram metric
diff --git a/content/browser/bluetooth/bluetooth_device_scanning_prompt_controller.h b/content/browser/bluetooth/bluetooth_device_scanning_prompt_controller.h
index 4b402f49..25c5be8 100644
--- a/content/browser/bluetooth/bluetooth_device_scanning_prompt_controller.h
+++ b/content/browser/bluetooth/bluetooth_device_scanning_prompt_controller.h
@@ -9,7 +9,6 @@
 #include <string>
 
 #include "base/memory/weak_ptr.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/bluetooth_scanning_prompt.h"
 
 namespace content {
@@ -18,7 +17,7 @@
 class WebBluetoothServiceImpl;
 
 // Class that interacts with a prompt.
-class CONTENT_EXPORT BluetoothDeviceScanningPromptController final {
+class BluetoothDeviceScanningPromptController final {
  public:
   // |web_bluetooth_service_| service that owns this class.
   // |render_frame_host| should be the RenderFrameHost that owns the
diff --git a/content/browser/broadcast_channel/broadcast_channel_provider.h b/content/browser/broadcast_channel/broadcast_channel_provider.h
index a013db75..e7a6024 100644
--- a/content/browser/broadcast_channel/broadcast_channel_provider.h
+++ b/content/browser/broadcast_channel/broadcast_channel_provider.h
@@ -8,7 +8,6 @@
 #include <map>
 
 #include "base/memory/weak_ptr.h"
-#include "content/common/content_export.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "third_party/blink/public/mojom/broadcastchannel/broadcast_channel.mojom.h"
 
@@ -16,8 +15,7 @@
 
 class BroadcastChannelService;
 
-class CONTENT_EXPORT BroadcastChannelProvider
-    : public blink::mojom::BroadcastChannelProvider {
+class BroadcastChannelProvider : public blink::mojom::BroadcastChannelProvider {
  public:
   BroadcastChannelProvider(BroadcastChannelService* broadcast_channel_service,
                            const blink::StorageKey& storage_key);
diff --git a/content/browser/broadcast_channel/broadcast_channel_service.h b/content/browser/broadcast_channel/broadcast_channel_service.h
index 8306ace..e96fe4a3 100644
--- a/content/browser/broadcast_channel/broadcast_channel_service.h
+++ b/content/browser/broadcast_channel/broadcast_channel_service.h
@@ -7,7 +7,6 @@
 
 #include <map>
 #include "content/browser/broadcast_channel/broadcast_channel_provider.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/unique_associated_receiver_set.h"
 #include "mojo/public/cpp/bindings/unique_receiver_set.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
@@ -16,7 +15,7 @@
 
 namespace content {
 
-class CONTENT_EXPORT BroadcastChannelService {
+class BroadcastChannelService {
  public:
   BroadcastChannelService();
   // Not copyable or moveable, since this will be a singleton owned by
diff --git a/content/browser/browser_child_process_host_impl.h b/content/browser/browser_child_process_host_impl.h
index 3668871..91f11025 100644
--- a/content/browser/browser_child_process_host_impl.h
+++ b/content/browser/browser_child_process_host_impl.h
@@ -22,7 +22,6 @@
 #include "content/browser/tracing/tracing_service_controller.h"
 #include "content/common/child_process.mojom.h"
 #include "content/common/child_process_host_impl.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/browser_child_process_host.h"
 #include "content/public/browser/child_process_data.h"
 #include "content/public/common/child_process_host.h"
@@ -51,7 +50,7 @@
 // Plugins/workers and other child processes that live on the IO thread use this
 // class. RenderProcessHostImpl is the main exception that doesn't use this
 /// class because it lives on the UI thread.
-class CONTENT_EXPORT BrowserChildProcessHostImpl
+class BrowserChildProcessHostImpl
     : public BrowserChildProcessHost,
       public ChildProcessHostDelegate,
 #if defined(OS_WIN)
diff --git a/content/browser/browser_main.h b/content/browser/browser_main.h
index c58d09d..f8a943afc 100644
--- a/content/browser/browser_main.h
+++ b/content/browser/browser_main.h
@@ -5,13 +5,11 @@
 #ifndef CONTENT_BROWSER_BROWSER_MAIN_H_
 #define CONTENT_BROWSER_BROWSER_MAIN_H_
 
-#include "content/common/content_export.h"
-
 namespace content {
 
 struct MainFunctionParams;
 
-CONTENT_EXPORT int BrowserMain(content::MainFunctionParams parameters);
+int BrowserMain(content::MainFunctionParams parameters);
 
 }  // namespace content
 
diff --git a/content/browser/browser_plugin/browser_plugin_embedder.h b/content/browser/browser_plugin/browser_plugin_embedder.h
index 13c6126..4c15916eb 100644
--- a/content/browser/browser_plugin/browser_plugin_embedder.h
+++ b/content/browser/browser_plugin/browser_plugin_embedder.h
@@ -14,8 +14,6 @@
 #ifndef CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_EMBEDDER_H_
 #define CONTENT_BROWSER_BROWSER_PLUGIN_BROWSER_PLUGIN_EMBEDDER_H_
 
-#include "content/common/content_export.h"
-
 namespace content {
 
 class BrowserPluginGuest;
@@ -26,7 +24,7 @@
 
 // TODO(wjmaclean): Get rid of "BrowserPlugin" in the name of this class.
 // Perhaps "WebContentsEmbedderDelegate" would be better?
-class CONTENT_EXPORT BrowserPluginEmbedder {
+class BrowserPluginEmbedder {
  public:
   BrowserPluginEmbedder(const BrowserPluginEmbedder&) = delete;
   BrowserPluginEmbedder& operator=(const BrowserPluginEmbedder&) = delete;
diff --git a/content/browser/browser_plugin/browser_plugin_guest.h b/content/browser/browser_plugin/browser_plugin_guest.h
index cd1ca88..16cf3ee 100644
--- a/content/browser/browser_plugin/browser_plugin_guest.h
+++ b/content/browser/browser_plugin/browser_plugin_guest.h
@@ -22,7 +22,6 @@
 
 #include "base/memory/weak_ptr.h"
 #include "build/build_config.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/browser_plugin_guest_delegate.h"
 #include "content/public/browser/guest_host.h"
 #include "content/public/browser/web_contents_observer.h"
@@ -50,8 +49,7 @@
 // dropped on the floor since we don't have a BrowserPlugin.
 // TODO(wjmaclean): Get rid of "BrowserPlugin" in the name of this class.
 // Perhaps "InnerWebContentsGuestConnector"?
-class CONTENT_EXPORT BrowserPluginGuest : public GuestHost,
-                                          public WebContentsObserver {
+class BrowserPluginGuest : public GuestHost, public WebContentsObserver {
  public:
   BrowserPluginGuest(const BrowserPluginGuest&) = delete;
   BrowserPluginGuest& operator=(const BrowserPluginGuest&) = delete;
diff --git a/content/browser/browsing_data/conditional_cache_deletion_helper.h b/content/browser/browsing_data/conditional_cache_deletion_helper.h
index e882a61..834a1d0 100644
--- a/content/browser/browsing_data/conditional_cache_deletion_helper.h
+++ b/content/browser/browsing_data/conditional_cache_deletion_helper.h
@@ -9,7 +9,6 @@
 
 #include "base/callback_forward.h"
 #include "base/task/sequenced_task_runner_helpers.h"
-#include "content/common/content_export.h"
 #include "net/base/completion_once_callback.h"
 #include "net/base/net_errors.h"
 #include "net/disk_cache/disk_cache.h"
@@ -22,7 +21,7 @@
 namespace content {
 
 // Helper to remove http/code cache data from a StoragePartition.
-class CONTENT_EXPORT ConditionalCacheDeletionHelper {
+class ConditionalCacheDeletionHelper {
  public:
   // Creates a helper to delete |cache| entries that match the |condition|.
   ConditionalCacheDeletionHelper(
diff --git a/content/browser/cache_storage/cache_storage_cache_entry_handler.h b/content/browser/cache_storage/cache_storage_cache_entry_handler.h
index 305e20f..e0d496a 100644
--- a/content/browser/cache_storage/cache_storage_cache_entry_handler.h
+++ b/content/browser/cache_storage/cache_storage_cache_entry_handler.h
@@ -18,7 +18,6 @@
 #include "content/browser/cache_storage/cache_storage_cache_handle.h"
 #include "content/browser/cache_storage/cache_storage_manager.h"
 #include "content/browser/cache_storage/scoped_writable_entry.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "net/disk_cache/disk_cache.h"
 #include "storage/browser/blob/blob_data_builder.h"
@@ -63,7 +62,7 @@
   ScopedWritableEntry cache_entry;
 };
 
-class CONTENT_EXPORT CacheStorageCacheEntryHandler {
+class CacheStorageCacheEntryHandler {
  public:
   // The DiskCacheBlobEntry is a ref-counted object containing both
   // a disk_cache Entry and a Handle to the cache in which it lives.  This
diff --git a/content/browser/cache_storage/cache_storage_cache_observer.h b/content/browser/cache_storage/cache_storage_cache_observer.h
index e0386ee..edb67486 100644
--- a/content/browser/cache_storage/cache_storage_cache_observer.h
+++ b/content/browser/cache_storage/cache_storage_cache_observer.h
@@ -5,13 +5,11 @@
 #ifndef CONTENT_BROWSER_CACHE_STORAGE_CACHE_STORAGE_CACHE_OBSERVER_H_
 #define CONTENT_BROWSER_CACHE_STORAGE_CACHE_STORAGE_CACHE_OBSERVER_H_
 
-#include "content/common/content_export.h"
-
 namespace content {
 
 class LegacyCacheStorageCache;
 
-class CONTENT_EXPORT CacheStorageCacheObserver {
+class CacheStorageCacheObserver {
  public:
   // The cache size has been set.
   virtual void CacheSizeUpdated(const LegacyCacheStorageCache* cache) = 0;
diff --git a/content/browser/cache_storage/cache_storage_control_wrapper.h b/content/browser/cache_storage/cache_storage_control_wrapper.h
index e4522b3d..05bdd1c 100644
--- a/content/browser/cache_storage/cache_storage_control_wrapper.h
+++ b/content/browser/cache_storage/cache_storage_control_wrapper.h
@@ -9,7 +9,6 @@
 #include "base/memory/scoped_refptr.h"
 #include "components/services/storage/public/mojom/cache_storage_control.mojom.h"
 #include "content/browser/cache_storage/cache_storage_context_impl.h"
-#include "content/common/content_export.h"
 #include "storage/browser/quota/quota_manager_proxy.h"
 #include "storage/browser/quota/special_storage_policy.h"
 #include "storage/browser/quota/storage_policy_observer.h"
@@ -22,8 +21,7 @@
 // service mojo for cache storage. It wraps mojo calls to track storage keys
 // usage and forwards them to the storage service remote. All functions should
 // be called on the UI thread.
-class CONTENT_EXPORT CacheStorageControlWrapper
-    : public storage::mojom::CacheStorageControl {
+class CacheStorageControlWrapper : public storage::mojom::CacheStorageControl {
  public:
   CacheStorageControlWrapper(
       scoped_refptr<base::SequencedTaskRunner> io_task_runner,
diff --git a/content/browser/cache_storage/cache_storage_operation.h b/content/browser/cache_storage/cache_storage_operation.h
index 6ff28475..1544f16 100644
--- a/content/browser/cache_storage/cache_storage_operation.h
+++ b/content/browser/cache_storage/cache_storage_operation.h
@@ -12,13 +12,12 @@
 #include "base/task/sequenced_task_runner.h"
 #include "base/time/time.h"
 #include "content/browser/cache_storage/cache_storage_scheduler_types.h"
-#include "content/common/content_export.h"
 
 namespace content {
 
 // An operation to run in the CacheStorageScheduler. It's mostly just a closure
 // to run plus a bunch of metrics data.
-class CONTENT_EXPORT CacheStorageOperation {
+class CacheStorageOperation {
  public:
   CacheStorageOperation(base::OnceClosure closure,
                         CacheStorageSchedulerId id,
diff --git a/content/browser/compositor/viz_process_transport_factory.cc b/content/browser/compositor/viz_process_transport_factory.cc
index bb929cb9..c89b4d4 100644
--- a/content/browser/compositor/viz_process_transport_factory.cc
+++ b/content/browser/compositor/viz_process_transport_factory.cc
@@ -475,7 +475,10 @@
       gpu_feature_info.status_values[gpu::GPU_FEATURE_TYPE_OOP_RASTERIZATION] ==
           gpu::kGpuFeatureStatusEnabled;
   bool enable_gpu_rasterization =
-      features::IsUiGpuRasterizationEnabled() && !enable_oop_rasterization;
+      features::IsUiGpuRasterizationEnabled() &&
+      gpu_feature_info.status_values[gpu::GPU_FEATURE_TYPE_GPU_RASTERIZATION] ==
+          gpu::kGpuFeatureStatusEnabled &&
+      !enable_oop_rasterization;
 
   if (!worker_context_provider_) {
     worker_context_provider_ = CreateContextProvider(
diff --git a/content/browser/compute_pressure/compute_pressure_sample.h b/content/browser/compute_pressure/compute_pressure_sample.h
index b6aa6e0..2fd7486 100644
--- a/content/browser/compute_pressure/compute_pressure_sample.h
+++ b/content/browser/compute_pressure/compute_pressure_sample.h
@@ -5,12 +5,10 @@
 #ifndef CONTENT_BROWSER_COMPUTE_PRESSURE_COMPUTE_PRESSURE_SAMPLE_H_
 #define CONTENT_BROWSER_COMPUTE_PRESSURE_COMPUTE_PRESSURE_SAMPLE_H_
 
-#include "content/common/content_export.h"
-
 namespace content {
 
 // Represents availability of compute resources measured over a period of time.
-struct CONTENT_EXPORT ComputePressureSample {
+struct ComputePressureSample {
   // Average utilization of all CPU cores.
   //
   // Values use a scale from 0.0 (no utilization) to 1.0 (100% utilization).
diff --git a/content/browser/contacts/contacts_manager_impl.h b/content/browser/contacts/contacts_manager_impl.h
index 52f8cc5..4ff6b6a 100644
--- a/content/browser/contacts/contacts_manager_impl.h
+++ b/content/browser/contacts/contacts_manager_impl.h
@@ -6,7 +6,6 @@
 #define CONTENT_BROWSER_CONTACTS_CONTACTS_MANAGER_IMPL_H_
 
 #include "content/browser/contacts/contacts_provider.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/document_service.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "third_party/blink/public/mojom/contacts/contacts_manager.mojom.h"
@@ -15,7 +14,7 @@
 
 class RenderFrameHostImpl;
 
-class CONTENT_EXPORT ContactsManagerImpl
+class ContactsManagerImpl
     : public DocumentService<blink::mojom::ContactsManager> {
  public:
   explicit ContactsManagerImpl(
diff --git a/content/browser/content_index/content_index_context_impl.h b/content/browser/content_index/content_index_context_impl.h
index 482ae6b..46b9706 100644
--- a/content/browser/content_index/content_index_context_impl.h
+++ b/content/browser/content_index/content_index_context_impl.h
@@ -7,7 +7,6 @@
 
 #include "base/memory/ref_counted.h"
 #include "content/browser/content_index/content_index_database.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/content_index_context.h"
 #include "third_party/blink/public/mojom/content_index/content_index.mojom.h"
@@ -20,7 +19,7 @@
 
 // Owned by the Storage Partition. Components that want to query or modify the
 // Content Index database should hold a reference to this.
-class CONTENT_EXPORT ContentIndexContextImpl
+class ContentIndexContextImpl
     : public ContentIndexContext,
       public base::RefCountedThreadSafe<ContentIndexContextImpl> {
  public:
diff --git a/content/browser/devtools/devtools_instrumentation.cc b/content/browser/devtools/devtools_instrumentation.cc
index b55f037..35177de 100644
--- a/content/browser/devtools/devtools_instrumentation.cc
+++ b/content/browser/devtools/devtools_instrumentation.cc
@@ -890,6 +890,17 @@
   UpdatePortals(render_frame_host_impl);
 }
 
+void FencedFrameCreated(
+    base::SafeRef<RenderFrameHostImpl> owner_render_frame_host,
+    FencedFrame* fenced_frame) {
+  auto* agent_host = static_cast<RenderFrameDevToolsAgentHost*>(
+      RenderFrameDevToolsAgentHost::GetFor(
+          owner_render_frame_host->frame_tree_node()));
+  if (!agent_host)
+    return;
+  agent_host->DidCreateFencedFrame(fenced_frame);
+}
+
 void WillStartDragging(FrameTreeNode* main_frame_tree_node,
                        const blink::mojom::DragDataPtr drag_data,
                        blink::DragOperationsMask drag_operations_mask,
diff --git a/content/browser/devtools/devtools_instrumentation.h b/content/browser/devtools/devtools_instrumentation.h
index 1bff9c3..fdfb9529 100644
--- a/content/browser/devtools/devtools_instrumentation.h
+++ b/content/browser/devtools/devtools_instrumentation.h
@@ -53,6 +53,7 @@
 class BackForwardCacheCanStoreDocumentResult;
 class BrowserContext;
 class DevToolsAgentHostImpl;
+class FencedFrame;
 class FrameTreeNode;
 class NavigationHandle;
 class NavigationRequest;
@@ -233,6 +234,10 @@
 void PortalDetached(RenderFrameHostImpl* render_frame_host_impl);
 void PortalActivated(RenderFrameHostImpl* render_frame_host_impl);
 
+void FencedFrameCreated(
+    base::SafeRef<RenderFrameHostImpl> owner_render_frame_host,
+    FencedFrame* fenced_frame);
+
 void ReportSameSiteCookieIssue(
     RenderFrameHostImpl* render_frame_host_impl,
     const network::mojom::CookieOrLineWithAccessResultPtr& excluded_cookie,
diff --git a/content/browser/devtools/devtools_manager.h b/content/browser/devtools/devtools_manager.h
index 9f3c14b..98cdcec0 100644
--- a/content/browser/devtools/devtools_manager.h
+++ b/content/browser/devtools/devtools_manager.h
@@ -8,7 +8,6 @@
 #include <memory>
 
 #include "base/memory/singleton.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/devtools_manager_delegate.h"
 
 namespace content {
@@ -16,7 +15,7 @@
 // This class is a singleton that manage global DevTools state for the whole
 // browser.
 // TODO(dgozman): remove this class entirely.
-class CONTENT_EXPORT DevToolsManager {
+class DevToolsManager {
  public:
   // Returns single instance of this class. The instance is destroyed on the
   // browser main loop exit so this method MUST NOT be called after that point.
diff --git a/content/browser/devtools/devtools_renderer_channel.h b/content/browser/devtools/devtools_renderer_channel.h
index aea7b3e..4bc39c3d 100644
--- a/content/browser/devtools/devtools_renderer_channel.h
+++ b/content/browser/devtools/devtools_renderer_channel.h
@@ -9,7 +9,6 @@
 #include "base/callback_helpers.h"
 #include "base/containers/flat_set.h"
 #include "base/memory/weak_ptr.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
@@ -33,8 +32,7 @@
 // is used for the frame), different DevToolsAgentHostImpl subclasses
 // retrieve a new blink::mojom::DevToolsAgent pointer, and this channel
 // starts using it for all existing and future sessions.
-class CONTENT_EXPORT DevToolsRendererChannel
-    : public blink::mojom::DevToolsAgentHost {
+class DevToolsRendererChannel : public blink::mojom::DevToolsAgentHost {
  public:
   explicit DevToolsRendererChannel(DevToolsAgentHostImpl* owner);
 
diff --git a/content/browser/devtools/devtools_video_consumer.cc b/content/browser/devtools/devtools_video_consumer.cc
index 8098d0e..513bd8de 100644
--- a/content/browser/devtools/devtools_video_consumer.cc
+++ b/content/browser/devtools/devtools_video_consumer.cc
@@ -86,11 +86,7 @@
     const viz::FrameSinkId& frame_sink_id) {
   frame_sink_id_ = frame_sink_id;
   if (capturer_) {
-    capturer_->ChangeTarget(
-        frame_sink_id_.is_valid()
-            ? absl::make_optional<viz::FrameSinkId>(frame_sink_id_)
-            : absl::nullopt,
-        nullptr);
+    capturer_->ChangeTarget(viz::VideoCaptureTarget(frame_sink_id_));
   }
 }
 
@@ -132,7 +128,7 @@
                                       kDefaultUseFixedAspectRatio);
   capturer_->SetFormat(pixel_format_, color_space_);
   if (frame_sink_id_.is_valid())
-    capturer_->ChangeTarget(frame_sink_id_, nullptr);
+    capturer_->ChangeTarget(viz::VideoCaptureTarget(frame_sink_id_));
 
   capturer_->Start(this);
 }
diff --git a/content/browser/devtools/devtools_video_consumer_unittest.cc b/content/browser/devtools/devtools_video_consumer_unittest.cc
index bb20f060..6ed566ef 100644
--- a/content/browser/devtools/devtools_video_consumer_unittest.cc
+++ b/content/browser/devtools/devtools_video_consumer_unittest.cc
@@ -85,9 +85,9 @@
                     bool use_fixed_aspect_ratio));
   // This is never called.
   MOCK_METHOD1(SetAutoThrottlingEnabled, void(bool));
-  void ChangeTarget(const absl::optional<viz::FrameSinkId>& frame_sink_id,
-                    viz::mojom::SubTargetPtr sub_target) final {
-    frame_sink_id_ = frame_sink_id ? *frame_sink_id : viz::FrameSinkId();
+  void ChangeTarget(
+      const absl::optional<viz::VideoCaptureTarget>& target) final {
+    frame_sink_id_ = target ? target->frame_sink_id : viz::FrameSinkId();
     MockChangeTarget(frame_sink_id_);
   }
   MOCK_METHOD1(MockChangeTarget, void(const viz::FrameSinkId& frame_sink_id));
diff --git a/content/browser/devtools/frame_auto_attacher.cc b/content/browser/devtools/frame_auto_attacher.cc
index b31873c..5f0b667c 100644
--- a/content/browser/devtools/frame_auto_attacher.cc
+++ b/content/browser/devtools/frame_auto_attacher.cc
@@ -194,6 +194,15 @@
   DispatchSetAttachedTargetsOfType(new_hosts, DevToolsAgentHost::kTypePage);
 }
 
+void FrameAutoAttacher::AutoAttachToPage(FrameTree* frame_tree,
+                                         bool wait_for_debugger_on_start) {
+  if (!auto_attach())
+    return;
+  scoped_refptr<DevToolsAgentHost> agent_host =
+      RenderFrameDevToolsAgentHost::GetOrCreateFor(frame_tree->root());
+  DispatchAutoAttach(agent_host.get(), wait_for_debugger_on_start);
+}
+
 void FrameAutoAttacher::UpdateAutoAttach(base::OnceClosure callback) {
   if (auto_attach()) {
     UpdateFrames();
diff --git a/content/browser/devtools/frame_auto_attacher.h b/content/browser/devtools/frame_auto_attacher.h
index 6e17f6e..9adda9d 100644
--- a/content/browser/devtools/frame_auto_attacher.h
+++ b/content/browser/devtools/frame_auto_attacher.h
@@ -12,6 +12,7 @@
 namespace content {
 
 class DevToolsRendererChannel;
+class FrameTree;
 class NavigationRequest;
 class RenderFrameHostImpl;
 class ServiceWorkerDevToolsAgentHost;
@@ -26,6 +27,7 @@
   void SetRenderFrameHost(RenderFrameHostImpl* render_frame_host);
   void DidFinishNavigation(NavigationRequest* navigation_request);
   void UpdatePages();
+  void AutoAttachToPage(FrameTree* frame_tree, bool wait_for_debugger_on_start);
 
  protected:
   // Base overrides.
diff --git a/content/browser/devtools/network_service_devtools_observer.h b/content/browser/devtools/network_service_devtools_observer.h
index 943ee2a3..f9b01b0 100644
--- a/content/browser/devtools/network_service_devtools_observer.h
+++ b/content/browser/devtools/network_service_devtools_observer.h
@@ -10,7 +10,6 @@
 #include "base/time/time.h"
 #include "base/types/pass_key.h"
 #include "content/browser/renderer_host/frame_tree_node.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "services/network/public/mojom/devtools_observer.mojom.h"
 
@@ -20,8 +19,7 @@
 
 // A springboard class to be able to bind to the network service as a
 // DevToolsObserver but not requiring the creation of a DevToolsAgentHostImpl.
-class CONTENT_EXPORT NetworkServiceDevToolsObserver
-    : public network::mojom::DevToolsObserver {
+class NetworkServiceDevToolsObserver : public network::mojom::DevToolsObserver {
  public:
   NetworkServiceDevToolsObserver(
       base::PassKey<NetworkServiceDevToolsObserver> pass_key,
diff --git a/content/browser/devtools/protocol/devtools_download_manager_delegate.h b/content/browser/devtools/protocol/devtools_download_manager_delegate.h
index 44af9c8b..3cd5e8af 100644
--- a/content/browser/devtools/protocol/devtools_download_manager_delegate.h
+++ b/content/browser/devtools/protocol/devtools_download_manager_delegate.h
@@ -14,7 +14,6 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/singleton.h"
 #include "base/memory/weak_ptr.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/download_manager_delegate.h"
 
 namespace content {
@@ -23,7 +22,7 @@
 
 namespace protocol {
 
-class CONTENT_EXPORT DevToolsDownloadManagerDelegate
+class DevToolsDownloadManagerDelegate
     : public base::SupportsUserData::Data,
       public content::DownloadManagerDelegate {
  public:
diff --git a/content/browser/devtools/protocol/target_auto_attacher.cc b/content/browser/devtools/protocol/target_auto_attacher.cc
index 6a232d2..5e295ad 100644
--- a/content/browser/devtools/protocol/target_auto_attacher.cc
+++ b/content/browser/devtools/protocol/target_auto_attacher.cc
@@ -51,10 +51,8 @@
       frame_tree_node->IsMainFrame() &&
       static_cast<WebContentsImpl*>(WebContents::FromRenderFrameHost(new_host))
           ->IsPortal();
-  bool is_fenced_frame_main_frame =
-      frame_tree_node->IsMainFrame() && frame_tree_node->IsFencedFrameRoot();
-  bool needs_host_attached = new_host->is_local_root_subframe() ||
-                             is_portal_main_frame || is_fenced_frame_main_frame;
+  bool needs_host_attached =
+      new_host->is_local_root_subframe() || is_portal_main_frame;
 
   if (needs_host_attached) {
     if (!agent_host) {
diff --git a/content/browser/devtools/protocol/target_handler.cc b/content/browser/devtools/protocol/target_handler.cc
index 6d6ed82..8ce952c4 100644
--- a/content/browser/devtools/protocol/target_handler.cc
+++ b/content/browser/devtools/protocol/target_handler.cc
@@ -648,7 +648,9 @@
       NavigationRequest::From(navigation_handle)->frame_tree_node();
   DCHECK(access_mode_ != AccessMode::kBrowser ||
          !auto_attach_related_targets_.empty() || !frame_tree_node->parent());
-  if (access_mode_ == AccessMode::kBrowser && !frame_tree_node->parent()) {
+  if (frame_tree_node->IsMainFrame() &&
+      (access_mode_ == AccessMode::kBrowser ||
+       frame_tree_node->IsFencedFrameRoot())) {
     DCHECK(auto_attacher == auto_attacher_);
     DevToolsAgentHost* host =
         RenderFrameDevToolsAgentHost::GetFor(frame_tree_node);
diff --git a/content/browser/devtools/render_frame_devtools_agent_host.cc b/content/browser/devtools/render_frame_devtools_agent_host.cc
index 3577412a..6562d5f8 100644
--- a/content/browser/devtools/render_frame_devtools_agent_host.cc
+++ b/content/browser/devtools/render_frame_devtools_agent_host.cc
@@ -45,6 +45,7 @@
 #include "content/browser/devtools/protocol/storage_handler.h"
 #include "content/browser/devtools/protocol/target_handler.h"
 #include "content/browser/devtools/protocol/tracing_handler.h"
+#include "content/browser/fenced_frame/fenced_frame.h"
 #include "content/browser/renderer_host/navigation_request.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/renderer_host/render_process_host_impl.h"
@@ -653,6 +654,12 @@
   auto_attacher_->UpdatePages();
 }
 
+void RenderFrameDevToolsAgentHost::DidCreateFencedFrame(
+    FencedFrame* fenced_frame) {
+  auto_attacher_->AutoAttachToPage(fenced_frame->GetInnerRoot()->frame_tree(),
+                                   true);
+}
+
 void RenderFrameDevToolsAgentHost::DisconnectWebContents() {
   navigation_requests_.clear();
   SetFrameTreeNode(nullptr);
diff --git a/content/browser/devtools/render_frame_devtools_agent_host.h b/content/browser/devtools/render_frame_devtools_agent_host.h
index f84a0427..d2bc386 100644
--- a/content/browser/devtools/render_frame_devtools_agent_host.h
+++ b/content/browser/devtools/render_frame_devtools_agent_host.h
@@ -33,6 +33,7 @@
 
 class BrowserContext;
 class DevToolsFrameTraceRecorder;
+class FencedFrame;
 class FrameTreeNode;
 class FrameAutoAttacher;
 class NavigationRequest;
@@ -87,6 +88,7 @@
   void OnNavigationRequestWillBeSent(
       const NavigationRequest& navigation_request);
   void UpdatePortals();
+  void DidCreateFencedFrame(FencedFrame* fenced_frame);
 
   // DevToolsAgentHost overrides.
   void DisconnectWebContents() override;
diff --git a/content/browser/devtools/service_worker_devtools_manager.h b/content/browser/devtools/service_worker_devtools_manager.h
index 6724807..477baacf 100644
--- a/content/browser/devtools/service_worker_devtools_manager.h
+++ b/content/browser/devtools/service_worker_devtools_manager.h
@@ -15,7 +15,6 @@
 #include "base/observer_list.h"
 #include "base/unguessable_token.h"
 #include "content/browser/devtools/devtools_throttle_handle.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/global_routing_id.h"
 #include "services/network/public/cpp/cross_origin_embedder_policy.h"
 #include "services/network/public/mojom/cross_origin_embedder_policy.mojom-forward.h"
@@ -36,7 +35,7 @@
 class ServiceWorkerContextWrapper;
 
 // Manages ServiceWorkerDevToolsAgentHost's. This class lives on UI thread.
-class CONTENT_EXPORT ServiceWorkerDevToolsManager {
+class ServiceWorkerDevToolsManager {
  public:
   class Observer {
    public:
diff --git a/content/browser/devtools/shared_worker_devtools_manager.h b/content/browser/devtools/shared_worker_devtools_manager.h
index fa4263a..02205863b 100644
--- a/content/browser/devtools/shared_worker_devtools_manager.h
+++ b/content/browser/devtools/shared_worker_devtools_manager.h
@@ -11,7 +11,6 @@
 #include "base/gtest_prod_util.h"
 #include "base/memory/singleton.h"
 #include "base/unguessable_token.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/devtools_agent_host.h"
 #include "third_party/blink/public/mojom/devtools/devtools_agent.mojom.h"
 
@@ -22,7 +21,7 @@
 
 // Manages WorkerDevToolsAgentHost's for Shared Workers.
 // This class lives on UI thread.
-class CONTENT_EXPORT SharedWorkerDevToolsManager {
+class SharedWorkerDevToolsManager {
  public:
   // Returns the SharedWorkerDevToolsManager singleton.
   static SharedWorkerDevToolsManager* GetInstance();
diff --git a/content/browser/devtools/worker_devtools_manager.h b/content/browser/devtools/worker_devtools_manager.h
index 180fff6..1138dc0 100644
--- a/content/browser/devtools/worker_devtools_manager.h
+++ b/content/browser/devtools/worker_devtools_manager.h
@@ -10,7 +10,6 @@
 #include "base/memory/singleton.h"
 #include "content/browser/devtools/devtools_agent_host_impl.h"
 #include "content/browser/devtools/devtools_throttle_handle.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/devtools_agent_host.h"
 #include "content/public/browser/global_routing_id.h"
 #include "third_party/blink/public/mojom/devtools/devtools_agent.mojom.h"
@@ -22,7 +21,7 @@
 
 // Manages WorkerDevToolsAgentHost's for Dedicated Workers. This class lives on
 // UI thread. This is only used for PlzDedicatedWorker.
-class CONTENT_EXPORT WorkerDevToolsManager {
+class WorkerDevToolsManager {
  public:
   // Returns the WorkerDevToolsManager singleton.
   static WorkerDevToolsManager& GetInstance();
diff --git a/content/browser/direct_sockets/direct_udp_socket_impl.h b/content/browser/direct_sockets/direct_udp_socket_impl.h
index bc6934d7..0c09824 100644
--- a/content/browser/direct_sockets/direct_udp_socket_impl.h
+++ b/content/browser/direct_sockets/direct_udp_socket_impl.h
@@ -5,7 +5,6 @@
 #ifndef CONTENT_BROWSER_DIRECT_SOCKETS_DIRECT_UDP_SOCKET_IMPL_H_
 #define CONTENT_BROWSER_DIRECT_SOCKETS_DIRECT_UDP_SOCKET_IMPL_H_
 
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "net/base/ip_endpoint.h"
@@ -18,8 +17,7 @@
 // Forwards requests from the Renderer to the connected UDPSocket.
 // We do not expose the UDPSocket directly to the Renderer, as that
 // would allow a compromised Renderer to contact other end points.
-class CONTENT_EXPORT DirectUDPSocketImpl
-    : public blink::mojom::DirectUDPSocket {
+class DirectUDPSocketImpl : public blink::mojom::DirectUDPSocket {
  public:
   typedef network::mojom::UDPSocket::ConnectCallback ConnectCallback;
 
diff --git a/content/browser/dom_storage/session_storage_namespace_impl.h b/content/browser/dom_storage/session_storage_namespace_impl.h
index d036cd1..9d925ae5 100644
--- a/content/browser/dom_storage/session_storage_namespace_impl.h
+++ b/content/browser/dom_storage/session_storage_namespace_impl.h
@@ -11,7 +11,6 @@
 #include <string>
 
 #include "base/memory/scoped_refptr.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/session_storage_namespace.h"
 
 namespace content {
@@ -21,14 +20,14 @@
  public:
   // Constructs a SessionStorageNamespaceImpl and allocates a new ID for it.
   //
-  // The CONTENT_EXPORT allows TestRenderViewHost to instantiate these.
-  CONTENT_EXPORT static scoped_refptr<SessionStorageNamespaceImpl> Create(
+  // The allows TestRenderViewHost to instantiate these.
+  static scoped_refptr<SessionStorageNamespaceImpl> Create(
       scoped_refptr<DOMStorageContextWrapper> context);
 
   // If there is an existing SessionStorageNamespaceImpl with the given id in
   // the DOMStorageContextWrapper, this will return that object. Otherwise this
   // constructs a SessionStorageNamespaceImpl and assigns |namespace_id| to it.
-  CONTENT_EXPORT static scoped_refptr<SessionStorageNamespaceImpl> Create(
+  static scoped_refptr<SessionStorageNamespaceImpl> Create(
       scoped_refptr<DOMStorageContextWrapper> context,
       std::string namespace_id);
 
diff --git a/content/browser/download/data_url_blob_reader.h b/content/browser/download/data_url_blob_reader.h
index de6faca..144fddb 100644
--- a/content/browser/download/data_url_blob_reader.h
+++ b/content/browser/download/data_url_blob_reader.h
@@ -9,7 +9,6 @@
 
 #include "base/callback_forward.h"
 #include "base/sequence_checker.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -24,7 +23,7 @@
 namespace content {
 
 // Helper class to read a data url from a BlobDataHandle.
-class CONTENT_EXPORT DataURLBlobReader : public mojo::DataPipeDrainer::Client {
+class DataURLBlobReader : public mojo::DataPipeDrainer::Client {
  public:
   using ReadCompletionCallback = base::OnceCallback<void(GURL)>;
 
diff --git a/content/browser/download/drag_download_util.h b/content/browser/download/drag_download_util.h
index 10260a2..18d0026 100644
--- a/content/browser/download/drag_download_util.h
+++ b/content/browser/download/drag_download_util.h
@@ -10,7 +10,6 @@
 #include "base/files/file.h"
 #include "base/memory/ref_counted.h"
 #include "content/browser/download/drag_download_file.h"
-#include "content/common/content_export.h"
 #include "ui/base/dragdrop/download_file_interface.h"
 
 class GURL;
@@ -38,7 +37,7 @@
 // Create a new file at the specified path. If the file already exists, try to
 // insert the sequential unifier to produce a new file, like foo-01.txt.
 // Return a File if successful.
-CONTENT_EXPORT base::File CreateFileForDrop(base::FilePath* file_path);
+base::File CreateFileForDrop(base::FilePath* file_path);
 
 // Implementation of DownloadFileObserver to finalize the download process.
 class PromiseFileFinalizer : public ui::DownloadFileObserver {
diff --git a/content/browser/download/save_package_serialization_handler.h b/content/browser/download/save_package_serialization_handler.h
index 79acb0c..b670bce 100644
--- a/content/browser/download/save_package_serialization_handler.h
+++ b/content/browser/download/save_package_serialization_handler.h
@@ -8,7 +8,6 @@
 #include <string>
 
 #include "base/callback.h"
-#include "content/common/content_export.h"
 #include "content/common/frame.mojom.h"
 
 namespace content {
@@ -18,7 +17,7 @@
 // renderer: |did_serialize_data_callback| will report each chunk of data that's
 // being serialized, while |done_callback| will simply notify when the
 // serialization process is finished.
-class CONTENT_EXPORT SavePackageSerializationHandler
+class SavePackageSerializationHandler
     : public mojom::FrameHTMLSerializerHandler {
  public:
   using DidReceiveDataCallback =
diff --git a/content/browser/feature_observer.h b/content/browser/feature_observer.h
index e80417f0..1422a4f 100644
--- a/content/browser/feature_observer.h
+++ b/content/browser/feature_observer.h
@@ -6,7 +6,6 @@
 #define CONTENT_BROWSER_FEATURE_OBSERVER_H_
 
 #include "base/containers/stack_container.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/global_routing_id.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "third_party/blink/public/mojom/feature_observer/feature_observer.mojom.h"
@@ -18,7 +17,7 @@
 // Observer interface to be notified when frames hold resources.
 // client interfaces will be called on the same sequence GetFeatureObserver is
 // called from.
-class CONTENT_EXPORT FeatureObserver : public blink::mojom::FeatureObserver {
+class FeatureObserver : public blink::mojom::FeatureObserver {
  public:
   // |client_| must outlive FeatureObserver.
   FeatureObserver(FeatureObserverClient* client, GlobalRenderFrameHostId id);
diff --git a/content/browser/federated_learning/floc_service_impl.h b/content/browser/federated_learning/floc_service_impl.h
index 0e49634..2750bcef 100644
--- a/content/browser/federated_learning/floc_service_impl.h
+++ b/content/browser/federated_learning/floc_service_impl.h
@@ -5,7 +5,6 @@
 #ifndef CONTENT_BROWSER_FEDERATED_LEARNING_FLOC_SERVICE_IMPL_H_
 #define CONTENT_BROWSER_FEDERATED_LEARNING_FLOC_SERVICE_IMPL_H_
 
-#include "content/common/content_export.h"
 #include "content/public/browser/document_service.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "third_party/blink/public/mojom/federated_learning/floc.mojom.h"
@@ -14,7 +13,7 @@
 
 class RenderFrameHost;
 
-class CONTENT_EXPORT FlocServiceImpl final
+class FlocServiceImpl final
     : public DocumentService<blink::mojom::FlocService> {
  public:
   FlocServiceImpl(RenderFrameHost* render_frame_host,
diff --git a/content/browser/fenced_frame/fenced_frame.cc b/content/browser/fenced_frame/fenced_frame.cc
index 33556ef..7eea83fe 100644
--- a/content/browser/fenced_frame/fenced_frame.cc
+++ b/content/browser/fenced_frame/fenced_frame.cc
@@ -5,6 +5,7 @@
 #include "content/browser/fenced_frame/fenced_frame.h"
 
 #include "base/notreached.h"
+#include "content/browser/devtools/devtools_instrumentation.h"
 #include "content/browser/renderer_host/render_frame_proxy_host.h"
 #include "content/browser/renderer_host/render_widget_host_view_child_frame.h"
 #include "content/browser/web_contents/web_contents_impl.h"
@@ -49,6 +50,8 @@
       frame_tree_->root()->render_manager()->current_frame_host());
 
   CreateProxyAndAttachToOuterFrameTree();
+
+  devtools_instrumentation::FencedFrameCreated(owner_render_frame_host_, this);
 }
 
 FencedFrame::~FencedFrame() {
diff --git a/content/browser/file_system_access/file_system_access_access_handle_host_impl.h b/content/browser/file_system_access/file_system_access_access_handle_host_impl.h
index 6ea8e62..b89693f 100644
--- a/content/browser/file_system_access/file_system_access_access_handle_host_impl.h
+++ b/content/browser/file_system_access/file_system_access_access_handle_host_impl.h
@@ -10,7 +10,6 @@
 #include "content/browser/file_system_access/file_system_access_capacity_allocation_host_impl.h"
 #include "content/browser/file_system_access/file_system_access_file_delegate_host_impl.h"
 #include "content/browser/file_system_access/file_system_access_manager_impl.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_access_handle_host.mojom.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_file_delegate_host.mojom.h"
@@ -21,7 +20,7 @@
 // FileSystemAccessHandleHost mojom interface. Instances of this class are
 // owned by the FileSystemAccessManagerImpl instance passed in to the
 // constructor.
-class CONTENT_EXPORT FileSystemAccessAccessHandleHostImpl
+class FileSystemAccessAccessHandleHostImpl
     : public blink::mojom::FileSystemAccessAccessHandleHost {
  public:
   // Crates an AccessHandleHost that acts as an exclusive write lock on the
diff --git a/content/browser/file_system_access/file_system_access_file_delegate_host_impl.h b/content/browser/file_system_access/file_system_access_file_delegate_host_impl.h
index 0d1068b..907339e 100644
--- a/content/browser/file_system_access/file_system_access_file_delegate_host_impl.h
+++ b/content/browser/file_system_access/file_system_access_file_delegate_host_impl.h
@@ -7,7 +7,6 @@
 
 #include "components/services/storage/public/cpp/big_io_buffer.h"
 #include "content/browser/file_system_access/file_system_access_manager_impl.h"
-#include "content/common/content_export.h"
 #include "storage/browser/file_system/file_stream_reader.h"
 #include "storage/browser/file_system/file_system_url.h"
 #include "third_party/blink/public/mojom/file_system_access/file_system_access_file_delegate_host.mojom.h"
@@ -18,7 +17,7 @@
 // interface. Instances of this class are owned by the
 // FileSystemAccessAccessHandleHostImpl instance of the associated URL, which
 // constructs it.
-class CONTENT_EXPORT FileSystemAccessFileDelegateHostImpl
+class FileSystemAccessFileDelegateHostImpl
     : public blink::mojom::FileSystemAccessFileDelegateHost {
  public:
   FileSystemAccessFileDelegateHostImpl(
diff --git a/content/browser/find_request_manager.h b/content/browser/find_request_manager.h
index b59c4b9..fec29c22 100644
--- a/content/browser/find_request_manager.h
+++ b/content/browser/find_request_manager.h
@@ -12,7 +12,6 @@
 
 #include "base/containers/queue.h"
 #include "build/build_config.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/stop_find_action.h"
 #include "third_party/blink/public/mojom/frame/find_in_page.mojom.h"
@@ -32,7 +31,7 @@
 // results from each frame, and facilitates active match traversal. It is
 // instantiated once per top-level WebContents, and is owned by that
 // WebContents.
-class CONTENT_EXPORT FindRequestManager {
+class FindRequestManager {
  public:
   explicit FindRequestManager(WebContentsImpl* web_contents);
 
diff --git a/content/browser/font_service.h b/content/browser/font_service.h
index 556a0f4..ce01e57 100644
--- a/content/browser/font_service.h
+++ b/content/browser/font_service.h
@@ -6,13 +6,12 @@
 #define CONTENT_BROWSER_FONT_SERVICE_H_
 
 #include "components/services/font/public/mojom/font_service.mojom.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 
 namespace content {
 
 // Connects |receiver| to the global in-process instance of the Font service.
-CONTENT_EXPORT void ConnectToFontService(
+void ConnectToFontService(
     mojo::PendingReceiver<font_service::mojom::FontService> receiver);
 
 }  // namespace content
diff --git a/content/browser/gpu/browser_gpu_channel_host_factory.h b/content/browser/gpu/browser_gpu_channel_host_factory.h
index 2cd864c6..cfca6f4 100644
--- a/content/browser/gpu/browser_gpu_channel_host_factory.h
+++ b/content/browser/gpu/browser_gpu_channel_host_factory.h
@@ -16,7 +16,6 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/timer/timer.h"
 #include "build/build_config.h"
-#include "content/common/content_export.h"
 #include "gpu/ipc/client/gpu_channel_host.h"
 #include "ipc/message_filter.h"
 
@@ -26,8 +25,7 @@
 
 namespace content {
 
-class CONTENT_EXPORT BrowserGpuChannelHostFactory
-    : public gpu::GpuChannelEstablishFactory {
+class BrowserGpuChannelHostFactory : public gpu::GpuChannelEstablishFactory {
  public:
   static void Initialize(bool establish_gpu_channel);
   static void Terminate();
diff --git a/content/browser/gpu/chromeos/video_capture_dependencies.h b/content/browser/gpu/chromeos/video_capture_dependencies.h
index b93e369..e862062 100644
--- a/content/browser/gpu/chromeos/video_capture_dependencies.h
+++ b/content/browser/gpu/chromeos/video_capture_dependencies.h
@@ -5,14 +5,13 @@
 #ifndef CONTENT_BROWSER_GPU_CHROMEOS_VIDEO_CAPTURE_DEPENDENCIES_H_
 #define CONTENT_BROWSER_GPU_CHROMEOS_VIDEO_CAPTURE_DEPENDENCIES_H_
 
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "services/viz/privileged/mojom/gl/gpu_service.mojom.h"
 
 namespace content {
 
 // Browser-process-provided GPU dependencies for video capture.
-class CONTENT_EXPORT VideoCaptureDependencies {
+class VideoCaptureDependencies {
  public:
   static void CreateJpegDecodeAccelerator(
       mojo::PendingReceiver<chromeos_camera::mojom::MjpegDecodeAccelerator>
diff --git a/content/browser/gpu/compositor_util.h b/content/browser/gpu/compositor_util.h
index 2d7ece9f..59af2874 100644
--- a/content/browser/gpu/compositor_util.h
+++ b/content/browser/gpu/compositor_util.h
@@ -10,7 +10,6 @@
 #include <vector>
 
 #include "base/values.h"
-#include "content/common/content_export.h"
 
 namespace content {
 
@@ -19,31 +18,31 @@
 
 // Returns true if zero-copy uploads is on (via flags, or platform default).
 // Only one of one-copy and zero-copy can be enabled at a time.
-CONTENT_EXPORT bool IsZeroCopyUploadEnabled();
+bool IsZeroCopyUploadEnabled();
 
 // Returns true if a partial raster is on (via flags).
-CONTENT_EXPORT bool IsPartialRasterEnabled();
+bool IsPartialRasterEnabled();
 
 // Returns true if all compositor resources should use GPU memory buffers.
-CONTENT_EXPORT bool IsGpuMemoryBufferCompositorResourcesEnabled();
+bool IsGpuMemoryBufferCompositorResourcesEnabled();
 
 // Returns the number of multisample antialiasing samples (via flags) for
 // GPU rasterization.
-CONTENT_EXPORT int GpuRasterizationMSAASampleCount();
+int GpuRasterizationMSAASampleCount();
 
 // Returns the number of raster threads to use for compositing.
-CONTENT_EXPORT int NumberOfRendererRasterThreads();
+int NumberOfRendererRasterThreads();
 
 // Returns true if main thread can be pipelined with activation.
-CONTENT_EXPORT bool IsMainFrameBeforeActivationEnabled();
+bool IsMainFrameBeforeActivationEnabled();
 
-CONTENT_EXPORT base::Value GetFeatureStatus();
-CONTENT_EXPORT base::Value GetProblems();
-CONTENT_EXPORT std::vector<std::string> GetDriverBugWorkarounds();
+base::Value GetFeatureStatus();
+base::Value GetProblems();
+std::vector<std::string> GetDriverBugWorkarounds();
 
-CONTENT_EXPORT base::Value GetFeatureStatusForHardwareGpu();
-CONTENT_EXPORT base::Value GetProblemsForHardwareGpu();
-CONTENT_EXPORT std::vector<std::string> GetDriverBugWorkaroundsForHardwareGpu();
+base::Value GetFeatureStatusForHardwareGpu();
+base::Value GetProblemsForHardwareGpu();
+std::vector<std::string> GetDriverBugWorkaroundsForHardwareGpu();
 
 }  // namespace content
 
diff --git a/content/browser/gpu/gpu_feature_checker_impl.h b/content/browser/gpu/gpu_feature_checker_impl.h
index 3f25fcc2..31aa2cac 100644
--- a/content/browser/gpu/gpu_feature_checker_impl.h
+++ b/content/browser/gpu/gpu_feature_checker_impl.h
@@ -7,15 +7,14 @@
 
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/gpu_data_manager_observer.h"
 #include "content/public/browser/gpu_feature_checker.h"
 #include "gpu/config/gpu_feature_type.h"
 
 namespace content {
 
-class CONTENT_EXPORT GpuFeatureCheckerImpl : public GpuFeatureChecker,
-                                             public GpuDataManagerObserver {
+class GpuFeatureCheckerImpl : public GpuFeatureChecker,
+                              public GpuDataManagerObserver {
  public:
   GpuFeatureCheckerImpl(gpu::GpuFeatureType feature,
                         FeatureAvailableCallback callback);
diff --git a/content/browser/gpu/peak_gpu_memory_tracker_impl.h b/content/browser/gpu/peak_gpu_memory_tracker_impl.h
index 85e45f6..53d041a24 100644
--- a/content/browser/gpu/peak_gpu_memory_tracker_impl.h
+++ b/content/browser/gpu/peak_gpu_memory_tracker_impl.h
@@ -8,7 +8,6 @@
 #include "base/callback_forward.h"
 #include "base/callback_helpers.h"
 #include "base/task/single_thread_task_runner.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/peak_gpu_memory_tracker.h"
 
 namespace content {
@@ -22,7 +21,7 @@
 // GPU connection.
 //
 // This is instaniated via PeakGpuMemoryTracker::Create.
-class CONTENT_EXPORT PeakGpuMemoryTrackerImpl : public PeakGpuMemoryTracker {
+class PeakGpuMemoryTrackerImpl : public PeakGpuMemoryTracker {
  public:
   // Requests the GPU service to begin peak memory tracking.
   PeakGpuMemoryTrackerImpl(PeakGpuMemoryTracker::Usage usage);
diff --git a/content/browser/handwriting/handwriting_recognizer_impl.h b/content/browser/handwriting/handwriting_recognizer_impl.h
index b0288c3..f6cfb3e 100644
--- a/content/browser/handwriting/handwriting_recognizer_impl.h
+++ b/content/browser/handwriting/handwriting_recognizer_impl.h
@@ -5,7 +5,6 @@
 #ifndef CONTENT_BROWSER_HANDWRITING_HANDWRITING_RECOGNIZER_IMPL_H_
 #define CONTENT_BROWSER_HANDWRITING_HANDWRITING_RECOGNIZER_IMPL_H_
 
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/mojom/handwriting/handwriting.mojom.h"
@@ -20,7 +19,7 @@
 // This class will not return any prediction. But it has the ability to
 // set up and hold a mojo pipe with renderer, which can be used by the derived
 // classes.
-class CONTENT_EXPORT HandwritingRecognizerImpl
+class HandwritingRecognizerImpl
     : public handwriting::mojom::HandwritingRecognizer {
  public:
   // The interface to create an object, called by handwriting service.
diff --git a/content/browser/handwriting/handwriting_recognizer_impl_cros.h b/content/browser/handwriting/handwriting_recognizer_impl_cros.h
index 3353f26..25a5e35 100644
--- a/content/browser/handwriting/handwriting_recognizer_impl_cros.h
+++ b/content/browser/handwriting/handwriting_recognizer_impl_cros.h
@@ -11,7 +11,6 @@
 #include "chromeos/services/machine_learning/public/mojom/handwriting_recognizer.mojom-forward.h"
 #include "chromeos/services/machine_learning/public/mojom/web_platform_handwriting.mojom.h"
 #include "content/browser/handwriting/handwriting_recognizer_impl.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/mojom/handwriting/handwriting.mojom-forward.h"
@@ -25,8 +24,7 @@
 // checks etc. This class will also hold a mojo remote to the mlservice daemon
 // CrOS and mlservice will create a handwriting model instance for each of this
 // class.
-class CONTENT_EXPORT CrOSHandwritingRecognizerImpl final
-    : public HandwritingRecognizerImpl {
+class CrOSHandwritingRecognizerImpl final : public HandwritingRecognizerImpl {
  public:
   // The interface to create an object, called by handwriting service.
   static void Create(
diff --git a/content/browser/indexed_db/file_stream_reader_to_data_pipe.h b/content/browser/indexed_db/file_stream_reader_to_data_pipe.h
index b37b8caf..e24da519 100644
--- a/content/browser/indexed_db/file_stream_reader_to_data_pipe.h
+++ b/content/browser/indexed_db/file_stream_reader_to_data_pipe.h
@@ -7,7 +7,6 @@
 
 #include "base/callback_forward.h"
 #include "base/memory/weak_ptr.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/system/data_pipe.h"
 #include "mojo/public/cpp/system/simple_watcher.h"
 #include "services/network/public/cpp/net_adapters.h"
@@ -18,7 +17,7 @@
 
 // A convenient adapter class to read out data from a FileStreamReader
 // and write them into a data pipe.
-class CONTENT_EXPORT FileStreamReaderToDataPipe {
+class FileStreamReaderToDataPipe {
  public:
   // Reads out the data from |reader| and write into |dest|.
   // Can be called from any sequence.
diff --git a/content/browser/indexed_db/indexed_db_control_wrapper.h b/content/browser/indexed_db/indexed_db_control_wrapper.h
index 6c4a811..1a6badc4 100644
--- a/content/browser/indexed_db/indexed_db_control_wrapper.h
+++ b/content/browser/indexed_db/indexed_db_control_wrapper.h
@@ -9,7 +9,6 @@
 #include "components/services/storage/public/mojom/indexed_db_control.mojom.h"
 #include "components/services/storage/public/mojom/storage_policy_update.mojom.h"
 #include "content/browser/indexed_db/indexed_db_context_impl.h"
-#include "content/common/content_export.h"
 #include "storage/browser/quota/storage_policy_observer.h"
 
 namespace blink {
@@ -19,8 +18,7 @@
 namespace content {
 
 // All functions should be called on the UI thread.
-class CONTENT_EXPORT IndexedDBControlWrapper
-    : public storage::mojom::IndexedDBControl {
+class IndexedDBControlWrapper : public storage::mojom::IndexedDBControl {
  public:
   explicit IndexedDBControlWrapper(
       const base::FilePath& data_path,
diff --git a/content/browser/indexed_db/indexed_db_cursor.h b/content/browser/indexed_db/indexed_db_cursor.h
index ec3d620e..2ec5598 100644
--- a/content/browser/indexed_db/indexed_db_cursor.h
+++ b/content/browser/indexed_db/indexed_db_cursor.h
@@ -14,14 +14,13 @@
 #include "content/browser/indexed_db/indexed_db_backing_store.h"
 #include "content/browser/indexed_db/indexed_db_database.h"
 #include "content/browser/indexed_db/indexed_db_transaction.h"
-#include "content/common/content_export.h"
 #include "third_party/blink/public/common/indexeddb/web_idb_types.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-forward.h"
 
 namespace content {
 
-class CONTENT_EXPORT IndexedDBCursor {
+class IndexedDBCursor {
  public:
   IndexedDBCursor(std::unique_ptr<IndexedDBBackingStore::Cursor> cursor,
                   indexed_db::CursorType cursor_type,
diff --git a/content/browser/indexed_db/indexed_db_return_value.h b/content/browser/indexed_db/indexed_db_return_value.h
index ca8436b..789f689 100644
--- a/content/browser/indexed_db/indexed_db_return_value.h
+++ b/content/browser/indexed_db/indexed_db_return_value.h
@@ -6,7 +6,6 @@
 #define CONTENT_BROWSER_INDEXED_DB_INDEXED_DB_RETURN_VALUE_H_
 
 #include "content/browser/indexed_db/indexed_db_value.h"
-#include "content/common/content_export.h"
 #include "third_party/blink/public/common/indexeddb/indexeddb_key.h"
 #include "third_party/blink/public/common/indexeddb/indexeddb_key_path.h"
 
@@ -17,7 +16,7 @@
 // value cannot (at least easily) be amended to the object being written to the
 // database, so they are kept separately, and sent back with the original data
 // so that the render process can amend the returned object.
-struct CONTENT_EXPORT IndexedDBReturnValue : public IndexedDBValue {
+struct IndexedDBReturnValue : public IndexedDBValue {
   // Destructively converts an IndexedDBReturnValue to a Mojo ReturnValue.
   static blink::mojom::IDBReturnValuePtr ConvertReturnValue(
       IndexedDBReturnValue* value);
diff --git a/content/browser/indexed_db/mock_browsertest_indexed_db_class_factory.h b/content/browser/indexed_db/mock_browsertest_indexed_db_class_factory.h
index 8922fd2..23e8136 100644
--- a/content/browser/indexed_db/mock_browsertest_indexed_db_class_factory.h
+++ b/content/browser/indexed_db/mock_browsertest_indexed_db_class_factory.h
@@ -18,7 +18,6 @@
 #include "content/browser/indexed_db/indexed_db_class_factory.h"
 #include "content/browser/indexed_db/indexed_db_database.h"
 #include "content/browser/indexed_db/indexed_db_task_helper.h"
-#include "content/common/content_export.h"
 #include "third_party/blink/public/common/indexeddb/web_idb_types.h"
 
 namespace content {
@@ -32,7 +31,7 @@
 class TransactionalLevelDBTransaction;
 class TransactionalLevelDBDatabase;
 
-class CONTENT_EXPORT MockBrowserTestIndexedDBClassFactory
+class MockBrowserTestIndexedDBClassFactory
     : public IndexedDBClassFactory,
       public DefaultTransactionalLevelDBFactory,
       public storage::mojom::MockFailureInjector {
diff --git a/content/browser/keyboard_lock/keyboard_lock_service_impl.h b/content/browser/keyboard_lock/keyboard_lock_service_impl.h
index 84145cba..2241a7f7 100644
--- a/content/browser/keyboard_lock/keyboard_lock_service_impl.h
+++ b/content/browser/keyboard_lock/keyboard_lock_service_impl.h
@@ -9,7 +9,6 @@
 #include <vector>
 
 #include "content/browser/renderer_host/render_frame_host_impl.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/document_service.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "third_party/blink/public/mojom/keyboard_lock/keyboard_lock.mojom.h"
@@ -19,7 +18,7 @@
 class RenderFrameHost;
 class RenderFrameHostImpl;
 
-class CONTENT_EXPORT KeyboardLockServiceImpl final
+class KeyboardLockServiceImpl final
     : public DocumentService<blink::mojom::KeyboardLockService> {
  public:
   KeyboardLockServiceImpl(
diff --git a/content/browser/loader/merkle_integrity_source_stream.cc b/content/browser/loader/merkle_integrity_source_stream.cc
index 1061b2f..8a556c13 100644
--- a/content/browser/loader/merkle_integrity_source_stream.cc
+++ b/content/browser/loader/merkle_integrity_source_stream.cc
@@ -103,7 +103,8 @@
       return false;
     }
     uint64_t record_size;
-    base::ReadBigEndian(bytes.data(), &record_size);
+    base::ReadBigEndian(reinterpret_cast<const uint8_t*>(bytes.data()),
+                        &record_size);
     if (record_size == 0) {
       return false;
     }
diff --git a/content/browser/loader/prefetch_url_loader.h b/content/browser/loader/prefetch_url_loader.h
index 8b2907f4..887ae6c2 100644
--- a/content/browser/loader/prefetch_url_loader.h
+++ b/content/browser/loader/prefetch_url_loader.h
@@ -12,7 +12,6 @@
 #include "base/memory/weak_ptr.h"
 #include "base/unguessable_token.h"
 #include "content/browser/web_package/prefetched_signed_exchange_cache.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -41,9 +40,9 @@
 
 // A URLLoader for loading a prefetch request, including <link rel="prefetch">.
 // It basically just keeps draining the data.
-class CONTENT_EXPORT PrefetchURLLoader : public network::mojom::URLLoader,
-                                         public network::mojom::URLLoaderClient,
-                                         public mojo::DataPipeDrainer::Client {
+class PrefetchURLLoader : public network::mojom::URLLoader,
+                          public network::mojom::URLLoaderClient,
+                          public mojo::DataPipeDrainer::Client {
  public:
   using URLLoaderThrottlesGetter = base::RepeatingCallback<
       std::vector<std::unique_ptr<blink::URLLoaderThrottle>>()>;
diff --git a/content/browser/loader/prefetch_url_loader_service.h b/content/browser/loader/prefetch_url_loader_service.h
index d07826ea..0b7b857 100644
--- a/content/browser/loader/prefetch_url_loader_service.h
+++ b/content/browser/loader/prefetch_url_loader_service.h
@@ -10,7 +10,6 @@
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
 #include "content/browser/web_package/signed_exchange_prefetch_metric_recorder.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/browser_thread.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -33,7 +32,7 @@
 // A URLLoaderFactory that can be passed to a renderer to use for performing
 // prefetches. The renderer uses it for prefetch requests including <link
 // rel="prefetch">.
-class CONTENT_EXPORT PrefetchURLLoaderService final
+class PrefetchURLLoaderService final
     : public base::RefCountedThreadSafe<
           PrefetchURLLoaderService,
           content::BrowserThread::DeleteOnUIThread>,
diff --git a/content/browser/media/audio_muting_session.h b/content/browser/media/audio_muting_session.h
index 439938be..315e0bb3 100644
--- a/content/browser/media/audio_muting_session.h
+++ b/content/browser/media/audio_muting_session.h
@@ -6,13 +6,12 @@
 #define CONTENT_BROWSER_MEDIA_AUDIO_MUTING_SESSION_H_
 
 #include "base/unguessable_token.h"
-#include "content/common/content_export.h"
 #include "media/mojo/mojom/audio_stream_factory.mojom.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 
 namespace content {
 
-class CONTENT_EXPORT AudioMutingSession {
+class AudioMutingSession {
  public:
   explicit AudioMutingSession(const base::UnguessableToken& group_id);
 
diff --git a/content/browser/media/capture/aura_window_video_capture_device.cc b/content/browser/media/capture/aura_window_video_capture_device.cc
index 90fc150..3ff1aea 100644
--- a/content/browser/media/capture/aura_window_video_capture_device.cc
+++ b/content/browser/media/capture/aura_window_video_capture_device.cc
@@ -81,17 +81,19 @@
     DCHECK(!target_window_);
 
     target_window_ = DesktopMediaID::GetNativeWindowById(source_id);
-    FrameSinkVideoCaptureDevice::VideoCaptureTarget target;
-    if (target_window_) {
-      target.frame_sink_id = target_window_->GetRootWindow()->GetFrameSinkId();
+    if (target_window_ &&
+        target_window_->GetRootWindow()->GetFrameSinkId().is_valid()) {
+      target_ = viz::VideoCaptureTarget(
+          target_window_->GetRootWindow()->GetFrameSinkId());
       if (!target_window_->IsRootWindow()) {
         capture_request_ = target_window_->MakeWindowCapturable();
-        target.subtree_capture_id = capture_request_.GetCaptureId();
+        target_->sub_target = capture_request_.GetCaptureId();
       }
+    } else {
+      target_ = absl::nullopt;
     }
 
-    if (target.frame_sink_id.is_valid()) {
-      target_ = target;
+    if (target_) {
       video_capture_lock_ = target_window_->GetHost()->CreateVideoCaptureLock();
 #if BUILDFLAG(IS_CHROMEOS_ASH)
       force_visible_.emplace(target_window_);
@@ -100,7 +102,7 @@
       device_task_runner_->PostTask(
           FROM_HERE,
           base::BindOnce(&FrameSinkVideoCaptureDevice::OnTargetChanged, device_,
-                         std::move(target)));
+                         target_));
       // Note: The MouseCursorOverlayController runs on the UI thread. It's also
       // important that SetTargetView() be called in the current stack while
       // |target_window_| is known to be a valid pointer.
@@ -144,12 +146,13 @@
 
     // Since the window is not destroyed, only re-parented, we can keep the
     // same subtree ID and only update the FrameSinkId of the target.
-    if (new_frame_sink_id != target_.frame_sink_id) {
-      target_.frame_sink_id = new_frame_sink_id;
+    DCHECK(target_);
+    if (new_frame_sink_id != target_->frame_sink_id) {
+      target_->frame_sink_id = new_frame_sink_id;
       device_task_runner_->PostTask(
           FROM_HERE,
           base::BindOnce(&FrameSinkVideoCaptureDevice::OnTargetChanged, device_,
-                         target_));
+                         target_.value()));
     }
   }
 #endif
@@ -173,7 +176,7 @@
 #endif
 
   aura::ScopedWindowCaptureRequest capture_request_;
-  FrameSinkVideoCaptureDevice::VideoCaptureTarget target_;
+  absl::optional<viz::VideoCaptureTarget> target_;
 
   std::unique_ptr<aura::WindowTreeHost::VideoCaptureLock> video_capture_lock_;
 };
diff --git a/content/browser/media/capture/desktop_streams_registry_impl.h b/content/browser/media/capture/desktop_streams_registry_impl.h
index 736f964..5ced17b 100644
--- a/content/browser/media/capture/desktop_streams_registry_impl.h
+++ b/content/browser/media/capture/desktop_streams_registry_impl.h
@@ -8,15 +8,13 @@
 #include <map>
 #include <string>
 
-#include "content/common/content_export.h"
 #include "content/public/browser/desktop_media_id.h"
 #include "content/public/browser/desktop_streams_registry.h"
 #include "url/origin.h"
 
 namespace content {
 
-class CONTENT_EXPORT DesktopStreamsRegistryImpl
-    : public DesktopStreamsRegistry {
+class DesktopStreamsRegistryImpl : public DesktopStreamsRegistry {
  public:
   DesktopStreamsRegistryImpl();
 
diff --git a/content/browser/media/capture/frame_sink_video_capture_device.cc b/content/browser/media/capture/frame_sink_video_capture_device.cc
index 5287252f8a..9dbbfad4 100644
--- a/content/browser/media/capture/frame_sink_video_capture_device.cc
+++ b/content/browser/media/capture/frame_sink_video_capture_device.cc
@@ -65,23 +65,6 @@
   GetDeviceService().BindWakeLockProvider(std::move(receiver));
 }
 
-viz::mojom::SubTargetPtr ToSubTargetPtr(
-    const FrameSinkVideoCaptureDevice::VideoCaptureTarget& target) {
-  // Recall that either |subtree_capture_id| or |crop_id| is set,
-  // or neither, but never both. This was verified in |target|'s ctor,
-  // but is reiterated here for clarity's sake.
-  DCHECK(!target.subtree_capture_id.is_valid() || target.crop_id.is_zero());
-
-  if (target.subtree_capture_id.is_valid()) {
-    return viz::mojom::SubTarget::NewSubtreeCaptureId(
-        target.subtree_capture_id);
-  }
-  if (!target.crop_id.is_zero()) {
-    return viz::mojom::SubTarget::NewRegionCaptureCropId(target.crop_id);
-  }
-  return nullptr;
-}
-
 }  // namespace
 
 #if !defined(OS_ANDROID)
@@ -137,8 +120,8 @@
                                       constraints.max_frame_size,
                                       constraints.fixed_aspect_ratio);
 
-  if (target_.frame_sink_id.is_valid()) {
-    capturer_->ChangeTarget(target_.frame_sink_id, ToSubTargetPtr(target_));
+  if (target_) {
+    capturer_->ChangeTarget(target_);
   }
 
 #if !defined(OS_ANDROID)
@@ -325,22 +308,17 @@
 }
 
 void FrameSinkVideoCaptureDevice::OnTargetChanged(
-    const FrameSinkVideoCaptureDevice::VideoCaptureTarget& target) {
+    const absl::optional<viz::VideoCaptureTarget>& target) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   target_ = target;
   if (capturer_) {
-    capturer_->ChangeTarget(
-        target_.frame_sink_id.is_valid()
-            ? absl::make_optional<viz::FrameSinkId>(target_.frame_sink_id)
-            : absl::nullopt,
-        ToSubTargetPtr(target_));
+    capturer_->ChangeTarget(target_);
   }
 }
 
 void FrameSinkVideoCaptureDevice::OnTargetPermanentlyLost() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-
-  OnTargetChanged(VideoCaptureTarget());
+  OnTargetChanged(absl::nullopt);
   OnFatalError("Capture target has been permanently lost.");
 }
 
diff --git a/content/browser/media/capture/frame_sink_video_capture_device.h b/content/browser/media/capture/frame_sink_video_capture_device.h
index 5fd266be..c99d784 100644
--- a/content/browser/media/capture/frame_sink_video_capture_device.h
+++ b/content/browser/media/capture/frame_sink_video_capture_device.h
@@ -14,9 +14,9 @@
 #include "base/check.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
-#include "base/token.h"
 #include "build/build_config.h"
 #include "components/viz/common/surfaces/frame_sink_id.h"
+#include "components/viz/common/surfaces/video_capture_target.h"
 #include "components/viz/host/client_frame_sink_video_capturer.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/browser_thread.h"
@@ -29,6 +29,7 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/device/public/mojom/wake_lock.mojom.h"
+#include "services/viz/public/cpp/compositing/video_capture_target_mojom_traits.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace content {
@@ -97,48 +98,11 @@
   void OnStopped() final;
   void OnLog(const std::string& message) final;
 
-  // All of the information necessary to select a target for capture.
-  struct VideoCaptureTarget {
-    VideoCaptureTarget() = default;
-    VideoCaptureTarget(viz::FrameSinkId frame_sink_id,
-                       viz::SubtreeCaptureId subtree_capture_id,
-                       const base::Token& crop_id)
-        : frame_sink_id(frame_sink_id),
-          subtree_capture_id(subtree_capture_id),
-          crop_id(crop_id) {
-      // Subtree-capture and region-capture are mutually exclusive.
-      // This is trivially guaranteed by subtree-capture only being supported
-      // on Aura window-capture, and region-capture only being supported on
-      // tab-capture.
-      DCHECK(!subtree_capture_id.is_valid() || crop_id.is_zero());
-    }
-
-    // The target frame sink id.
-    viz::FrameSinkId frame_sink_id;
-
-    // The subtree capture identifier--may be default initialized to indicate
-    // that the entire frame sink (defined by |frame_sink_id|) should be
-    // captured.
-    viz::SubtreeCaptureId subtree_capture_id;
-
-    // If |crop_id| is non-zero, it indicates that the video should be
-    // cropped to coordinates identified by it.
-    base::Token crop_id;
-
-    inline bool operator==(const VideoCaptureTarget& other) const {
-      return frame_sink_id == other.frame_sink_id &&
-             subtree_capture_id == other.subtree_capture_id &&
-             crop_id == other.crop_id;
-    }
-
-    inline bool operator!=(const VideoCaptureTarget& other) const {
-      return !(*this == other);
-    }
-  };
-
   // These are called to notify when the capture target has changed or was
-  // permanently lost.
-  virtual void OnTargetChanged(const VideoCaptureTarget& target);
+  // permanently lost. NOTE: a target can be temporarily absl::nullopt without
+  // being permanently lost.
+  virtual void OnTargetChanged(
+      const absl::optional<viz::VideoCaptureTarget>& target);
   virtual void OnTargetPermanentlyLost();
 
  protected:
@@ -188,7 +152,7 @@
   // Current capture target. This is cached to resolve a race where
   // OnTargetChanged() can be called before the |capturer_| is created in
   // OnCapturerCreated().
-  VideoCaptureTarget target_;
+  absl::optional<viz::VideoCaptureTarget> target_;
 
   // The requested format, rate, and other capture constraints.
   media::VideoCaptureParams capture_params_;
diff --git a/content/browser/media/capture/frame_sink_video_capture_device_unittest.cc b/content/browser/media/capture/frame_sink_video_capture_device_unittest.cc
index 6c1c06b..6f2d2fb 100644
--- a/content/browser/media/capture/frame_sink_video_capture_device_unittest.cc
+++ b/content/browser/media/capture/frame_sink_video_capture_device_unittest.cc
@@ -110,20 +110,13 @@
                     const gfx::Size& max_size,
                     bool use_fixed_aspect_ratio));
   MOCK_METHOD1(SetAutoThrottlingEnabled, void(bool));
-  void ChangeTarget(const absl::optional<viz::FrameSinkId>& frame_sink_id,
-                    viz::mojom::SubTargetPtr sub_target) final {
+  void ChangeTarget(
+      const absl::optional<viz::VideoCaptureTarget>& target) final {
     DCHECK_NOT_ON_DEVICE_THREAD();
-    viz::SubtreeCaptureId subtree_id;
-    if (sub_target && sub_target->is_subtree_capture_id()) {
-      subtree_id = sub_target->get_subtree_capture_id();
-    }
-    MockChangeTarget(FrameSinkVideoCaptureDevice::VideoCaptureTarget(
-        frame_sink_id.value_or(viz::FrameSinkId{}), subtree_id,
-        /*crop_id=*/base::Token()));
+    MockChangeTarget(target);
   }
-  MOCK_METHOD1(
-      MockChangeTarget,
-      void(const FrameSinkVideoCaptureDevice::VideoCaptureTarget& target));
+  MOCK_METHOD1(MockChangeTarget,
+               void(const absl::optional<viz::VideoCaptureTarget>& target));
   void Start(
       mojo::PendingRemote<viz::mojom::FrameSinkVideoConsumer> consumer) final {
     DCHECK_NOT_ON_DEVICE_THREAD();
@@ -351,10 +344,10 @@
     EXPECT_CALL(capturer_, SetMinCapturePeriod(kMinCapturePeriod));
     EXPECT_CALL(capturer_,
                 SetResolutionConstraints(kResolution, kResolution, _));
-    FrameSinkVideoCaptureDevice::VideoCaptureTarget target(
-        viz::FrameSinkId{1, 1}, viz::SubtreeCaptureId(),
-        /*crop_id=*/base::Token());
-    EXPECT_CALL(capturer_, MockChangeTarget(target));
+    const viz::VideoCaptureTarget target(viz::FrameSinkId{1, 1});
+    EXPECT_CALL(
+        capturer_,
+        MockChangeTarget(absl::optional<viz::VideoCaptureTarget>(target)));
     EXPECT_CALL(capturer_, MockStart(NotNull()));
 
     EXPECT_FALSE(capturer_.is_bound());
@@ -602,9 +595,8 @@
   // consumption, unbind the capturer, log an error with the VideoFrameReceiver,
   // and destroy the VideoFrameReceiver.
   {
-    EXPECT_CALL(
-        capturer_,
-        MockChangeTarget(FrameSinkVideoCaptureDevice::VideoCaptureTarget()));
+    EXPECT_CALL(capturer_,
+                MockChangeTarget(absl::optional<viz::VideoCaptureTarget>()));
     EXPECT_CALL(capturer_, MockStop());
     POST_DEVICE_METHOD_CALL0(OnTargetPermanentlyLost);
     WAIT_FOR_DEVICE_TASKS();
diff --git a/content/browser/media/capture/views_widget_video_capture_device_mac.cc b/content/browser/media/capture/views_widget_video_capture_device_mac.cc
index 8c2ce59..e1747db2 100644
--- a/content/browser/media/capture/views_widget_video_capture_device_mac.cc
+++ b/content/browser/media/capture/views_widget_video_capture_device_mac.cc
@@ -55,9 +55,7 @@
           FROM_HERE,
           base::BindOnce(
               &FrameSinkVideoCaptureDevice::OnTargetChanged, device_,
-              FrameSinkVideoCaptureDevice::VideoCaptureTarget(
-                  scoped_cg_window_id_->GetFrameSinkId(),
-                  viz::SubtreeCaptureId(), /*crop_id=*/base::Token())));
+              viz::VideoCaptureTarget(scoped_cg_window_id_->GetFrameSinkId())));
     } else {
       // It is entirely possible (although unlikely) that the window
       // corresponding to |cg_window_id| be destroyed between when the capture
diff --git a/content/browser/media/capture/web_contents_frame_tracker.cc b/content/browser/media/capture/web_contents_frame_tracker.cc
index 24b9c01..e4ed168 100644
--- a/content/browser/media/capture/web_contents_frame_tracker.cc
+++ b/content/browser/media/capture/web_contents_frame_tracker.cc
@@ -212,14 +212,16 @@
 
   crop_id_ = crop_id;
 
-  const FrameSinkVideoCaptureDevice::VideoCaptureTarget target(
-      target_frame_sink_id_.value_or(viz::FrameSinkId()),
-      viz::SubtreeCaptureId(), crop_id_);
+  // If we don't have a target yet, we can store the crop ID but cannot actually
+  // crop yet.
+  if (!target_frame_sink_id_.is_valid())
+    return;
 
+  const viz::VideoCaptureTarget target(target_frame_sink_id_, crop_id_);
   device_task_runner_->PostTask(
       FROM_HERE,
       base::BindOnce(
-          [](const FrameSinkVideoCaptureDevice::VideoCaptureTarget& target,
+          [](const viz::VideoCaptureTarget& target,
              base::OnceCallback<void(media::mojom::CropRequestResult)> callback,
              base::WeakPtr<WebContentsVideoCaptureDevice> device) {
             if (!device) {
@@ -252,20 +254,26 @@
     return;
   }
 
-  // TODO(crbug.com/1247761): Clear |crop_id_| when share-this-tab-instead
-  // is clicked.
-
   viz::FrameSinkId frame_sink_id;
   if (context_) {
     frame_sink_id = context_->GetFrameSinkIdForCapture();
   }
+
+  // TODO(crbug.com/1247761): Clear |crop_id_| when share-this-tab-instead
+  // is clicked.
   if (frame_sink_id != target_frame_sink_id_) {
     target_frame_sink_id_ = frame_sink_id;
+    absl::optional<viz::VideoCaptureTarget> target;
+    if (frame_sink_id.is_valid()) {
+      target = viz::VideoCaptureTarget(frame_sink_id, crop_id_);
+    }
+
+    // The target may change to an invalid one, but we don't consider it
+    // permanently lost here yet.
     device_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(&WebContentsVideoCaptureDevice::OnTargetChanged, device_,
-                       FrameSinkVideoCaptureDevice::VideoCaptureTarget(
-                           frame_sink_id, viz::SubtreeCaptureId(), crop_id_)));
+                       std::move(target)));
   }
 
   SetTargetView(web_contents()->GetNativeView());
diff --git a/content/browser/media/capture/web_contents_frame_tracker.h b/content/browser/media/capture/web_contents_frame_tracker.h
index 96c9e73..67a3884 100644
--- a/content/browser/media/capture/web_contents_frame_tracker.h
+++ b/content/browser/media/capture/web_contents_frame_tracker.h
@@ -129,7 +129,7 @@
 
   // We may not have a frame sink ID target at all times.
   std::unique_ptr<Context> context_;
-  absl::optional<viz::FrameSinkId> target_frame_sink_id_;
+  viz::FrameSinkId target_frame_sink_id_;
   base::Token crop_id_;
   gfx::NativeView target_native_view_ = gfx::NativeView();
 
diff --git a/content/browser/media/capture/web_contents_frame_tracker_unittest.cc b/content/browser/media/capture/web_contents_frame_tracker_unittest.cc
index d58b1bb..41a5fab0 100644
--- a/content/browser/media/capture/web_contents_frame_tracker_unittest.cc
+++ b/content/browser/media/capture/web_contents_frame_tracker_unittest.cc
@@ -74,7 +74,7 @@
  public:
   using WebContentsVideoCaptureDevice::AsWeakPtr;
   MOCK_METHOD1(OnTargetChanged,
-               void(const FrameSinkVideoCaptureDevice::VideoCaptureTarget&));
+               void(const absl::optional<viz::VideoCaptureTarget>&));
   MOCK_METHOD0(OnTargetPermanentlyLost, void());
 };
 
@@ -315,11 +315,12 @@
 // test the observer callbacks here.
 TEST_F(WebContentsFrameTrackerTest, NotifiesOfTargetChanges) {
   const viz::FrameSinkId kNewId(42, 1337);
-  EXPECT_CALL(*device(),
-              OnTargetChanged(FrameSinkVideoCaptureDevice::VideoCaptureTarget(
-                  kNewId, viz::SubtreeCaptureId(), /*crop_id=*/base::Token())))
-      .Times(1);
   SetFrameSinkId(kNewId);
+  EXPECT_CALL(
+      *device(),
+      OnTargetChanged(absl::make_optional<viz::VideoCaptureTarget>(kNewId)))
+      .Times(1);
+
   // The tracker doesn't actually use the frame host information, just
   // posts a possible target change.
   tracker()->RenderFrameHostChanged(nullptr, nullptr);
@@ -341,8 +342,8 @@
 
   // Expect OnTargetChanged() to be invoked once with the crop-ID.
   EXPECT_CALL(*device(),
-              OnTargetChanged(FrameSinkVideoCaptureDevice::VideoCaptureTarget(
-                  kInitSinkId, viz::SubtreeCaptureId(), kCropId)))
+              OnTargetChanged(absl::make_optional<viz::VideoCaptureTarget>(
+                  kInitSinkId, kCropId)))
       .Times(1);
 
   tracker()->Crop(kCropId, std::move(callback));
diff --git a/content/browser/media/frameless_media_interface_proxy.h b/content/browser/media/frameless_media_interface_proxy.h
index 4759efada..e3449bae 100644
--- a/content/browser/media/frameless_media_interface_proxy.h
+++ b/content/browser/media/frameless_media_interface_proxy.h
@@ -10,7 +10,6 @@
 #include "base/threading/thread_checker.h"
 #include "base/unguessable_token.h"
 #include "build/build_config.h"
-#include "content/common/content_export.h"
 #include "media/media_buildflags.h"
 #include "media/mojo/buildflags.h"
 #include "media/mojo/mojom/interface_factory.mojom.h"
@@ -28,7 +27,7 @@
 // It is used in cases without a frame context, e.g. WebRTC's
 // RTCVideoDecoderFactory to create hardware video decoders using
 // MojoVideoDecoder, and WebCodecs audio/video decoding in workers.
-class CONTENT_EXPORT FramelessMediaInterfaceProxy final
+class FramelessMediaInterfaceProxy final
     : public media::mojom::InterfaceFactory {
  public:
   FramelessMediaInterfaceProxy();
diff --git a/content/browser/media/media_keys_listener_manager_impl.h b/content/browser/media/media_keys_listener_manager_impl.h
index 7e43e63..1eade5ec 100644
--- a/content/browser/media/media_keys_listener_manager_impl.h
+++ b/content/browser/media/media_keys_listener_manager_impl.h
@@ -12,7 +12,6 @@
 #include "base/observer_list.h"
 #include "build/build_config.h"
 #include "components/system_media_controls/system_media_controls_observer.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/media_keys_listener_manager.h"
 #include "ui/base/accelerators/media_keys_listener.h"
 #include "ui/events/keycodes/keyboard_codes.h"
@@ -30,7 +29,7 @@
 // particular, it owns one of its delegates (ActiveMediaSessionController), and
 // only propagates to the ActiveMediaSessionController if no other delegates are
 // listening to a particular media key.
-class CONTENT_EXPORT MediaKeysListenerManagerImpl
+class MediaKeysListenerManagerImpl
     : public MediaKeysListenerManager,
       public ui::MediaKeysListener::Delegate,
       public system_media_controls::SystemMediaControlsObserver {
diff --git a/content/browser/media/media_stream_web_contents_observer.h b/content/browser/media/media_stream_web_contents_observer.h
index 45a4bcb8..a7601a3 100644
--- a/content/browser/media/media_stream_web_contents_observer.h
+++ b/content/browser/media/media_stream_web_contents_observer.h
@@ -5,14 +5,12 @@
 #ifndef CONTENT_BROWSER_MEDIA_MEDIA_STREAM_WEB_CONTENTS_OBSERVER_H_
 #define CONTENT_BROWSER_MEDIA_MEDIA_STREAM_WEB_CONTENTS_OBSERVER_H_
 
-#include "content/common/content_export.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 
 namespace content {
 
-class CONTENT_EXPORT MediaStreamWebContentsObserver final
-    : public WebContentsObserver {
+class MediaStreamWebContentsObserver final : public WebContentsObserver {
  public:
   MediaStreamWebContentsObserver(WebContents* web_contents,
                                  base::RepeatingClosure focus_callback);
diff --git a/content/browser/native_io/native_io_context_impl.h b/content/browser/native_io/native_io_context_impl.h
index 10ee58f..8422227 100644
--- a/content/browser/native_io/native_io_context_impl.h
+++ b/content/browser/native_io/native_io_context_impl.h
@@ -8,7 +8,6 @@
 #include "base/files/file_path.h"
 #include "base/thread_annotations.h"
 #include "content/browser/native_io/native_io_host.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/native_io_context.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -26,7 +25,7 @@
 
 // Helper object on the UI thread whose sole responsibility is to maintain a
 // NativeIOManager on the IO thread, where it can be called by the QuotaClient.
-class CONTENT_EXPORT NativeIOContextImpl : public NativeIOContext {
+class NativeIOContextImpl : public NativeIOContext {
  public:
   // Creates an empty NativeIOContextImpl shell.
   //
diff --git a/content/browser/native_io/native_io_quota_client.h b/content/browser/native_io/native_io_quota_client.h
index 3afa4e9b..7349ff7 100644
--- a/content/browser/native_io/native_io_quota_client.h
+++ b/content/browser/native_io/native_io_quota_client.h
@@ -8,7 +8,6 @@
 #include "base/sequence_checker.h"
 #include "base/thread_annotations.h"
 #include "components/services/storage/public/cpp/storage_key_quota_client.h"
-#include "content/common/content_export.h"
 #include "storage/browser/quota/quota_client_type.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
 
@@ -23,8 +22,7 @@
 // Integrates NativeIO with the quota system.
 //
 // Each NativeIOManager owns exactly one NativeIOQuotaClient.
-class CONTENT_EXPORT NativeIOQuotaClient
-    : public storage::StorageKeyQuotaClient {
+class NativeIOQuotaClient : public storage::StorageKeyQuotaClient {
  public:
   explicit NativeIOQuotaClient(NativeIOManager* manager);
   ~NativeIOQuotaClient() override;
diff --git a/content/browser/network_service_client.h b/content/browser/network_service_client.h
index 2bea73e..5390a050 100644
--- a/content/browser/network_service_client.h
+++ b/content/browser/network_service_client.h
@@ -12,7 +12,6 @@
 #include "base/memory/memory_pressure_listener.h"
 #include "base/unguessable_token.h"
 #include "build/build_config.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
@@ -31,7 +30,7 @@
 
 class WebRtcConnectionsObserver;
 
-class CONTENT_EXPORT NetworkServiceClient
+class NetworkServiceClient
     : public network::mojom::URLLoaderNetworkServiceObserver,
 #if defined(OS_ANDROID)
       public net::NetworkChangeNotifier::ConnectionTypeObserver,
diff --git a/content/browser/notifications/platform_notification_service_proxy.h b/content/browser/notifications/platform_notification_service_proxy.h
index d24e5ddc..c71506a 100644
--- a/content/browser/notifications/platform_notification_service_proxy.h
+++ b/content/browser/notifications/platform_notification_service_proxy.h
@@ -12,7 +12,6 @@
 #include "base/callback_forward.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/weak_ptr.h"
-#include "content/common/content_export.h"
 
 class GURL;
 
@@ -32,7 +31,7 @@
 class ServiceWorkerContextWrapper;
 class ServiceWorkerRegistration;
 
-class CONTENT_EXPORT PlatformNotificationServiceProxy {
+class PlatformNotificationServiceProxy {
  public:
   using DisplayResultCallback =
       base::OnceCallback<void(bool /* success */,
diff --git a/content/browser/payments/payment_app_database.h b/content/browser/payments/payment_app_database.h
index 19dfd78e..a16b1b9 100644
--- a/content/browser/payments/payment_app_database.h
+++ b/content/browser/payments/payment_app_database.h
@@ -14,7 +14,6 @@
 #include "content/browser/payments/payment_instrument_icon_fetcher.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
 #include "content/browser/service_worker/service_worker_registration.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/stored_payment_app.h"
 #include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
 #include "third_party/blink/public/mojom/payments/payment_app.mojom.h"
@@ -23,7 +22,7 @@
 
 class ServiceWorkerRegistration;
 
-class CONTENT_EXPORT PaymentAppDatabase {
+class PaymentAppDatabase {
  public:
   using PaymentApps = std::map<int64_t, std::unique_ptr<StoredPaymentApp>>;
   using ReadAllPaymentAppsCallback = base::OnceCallback<void(PaymentApps)>;
diff --git a/content/browser/permissions/permission_service_context.h b/content/browser/permissions/permission_service_context.h
index 40e296b..01077db 100644
--- a/content/browser/permissions/permission_service_context.h
+++ b/content/browser/permissions/permission_service_context.h
@@ -8,7 +8,6 @@
 #include <memory>
 #include <unordered_map>
 
-#include "content/common/content_export.h"
 #include "content/public/browser/document_user_data.h"
 #include "content/public/browser/permission_controller.h"
 #include "content/public/browser/permission_type.h"
@@ -38,8 +37,7 @@
 // PermissionServiceContext instances associated with a RenderFrameHost must be
 // created via the DocumentUserData static factories, as these
 // instances are deleted when a new document is commited.
-class CONTENT_EXPORT PermissionServiceContext
-    : public RenderProcessHostObserver {
+class PermissionServiceContext : public RenderProcessHostObserver {
  public:
   explicit PermissionServiceContext(RenderProcessHost* render_process_host);
   PermissionServiceContext(const PermissionServiceContext&) = delete;
diff --git a/content/browser/permissions/permission_service_impl.h b/content/browser/permissions/permission_service_impl.h
index 3e95998..5eddd096 100644
--- a/content/browser/permissions/permission_service_impl.h
+++ b/content/browser/permissions/permission_service_impl.h
@@ -9,7 +9,6 @@
 #include "base/containers/id_map.h"
 #include "base/memory/weak_ptr.h"
 #include "content/browser/permissions/permission_service_context.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "third_party/blink/public/mojom/permissions/permission.mojom.h"
 #include "url/origin.h"
@@ -25,8 +24,7 @@
 // to have some information about the current context. That enables the service
 // to know whether it can show UI and have knowledge of the associated
 // WebContents for example.
-class CONTENT_EXPORT PermissionServiceImpl
-    : public blink::mojom::PermissionService {
+class PermissionServiceImpl : public blink::mojom::PermissionService {
  public:
   PermissionServiceImpl(PermissionServiceContext* context,
                         const url::Origin& origin);
diff --git a/content/browser/portal/portal_navigation_throttle.h b/content/browser/portal/portal_navigation_throttle.h
index 27c00113..5d5f00c 100644
--- a/content/browser/portal/portal_navigation_throttle.h
+++ b/content/browser/portal/portal_navigation_throttle.h
@@ -8,7 +8,6 @@
 #include <memory>
 
 #include "base/auto_reset.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/navigation_throttle.h"
 
 namespace content {
@@ -19,7 +18,7 @@
 // portal main frame to only the origin of its host. This allows a more limited
 // testing mode of the portals feature, in which third-party (cross-origin)
 // content cannot be loaded.
-class CONTENT_EXPORT PortalNavigationThrottle : public NavigationThrottle {
+class PortalNavigationThrottle : public NavigationThrottle {
  public:
   static std::unique_ptr<PortalNavigationThrottle> MaybeCreateThrottleFor(
       NavigationHandle* navigation_handle);
diff --git a/content/browser/renderer_host/cross_origin_opener_policy_access_report_manager.h b/content/browser/renderer_host/cross_origin_opener_policy_access_report_manager.h
index bb78429..a0b825e 100644
--- a/content/browser/renderer_host/cross_origin_opener_policy_access_report_manager.h
+++ b/content/browser/renderer_host/cross_origin_opener_policy_access_report_manager.h
@@ -9,7 +9,6 @@
 
 #include "base/values.h"
 #include "content/browser/net/cross_origin_opener_policy_reporter.h"
-#include "content/common/content_export.h"
 
 namespace content {
 
@@ -18,7 +17,7 @@
 // Used to monitor (potential) COOP breakages.
 // A CrossOriginOpenerPolicyAccessReportManager lives in the browser process and
 // has a 1:1 relationship with a RenderFrameHost.
-class CONTENT_EXPORT CrossOriginOpenerPolicyAccessReportManager {
+class CrossOriginOpenerPolicyAccessReportManager {
  public:
   CrossOriginOpenerPolicyAccessReportManager();
   ~CrossOriginOpenerPolicyAccessReportManager();
diff --git a/content/browser/renderer_host/embedded_frame_sink_impl.h b/content/browser/renderer_host/embedded_frame_sink_impl.h
index 73f9456..6092df6 100644
--- a/content/browser/renderer_host/embedded_frame_sink_impl.h
+++ b/content/browser/renderer_host/embedded_frame_sink_impl.h
@@ -12,7 +12,6 @@
 #include "components/viz/common/surfaces/frame_sink_id.h"
 #include "components/viz/common/surfaces/surface_info.h"
 #include "components/viz/host/host_frame_sink_client.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -29,7 +28,7 @@
 // Both the embedder and embedded frame sink are in the same renderer. Holds a
 // client connection to the renderer that is notified when a new SurfaceId
 // activates for the embedded frame sink.
-class CONTENT_EXPORT EmbeddedFrameSinkImpl : public viz::HostFrameSinkClient {
+class EmbeddedFrameSinkImpl : public viz::HostFrameSinkClient {
  public:
   using DestroyCallback = base::OnceCallback<void()>;
 
diff --git a/content/browser/renderer_host/input/input_device_change_observer.h b/content/browser/renderer_host/input/input_device_change_observer.h
index b158d9dd..f9c672c 100644
--- a/content/browser/renderer_host/input/input_device_change_observer.h
+++ b/content/browser/renderer_host/input/input_device_change_observer.h
@@ -5,7 +5,6 @@
 #ifndef CONTENT_BROWSER_RENDERER_HOST_INPUT_INPUT_DEVICE_CHANGE_OBSERVER_H_
 #define CONTENT_BROWSER_RENDERER_HOST_INPUT_INPUT_DEVICE_CHANGE_OBSERVER_H_
 
-#include "content/common/content_export.h"
 #include "ui/events/devices/input_device_event_observer.h"
 
 namespace content {
@@ -17,8 +16,7 @@
 // and it gets notified whenever the input capabilities change. Whenever
 // a change is detected the WebKit preferences are getting updated so the
 // interactions media-queries can be updated.
-class CONTENT_EXPORT InputDeviceChangeObserver
-    : public ui::InputDeviceEventObserver {
+class InputDeviceChangeObserver : public ui::InputDeviceEventObserver {
  public:
   InputDeviceChangeObserver(RenderViewHostImpl* rvh);
 
diff --git a/content/browser/renderer_host/input/input_disposition_handler.h b/content/browser/renderer_host/input/input_disposition_handler.h
index 8695c334..002a37b 100644
--- a/content/browser/renderer_host/input/input_disposition_handler.h
+++ b/content/browser/renderer_host/input/input_disposition_handler.h
@@ -6,7 +6,6 @@
 #define CONTENT_BROWSER_RENDERER_HOST_INPUT_INPUT_DISPOSITION_HANDLER_H_
 
 #include "content/browser/renderer_host/event_with_latency_info.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/native_web_keyboard_event.h"
 #include "third_party/blink/public/common/input/web_input_event.h"
 #include "third_party/blink/public/mojom/input/input_event_result.mojom-shared.h"
@@ -14,7 +13,7 @@
 namespace content {
 
 // Provided customized disposition response for input events.
-class CONTENT_EXPORT InputDispositionHandler {
+class InputDispositionHandler {
  public:
   virtual ~InputDispositionHandler() {}
 
diff --git a/content/browser/renderer_host/input/input_injector_impl.h b/content/browser/renderer_host/input/input_injector_impl.h
index 8ae7d45b..9f29a4c 100644
--- a/content/browser/renderer_host/input/input_injector_impl.h
+++ b/content/browser/renderer_host/input/input_injector_impl.h
@@ -9,14 +9,13 @@
 
 #include "content/browser/renderer_host/input/synthetic_gesture.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
-#include "content/common/content_export.h"
 #include "content/common/input/input_injector.mojom.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 
 namespace content {
 
 // An implementation of InputInjector.
-class CONTENT_EXPORT InputInjectorImpl : public mojom::InputInjector {
+class InputInjectorImpl : public mojom::InputInjector {
  public:
   explicit InputInjectorImpl(base::WeakPtr<RenderFrameHostImpl> frame_host);
 
diff --git a/content/browser/renderer_host/input/input_router_client.h b/content/browser/renderer_host/input/input_router_client.h
index 778852a4..7a43b5b8 100644
--- a/content/browser/renderer_host/input/input_router_client.h
+++ b/content/browser/renderer_host/input/input_router_client.h
@@ -7,7 +7,6 @@
 
 #include "cc/input/touch_action.h"
 #include "content/browser/renderer_host/event_with_latency_info.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/native_web_keyboard_event.h"
 #include "third_party/blink/public/common/input/web_input_event.h"
 #include "third_party/blink/public/mojom/input/input_event_result.mojom-shared.h"
@@ -20,7 +19,7 @@
 
 namespace content {
 
-class CONTENT_EXPORT InputRouterClient {
+class InputRouterClient {
  public:
   virtual ~InputRouterClient() {}
 
diff --git a/content/browser/renderer_host/input/mouse_wheel_phase_handler.h b/content/browser/renderer_host/input/mouse_wheel_phase_handler.h
index c9e905a..aacc741 100644
--- a/content/browser/renderer_host/input/mouse_wheel_phase_handler.h
+++ b/content/browser/renderer_host/input/mouse_wheel_phase_handler.h
@@ -7,7 +7,6 @@
 
 #include "base/timer/timer.h"
 #include "content/browser/renderer_host/render_widget_host_delegate.h"
-#include "content/common/content_export.h"
 #include "third_party/blink/public/common/input/web_mouse_wheel_event.h"
 #include "third_party/blink/public/mojom/input/input_event_result.mojom-shared.h"
 
@@ -50,7 +49,7 @@
 // The MouseWheelPhaseHandler is responsible for adding the proper phase to
 // wheel events. Phase information is necessary for wheel scrolling since it
 // shows the start and end of a scrolling sequence.
-class CONTENT_EXPORT MouseWheelPhaseHandler {
+class MouseWheelPhaseHandler {
  public:
   MouseWheelPhaseHandler(RenderWidgetHostViewBase* const host_view);
 
diff --git a/content/browser/renderer_host/input/synthetic_gesture_target.h b/content/browser/renderer_host/input/synthetic_gesture_target.h
index 3261157..64cbb1c 100644
--- a/content/browser/renderer_host/input/synthetic_gesture_target.h
+++ b/content/browser/renderer_host/input/synthetic_gesture_target.h
@@ -7,7 +7,6 @@
 
 #include "base/callback_forward.h"
 #include "base/time/time.h"
-#include "content/common/content_export.h"
 #include "content/common/input/synthetic_gesture_params.h"
 
 namespace blink {
@@ -17,7 +16,7 @@
 namespace content {
 
 // Interface between the synthetic gesture controller and the RenderWidgetHost.
-class CONTENT_EXPORT SyntheticGestureTarget {
+class SyntheticGestureTarget {
  public:
   SyntheticGestureTarget() {}
   virtual ~SyntheticGestureTarget() {}
diff --git a/content/browser/renderer_host/input/synthetic_mouse_driver.h b/content/browser/renderer_host/input/synthetic_mouse_driver.h
index 1b0c582..0337b754 100644
--- a/content/browser/renderer_host/input/synthetic_mouse_driver.h
+++ b/content/browser/renderer_host/input/synthetic_mouse_driver.h
@@ -6,11 +6,10 @@
 #define CONTENT_BROWSER_RENDERER_HOST_INPUT_SYNTHETIC_MOUSE_DRIVER_H_
 
 #include "content/browser/renderer_host/input/synthetic_pointer_driver.h"
-#include "content/common/content_export.h"
 
 namespace content {
 
-class CONTENT_EXPORT SyntheticMouseDriver : public SyntheticPointerDriver {
+class SyntheticMouseDriver : public SyntheticPointerDriver {
  public:
   SyntheticMouseDriver();
 
diff --git a/content/browser/renderer_host/input/synthetic_pen_driver.h b/content/browser/renderer_host/input/synthetic_pen_driver.h
index 202a73a03..6a8777a 100644
--- a/content/browser/renderer_host/input/synthetic_pen_driver.h
+++ b/content/browser/renderer_host/input/synthetic_pen_driver.h
@@ -6,11 +6,10 @@
 #define CONTENT_BROWSER_RENDERER_HOST_INPUT_SYNTHETIC_PEN_DRIVER_H_
 
 #include "content/browser/renderer_host/input/synthetic_mouse_driver.h"
-#include "content/common/content_export.h"
 
 namespace content {
 
-class CONTENT_EXPORT SyntheticPenDriver : public SyntheticMouseDriver {
+class SyntheticPenDriver : public SyntheticMouseDriver {
  public:
   SyntheticPenDriver();
 
diff --git a/content/browser/renderer_host/input/synthetic_touch_driver.h b/content/browser/renderer_host/input/synthetic_touch_driver.h
index b9c0e177..56291dc0 100644
--- a/content/browser/renderer_host/input/synthetic_touch_driver.h
+++ b/content/browser/renderer_host/input/synthetic_touch_driver.h
@@ -7,12 +7,11 @@
 
 #include <array>
 #include "content/browser/renderer_host/input/synthetic_pointer_driver.h"
-#include "content/common/content_export.h"
 #include "third_party/blink/public/common/input/synthetic_web_input_event_builders.h"
 
 namespace content {
 
-class CONTENT_EXPORT SyntheticTouchDriver : public SyntheticPointerDriver {
+class SyntheticTouchDriver : public SyntheticPointerDriver {
  public:
   SyntheticTouchDriver();
   explicit SyntheticTouchDriver(blink::SyntheticWebTouchEvent touch_event);
diff --git a/content/browser/renderer_host/input/touch_emulator_client.h b/content/browser/renderer_host/input/touch_emulator_client.h
index f6ddbab8..62239917 100644
--- a/content/browser/renderer_host/input/touch_emulator_client.h
+++ b/content/browser/renderer_host/input/touch_emulator_client.h
@@ -5,7 +5,6 @@
 #ifndef CONTENT_BROWSER_RENDERER_HOST_INPUT_TOUCH_EMULATOR_CLIENT_H_
 #define CONTENT_BROWSER_RENDERER_HOST_INPUT_TOUCH_EMULATOR_CLIENT_H_
 
-#include "content/common/content_export.h"
 #include "content/common/cursors/webcursor.h"
 #include "third_party/blink/public/common/input/web_gesture_event.h"
 #include "third_party/blink/public/common/input/web_touch_event.h"
@@ -16,7 +15,7 @@
 class RenderWidgetHostViewBase;
 
 // Emulates touch input with mouse and keyboard.
-class CONTENT_EXPORT TouchEmulatorClient {
+class TouchEmulatorClient {
  public:
   virtual ~TouchEmulatorClient() {}
 
diff --git a/content/browser/renderer_host/input/touch_selection_controller_client_child_frame.h b/content/browser/renderer_host/input/touch_selection_controller_client_child_frame.h
index 4dd9d2b..a5bf79ff 100644
--- a/content/browser/renderer_host/input/touch_selection_controller_client_child_frame.h
+++ b/content/browser/renderer_host/input/touch_selection_controller_client_child_frame.h
@@ -8,7 +8,6 @@
 #include <memory>
 
 #include "components/viz/common/quads/selection.h"
-#include "content/common/content_export.h"
 #include "ui/touch_selection/touch_selection_controller.h"
 #include "ui/touch_selection/touch_selection_menu_runner.h"
 
@@ -23,7 +22,7 @@
 // TouchSelectionControllerClient is intended to bind these views to the
 // TouchSelectionController, we need a different implementation for
 // cross-process subframes.
-class CONTENT_EXPORT TouchSelectionControllerClientChildFrame
+class TouchSelectionControllerClientChildFrame
     : public ui::TouchSelectionControllerClient,
       public ui::TouchSelectionMenuClient {
  public:
diff --git a/content/browser/renderer_host/media/audio_output_stream_observer_impl.h b/content/browser/renderer_host/media/audio_output_stream_observer_impl.h
index 73ad44f6..97d69b1d 100644
--- a/content/browser/renderer_host/media/audio_output_stream_observer_impl.h
+++ b/content/browser/renderer_host/media/audio_output_stream_observer_impl.h
@@ -5,12 +5,11 @@
 #ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_OUTPUT_STREAM_OBSERVER_IMPL_H_
 #define CONTENT_BROWSER_RENDERER_HOST_MEDIA_AUDIO_OUTPUT_STREAM_OBSERVER_IMPL_H_
 
-#include "content/common/content_export.h"
 #include "media/mojo/mojom/audio_output_stream.mojom.h"
 
 namespace content {
 
-class CONTENT_EXPORT AudioOutputStreamObserverImpl
+class AudioOutputStreamObserverImpl
     : public media::mojom::AudioOutputStreamObserver {
  public:
   AudioOutputStreamObserverImpl(int render_process_id,
diff --git a/content/browser/renderer_host/media/media_stream_provider.h b/content/browser/renderer_host/media/media_stream_provider.h
index 610e8a9b..1779a23 100644
--- a/content/browser/renderer_host/media/media_stream_provider.h
+++ b/content/browser/renderer_host/media/media_stream_provider.h
@@ -14,7 +14,6 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/unguessable_token.h"
-#include "content/common/content_export.h"
 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
 
 namespace content {
@@ -30,7 +29,7 @@
 };
 
 // Callback class used by MediaStreamProvider.
-class CONTENT_EXPORT MediaStreamProviderListener {
+class MediaStreamProviderListener {
  public:
   // Called by a MediaStreamProvider when a stream has been opened.
   virtual void Opened(blink::mojom::MediaStreamType stream_type,
@@ -50,7 +49,7 @@
 };
 
 // Implemented by a manager class providing captured media.
-class CONTENT_EXPORT MediaStreamProvider
+class MediaStreamProvider
     : public base::RefCountedThreadSafe<MediaStreamProvider> {
  public:
   // Registers a listener.
diff --git a/content/browser/renderer_host/media/video_capture_controller_event_handler.h b/content/browser/renderer_host/media/video_capture_controller_event_handler.h
index f772c6a..2effdf87 100644
--- a/content/browser/renderer_host/media/video_capture_controller_event_handler.h
+++ b/content/browser/renderer_host/media/video_capture_controller_event_handler.h
@@ -7,7 +7,6 @@
 
 #include <memory>
 
-#include "content/common/content_export.h"
 #include "media/capture/mojom/video_capture_buffer.mojom.h"
 #include "media/capture/mojom/video_capture_types.mojom.h"
 #include "mojo/public/cpp/system/buffer.h"
@@ -43,7 +42,7 @@
 // Other methods can be forwarded synchronously.
 
 // TODO(mcasas): https://crbug.com/654176 merge back into VideoCaptureController
-class CONTENT_EXPORT VideoCaptureControllerEventHandler {
+class VideoCaptureControllerEventHandler {
  public:
   // An Error has occurred in the VideoCaptureDevice.
   virtual void OnError(const VideoCaptureControllerID& id,
diff --git a/content/browser/renderer_host/media/video_capture_device_launch_observer.h b/content/browser/renderer_host/media/video_capture_device_launch_observer.h
index a0f4929..77443be 100644
--- a/content/browser/renderer_host/media/video_capture_device_launch_observer.h
+++ b/content/browser/renderer_host/media/video_capture_device_launch_observer.h
@@ -5,14 +5,13 @@
 #ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_DEVICE_LAUNCH_OBSERVER_H_
 #define CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_DEVICE_LAUNCH_OBSERVER_H_
 
-#include "content/common/content_export.h"
 #include "media/capture/video_capture_types.h"
 
 namespace content {
 
 class VideoCaptureController;
 
-class CONTENT_EXPORT VideoCaptureDeviceLaunchObserver {
+class VideoCaptureDeviceLaunchObserver {
  public:
   virtual ~VideoCaptureDeviceLaunchObserver() {}
   virtual void OnDeviceLaunched(VideoCaptureController* controller) = 0;
diff --git a/content/browser/renderer_host/media/video_capture_provider.h b/content/browser/renderer_host/media/video_capture_provider.h
index 4263d61..991a7f88 100644
--- a/content/browser/renderer_host/media/video_capture_provider.h
+++ b/content/browser/renderer_host/media/video_capture_provider.h
@@ -6,7 +6,6 @@
 #define CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_PROVIDER_H_
 
 #include "base/memory/ref_counted.h"
-#include "content/common/content_export.h"
 #include "media/capture/video/video_capture_device.h"
 #include "media/capture/video/video_capture_device_info.h"
 #include "media/capture/video/video_frame_receiver.h"
@@ -19,7 +18,7 @@
 
 // Note: GetDeviceInfosAsync is only relevant for devices with
 // MediaStreamType == DEVICE_VIDEO_CAPTURE, i.e. camera devices.
-class CONTENT_EXPORT VideoCaptureProvider {
+class VideoCaptureProvider {
  public:
   using GetDeviceInfosCallback = base::OnceCallback<void(
       const std::vector<media::VideoCaptureDeviceInfo>&)>;
diff --git a/content/browser/renderer_host/media/video_capture_provider_switcher.h b/content/browser/renderer_host/media/video_capture_provider_switcher.h
index fd7c1c9..a981532 100644
--- a/content/browser/renderer_host/media/video_capture_provider_switcher.h
+++ b/content/browser/renderer_host/media/video_capture_provider_switcher.h
@@ -6,15 +6,13 @@
 #define CONTENT_BROWSER_RENDERER_HOST_MEDIA_VIDEO_CAPTURE_PROVIDER_SWITCHER_H_
 
 #include "content/browser/renderer_host/media/video_capture_provider.h"
-#include "content/common/content_export.h"
 
 namespace content {
 
 // Routes requests for media devices, e.g. cameras, to
 // |media_device_capture_provider| and for all other types of capture, e.g.
 // screen or tab capture, to the given |other_types_capture_provider|.
-class CONTENT_EXPORT VideoCaptureProviderSwitcher
-    : public VideoCaptureProvider {
+class VideoCaptureProviderSwitcher : public VideoCaptureProvider {
  public:
   VideoCaptureProviderSwitcher(
       std::unique_ptr<VideoCaptureProvider> media_device_capture_provider,
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 3684132c..a7694895 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -309,7 +309,8 @@
 // values of relevant headers like Sec-CH-UA-Reduced.  If `user_agent_override`
 // is non-empty, `user_agent_override` is returned as the header value.
 std::string ComputeUserAgentValue(const net::HttpRequestHeaders& headers,
-                                  const std::string& user_agent_override) {
+                                  const std::string& user_agent_override,
+                                  content::BrowserContext* context) {
   if (!user_agent_override.empty()) {
     base::UmaHistogramEnumeration("Navigation.UserAgentStringType",
                                   UserAgentStringType::kOverriden);
@@ -327,7 +328,8 @@
                                 reduced ? UserAgentStringType::kReducedVersion
                                         : UserAgentStringType::kFullVersion);
   return reduced ? GetContentClient()->browser()->GetReducedUserAgent()
-                 : GetContentClient()->browser()->GetUserAgent();
+                 : GetContentClient()->browser()->GetUserAgentBasedOnPolicy(
+                       context);
 }
 
 // TODO(clamy): This should match what's happening in
@@ -362,7 +364,7 @@
 
   headers->SetHeaderIfMissing(
       net::HttpRequestHeaders::kUserAgent,
-      ComputeUserAgentValue(*headers, user_agent_override));
+      ComputeUserAgentValue(*headers, user_agent_override, browser_context));
 
   if (!render_prefs.enable_referrers) {
     *referrer =
@@ -3959,7 +3961,8 @@
     if (!devtools_user_agent_override_) {
       modified_headers.SetHeader(
           net::HttpRequestHeaders::kUserAgent,
-          ComputeUserAgentValue(modified_headers, GetUserAgentOverride()));
+          ComputeUserAgentValue(modified_headers, GetUserAgentOverride(),
+                                browser_context));
     }
   }
 
@@ -6476,8 +6479,9 @@
         common_params_->url, client_hints_delegate, is_overriding_user_agent(),
         frame_tree_node_, &headers);
   }
-  headers.SetHeader(net::HttpRequestHeaders::kUserAgent,
-                    ComputeUserAgentValue(headers, GetUserAgentOverride()));
+  headers.SetHeader(
+      net::HttpRequestHeaders::kUserAgent,
+      ComputeUserAgentValue(headers, GetUserAgentOverride(), browser_context));
   begin_params_->headers = headers.ToString();
   // |request_headers_| comes from |begin_params_|. Clear |request_headers_| now
   // so that if |request_headers_| are needed, they will be updated.
diff --git a/content/browser/renderer_host/navigator_delegate.h b/content/browser/renderer_host/navigator_delegate.h
index b1deb8d9..3d2b2d0 100644
--- a/content/browser/renderer_host/navigator_delegate.h
+++ b/content/browser/renderer_host/navigator_delegate.h
@@ -5,7 +5,6 @@
 #ifndef CONTENT_BROWSER_RENDERER_HOST_NAVIGATOR_DELEGATE_H_
 #define CONTENT_BROWSER_RENDERER_HOST_NAVIGATOR_DELEGATE_H_
 
-#include "content/common/content_export.h"
 #include "content/common/navigation_client.mojom.h"
 #include "content/public/browser/allow_service_worker_result.h"
 #include "content/public/browser/cookie_access_details.h"
@@ -32,7 +31,7 @@
 
 // A delegate API used by Navigator to notify its embedder of navigation
 // related events.
-class CONTENT_EXPORT NavigatorDelegate {
+class NavigatorDelegate {
  public:
   // Called when a navigation started. The same NavigationHandle will be
   // provided for events related to the same navigation.
diff --git a/content/browser/renderer_host/page_delegate.h b/content/browser/renderer_host/page_delegate.h
index cf98af0..ee1c618 100644
--- a/content/browser/renderer_host/page_delegate.h
+++ b/content/browser/renderer_host/page_delegate.h
@@ -5,8 +5,6 @@
 #ifndef CONTENT_BROWSER_RENDERER_HOST_PAGE_DELEGATE_H_
 #define CONTENT_BROWSER_RENDERER_HOST_PAGE_DELEGATE_H_
 
-#include "content/common/content_export.h"
-
 namespace content {
 
 class PageImpl;
@@ -14,7 +12,7 @@
 // Interface implemented by an object (in practice, WebContentsImpl) which
 // owns (possibly indirectly) and is interested in knowing about the state of
 // one or more Pages. It must outlive the Page.
-class CONTENT_EXPORT PageDelegate {
+class PageDelegate {
  public:
   // Called when a paint happens after the first non empty layout. In other
   // words, after the page has painted something.
diff --git a/content/browser/renderer_host/pepper/pepper_file_ref_host.h b/content/browser/renderer_host/pepper/pepper_file_ref_host.h
index 9f0b8dd..660300e 100644
--- a/content/browser/renderer_host/pepper/pepper_file_ref_host.h
+++ b/content/browser/renderer_host/pepper/pepper_file_ref_host.h
@@ -10,7 +10,6 @@
 #include <string>
 
 #include "base/memory/weak_ptr.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/browser_ppapi_host.h"
 #include "ppapi/c/pp_file_info.h"
 #include "ppapi/c/pp_instance.h"
@@ -53,9 +52,8 @@
   virtual int32_t CanReadWrite() const = 0;
 };
 
-class CONTENT_EXPORT PepperFileRefHost
-    : public ppapi::host::ResourceHost,
-      public base::SupportsWeakPtr<PepperFileRefHost> {
+class PepperFileRefHost : public ppapi::host::ResourceHost,
+                          public base::SupportsWeakPtr<PepperFileRefHost> {
  public:
   PepperFileRefHost(BrowserPpapiHost* host,
                     PP_Instance instance,
diff --git a/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.h b/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.h
index a9297cb..b4e8605 100644
--- a/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.h
+++ b/content/browser/renderer_host/pepper/pepper_host_resolver_message_filter.h
@@ -11,7 +11,6 @@
 #include <vector>
 
 #include "base/compiler_specific.h"
-#include "content/common/content_export.h"
 #include "content/public/common/process_type.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "ppapi/c/pp_instance.h"
@@ -38,7 +37,7 @@
 
 class BrowserPpapiHostImpl;
 
-class CONTENT_EXPORT PepperHostResolverMessageFilter
+class PepperHostResolverMessageFilter
     : public ppapi::host::ResourceMessageFilter,
       public network::ResolveHostClientBase {
  public:
diff --git a/content/browser/renderer_host/pepper/pepper_network_monitor_host.h b/content/browser/renderer_host/pepper/pepper_network_monitor_host.h
index 57f5964..20ca403 100644
--- a/content/browser/renderer_host/pepper/pepper_network_monitor_host.h
+++ b/content/browser/renderer_host/pepper/pepper_network_monitor_host.h
@@ -7,7 +7,6 @@
 
 #include "base/compiler_specific.h"
 #include "base/memory/weak_ptr.h"
-#include "content/common/content_export.h"
 #include "net/base/network_interfaces.h"
 #include "ppapi/host/host_message_context.h"
 #include "ppapi/host/resource_host.h"
@@ -18,7 +17,7 @@
 class BrowserPpapiHostImpl;
 
 // The host for PPB_NetworkMonitor. This class lives on the IO thread.
-class CONTENT_EXPORT PepperNetworkMonitorHost
+class PepperNetworkMonitorHost
     : public ppapi::host::ResourceHost,
       public network::NetworkConnectionTracker::NetworkConnectionObserver {
  public:
diff --git a/content/browser/renderer_host/pepper/pepper_network_proxy_host.h b/content/browser/renderer_host/pepper/pepper_network_proxy_host.h
index e08bce5d..662b436a 100644
--- a/content/browser/renderer_host/pepper/pepper_network_proxy_host.h
+++ b/content/browser/renderer_host/pepper/pepper_network_proxy_host.h
@@ -15,7 +15,6 @@
 #include "base/containers/queue.h"
 #include "base/containers/unique_ptr_adapters.h"
 #include "base/memory/weak_ptr.h"
-#include "content/common/content_export.h"
 #include "ppapi/host/host_message_context.h"
 #include "ppapi/host/resource_host.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -37,7 +36,7 @@
 class PepperProxyLookupHelper;
 
 // The host for PPB_NetworkProxy. This class lives on the IO thread.
-class CONTENT_EXPORT PepperNetworkProxyHost : public ppapi::host::ResourceHost {
+class PepperNetworkProxyHost : public ppapi::host::ResourceHost {
  public:
   PepperNetworkProxyHost(BrowserPpapiHostImpl* host,
                          PP_Instance instance,
diff --git a/content/browser/renderer_host/pepper/pepper_print_settings_manager.h b/content/browser/renderer_host/pepper/pepper_print_settings_manager.h
index 2dbbd26c..c9701354 100644
--- a/content/browser/renderer_host/pepper/pepper_print_settings_manager.h
+++ b/content/browser/renderer_host/pepper/pepper_print_settings_manager.h
@@ -9,13 +9,12 @@
 
 #include "base/bind.h"
 #include "base/compiler_specific.h"
-#include "content/common/content_export.h"
 #include "ppapi/c/dev/pp_print_settings_dev.h"
 
 namespace content {
 
 // A class for getting the default print settings for the default printer.
-class CONTENT_EXPORT PepperPrintSettingsManager {
+class PepperPrintSettingsManager {
  public:
   using Result = std::pair<PP_PrintSettings_Dev, int32_t>;
   using Callback = base::OnceCallback<void(Result)>;
@@ -30,8 +29,7 @@
 };
 
 // Real implementation for getting the default print settings.
-class CONTENT_EXPORT PepperPrintSettingsManagerImpl
-    : public PepperPrintSettingsManager {
+class PepperPrintSettingsManagerImpl : public PepperPrintSettingsManager {
  public:
   PepperPrintSettingsManagerImpl() {}
 
diff --git a/content/browser/renderer_host/pepper/pepper_security_helper.h b/content/browser/renderer_host/pepper/pepper_security_helper.h
index 4608f80..6dc1e922 100644
--- a/content/browser/renderer_host/pepper/pepper_security_helper.h
+++ b/content/browser/renderer_host/pepper/pepper_security_helper.h
@@ -6,23 +6,21 @@
 #define CONTENT_BROWSER_RENDERER_HOST_PEPPER_PEPPER_SECURITY_HELPER_H_
 
 #include "base/files/file_path.h"
-#include "content/common/content_export.h"
 #include "storage/browser/file_system/file_system_url.h"
 
 namespace content {
 
 // Helper function that returns whether or not the child process is allowed to
 // open the specified |file| with the specified |pp_open_flags|.
-CONTENT_EXPORT bool CanOpenWithPepperFlags(int pp_open_flags,
-                                           int child_id,
-                                           const base::FilePath& file);
+bool CanOpenWithPepperFlags(int pp_open_flags,
+                            int child_id,
+                            const base::FilePath& file);
 
 // Helper function that returns whether or not the child process is allowed to
 // open the specified file system |url| with the specified |pp_open_flags|.
-CONTENT_EXPORT bool CanOpenFileSystemURLWithPepperFlags(
-    int pp_open_flags,
-    int child_id,
-    const storage::FileSystemURL& url);
+bool CanOpenFileSystemURLWithPepperFlags(int pp_open_flags,
+                                         int child_id,
+                                         const storage::FileSystemURL& url);
 
 }  // namespace content
 
diff --git a/content/browser/renderer_host/pepper/pepper_truetype_font_host.h b/content/browser/renderer_host/pepper/pepper_truetype_font_host.h
index dd264e4..effd851c 100644
--- a/content/browser/renderer_host/pepper/pepper_truetype_font_host.h
+++ b/content/browser/renderer_host/pepper/pepper_truetype_font_host.h
@@ -15,7 +15,6 @@
 #include "base/memory/weak_ptr.h"
 #include "base/task/sequenced_task_runner.h"
 #include "content/browser/renderer_host/pepper/pepper_truetype_font.h"
-#include "content/common/content_export.h"
 #include "ppapi/host/host_message_context.h"
 #include "ppapi/host/resource_host.h"
 
@@ -23,7 +22,7 @@
 
 class BrowserPpapiHost;
 
-class CONTENT_EXPORT PepperTrueTypeFontHost : public ppapi::host::ResourceHost {
+class PepperTrueTypeFontHost : public ppapi::host::ResourceHost {
  public:
   PepperTrueTypeFontHost(BrowserPpapiHost* host,
                          PP_Instance instance,
diff --git a/content/browser/renderer_host/pepper/pepper_vpn_provider_message_filter_chromeos.h b/content/browser/renderer_host/pepper/pepper_vpn_provider_message_filter_chromeos.h
index a0a45c7..c9093d799 100644
--- a/content/browser/renderer_host/pepper/pepper_vpn_provider_message_filter_chromeos.h
+++ b/content/browser/renderer_host/pepper/pepper_vpn_provider_message_filter_chromeos.h
@@ -16,7 +16,6 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
 #include "content/browser/renderer_host/pepper/browser_ppapi_host_impl.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/vpn_service_proxy.h"
 #include "ipc/ipc_message.h"
 #include "ppapi/c/pp_instance.h"
@@ -32,7 +31,7 @@
 
 // The host for PPB_VpnProvider.
 // Important: The PPB_VpnProvider API is available only on Chrome OS.
-class CONTENT_EXPORT PepperVpnProviderMessageFilter
+class PepperVpnProviderMessageFilter
     : public ppapi::host::ResourceMessageFilter {
  public:
   PepperVpnProviderMessageFilter(BrowserPpapiHostImpl* host,
diff --git a/content/browser/renderer_host/render_frame_host_csp_context.h b/content/browser/renderer_host/render_frame_host_csp_context.h
index 3f083cd..d340b79b 100644
--- a/content/browser/renderer_host/render_frame_host_csp_context.h
+++ b/content/browser/renderer_host/render_frame_host_csp_context.h
@@ -5,7 +5,6 @@
 #ifndef CONTENT_BROWSER_RENDERER_HOST_RENDER_FRAME_HOST_CSP_CONTEXT_H_
 #define CONTENT_BROWSER_RENDERER_HOST_RENDER_FRAME_HOST_CSP_CONTEXT_H_
 
-#include "content/common/content_export.h"
 #include "services/network/public/cpp/content_security_policy/csp_context.h"
 
 class GURL;
@@ -17,7 +16,7 @@
 // RenderFrameHostCSPContext is a network::CSPContext that reports Content
 // Security Policy violations through the mojo connection between a
 // RenderFrameHostImpl and its corresponding LocalFrame.
-class CONTENT_EXPORT RenderFrameHostCSPContext : public network::CSPContext {
+class RenderFrameHostCSPContext : public network::CSPContext {
  public:
   // Construct a new RenderFrameHostCSPContext reporting CSP violations through
   // `render_frame_host`. The parameter `render_frame_host` can be null, in
diff --git a/content/browser/renderer_host/render_message_filter.h b/content/browser/renderer_host/render_message_filter.h
index b4d9327..f8469f14 100644
--- a/content/browser/renderer_host/render_message_filter.h
+++ b/content/browser/renderer_host/render_message_filter.h
@@ -14,7 +14,6 @@
 #include "base/memory/weak_ptr.h"
 #include "base/task/sequenced_task_runner_helpers.h"
 #include "build/build_config.h"
-#include "content/common/content_export.h"
 #include "content/common/render_message_filter.mojom.h"
 #include "content/public/browser/browser_associated_interface.h"
 #include "content/public/browser/browser_message_filter.h"
@@ -46,7 +45,7 @@
 
 // This class filters out incoming IPC messages for the renderer process on the
 // IPC thread.
-class CONTENT_EXPORT RenderMessageFilter
+class RenderMessageFilter
     : public BrowserMessageFilter,
       public BrowserAssociatedInterface<mojom::RenderMessageFilter> {
  public:
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 094b586f..2d4ac75 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -5031,7 +5031,8 @@
 
   // Pass bits of global renderer state to the renderer.
   GetRendererInterface()->SetUserAgent(
-      GetContentClient()->browser()->GetUserAgent());
+      GetContentClient()->browser()->GetUserAgentBasedOnPolicy(
+          browser_context_));
   GetRendererInterface()->SetReducedUserAgent(
       GetContentClient()->browser()->GetReducedUserAgent());
   GetRendererInterface()->SetUserAgentMetadata(
diff --git a/content/browser/renderer_host/render_view_host_delegate.h b/content/browser/renderer_host/render_view_host_delegate.h
index d2e70712..3e8c124 100644
--- a/content/browser/renderer_host/render_view_host_delegate.h
+++ b/content/browser/renderer_host/render_view_host_delegate.h
@@ -10,7 +10,6 @@
 #include "base/callback.h"
 #include "base/process/kill.h"
 #include "content/browser/dom_storage/session_storage_namespace_impl.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "net/base/load_states.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -45,7 +44,7 @@
 // this. This delegate interface is useful for renderer_host/ to make requests
 // to WebContentsImpl, as renderer_host/ is not permitted to know the
 // WebContents type (see //renderer_host/DEPS).
-class CONTENT_EXPORT RenderViewHostDelegate {
+class RenderViewHostDelegate {
  public:
   // Returns the current delegate associated with a feature. May return NULL if
   // there is no corresponding delegate.
diff --git a/content/browser/renderer_host/render_widget_host_owner_delegate.h b/content/browser/renderer_host/render_widget_host_owner_delegate.h
index 6117496..56fc39ce 100644
--- a/content/browser/renderer_host/render_widget_host_owner_delegate.h
+++ b/content/browser/renderer_host/render_widget_host_owner_delegate.h
@@ -6,7 +6,6 @@
 #define CONTENT_BROWSER_RENDERER_HOST_RENDER_WIDGET_HOST_OWNER_DELEGATE_H_
 
 #include "build/build_config.h"
-#include "content/common/content_export.h"
 #include "third_party/blink/public/common/widget/visual_properties.h"
 
 namespace blink {
@@ -26,7 +25,7 @@
 //  intended to be temporary until the RenderViewHostImpl and
 //  RenderWidgetHostImpl classes are disentangled; see http://crbug.com/542477
 //  and http://crbug.com/478281.
-class CONTENT_EXPORT RenderWidgetHostOwnerDelegate {
+class RenderWidgetHostOwnerDelegate {
  public:
   // The RenderWidgetHost got the focus.
   virtual void RenderWidgetGotFocus() = 0;
diff --git a/content/browser/renderer_host/render_widget_host_view_base.cc b/content/browser/renderer_host/render_widget_host_view_base.cc
index 8f29a8a..0399fd1 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.cc
+++ b/content/browser/renderer_host/render_widget_host_view_base.cc
@@ -303,7 +303,7 @@
 RenderWidgetHostViewBase::CreateVideoCapturer() {
   std::unique_ptr<viz::ClientFrameSinkVideoCapturer> video_capturer =
       GetHostFrameSinkManager()->CreateVideoCapturer();
-  video_capturer->ChangeTarget(GetFrameSinkId(), nullptr);
+  video_capturer->ChangeTarget(viz::VideoCaptureTarget(GetFrameSinkId()));
   return video_capturer;
 }
 
diff --git a/content/browser/renderer_host/stored_page.h b/content/browser/renderer_host/stored_page.h
index e80cb02..38403b71 100644
--- a/content/browser/renderer_host/stored_page.h
+++ b/content/browser/renderer_host/stored_page.h
@@ -8,7 +8,6 @@
 #include <unordered_map>
 
 #include "content/browser/site_instance_group.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/site_instance.h"
 #include "third_party/blink/public/mojom/page/page.mojom.h"
 
@@ -21,7 +20,7 @@
 // main RenderFrameHost together with RenderViewHosts and main document's
 // proxies. It's used for storing pages in back/forward cache or when preparing
 // prerendered pages for activation.
-struct CONTENT_EXPORT StoredPage {
+struct StoredPage {
   using RenderFrameProxyHostMap =
       std::unordered_map<SiteInstanceGroupId,
                          std::unique_ptr<RenderFrameProxyHost>,
diff --git a/content/browser/renderer_host/text_input_host_impl.h b/content/browser/renderer_host/text_input_host_impl.h
index 40973c5..96ead5a 100644
--- a/content/browser/renderer_host/text_input_host_impl.h
+++ b/content/browser/renderer_host/text_input_host_impl.h
@@ -5,7 +5,6 @@
 #ifndef CONTENT_BROWSER_RENDERER_HOST_TEXT_INPUT_HOST_IMPL_H_
 #define CONTENT_BROWSER_RENDERER_HOST_TEXT_INPUT_HOST_IMPL_H_
 
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "third_party/blink/public/mojom/input/text_input_host.mojom.h"
 
@@ -14,7 +13,7 @@
 // Note that the methods should run on BrowserThread::IO to get pumped because
 // BrowserThread::UI is being blocked on a semaphore at TextInputClientMac.
 // http://crbug.com/121917
-class CONTENT_EXPORT TextInputHostImpl : public blink::mojom::TextInputHost {
+class TextInputHostImpl : public blink::mojom::TextInputHost {
  public:
   TextInputHostImpl();
 
diff --git a/content/browser/renderer_host/ui_events_helper.h b/content/browser/renderer_host/ui_events_helper.h
index 961039f..5f7e83e3 100644
--- a/content/browser/renderer_host/ui_events_helper.h
+++ b/content/browser/renderer_host/ui_events_helper.h
@@ -9,7 +9,6 @@
 #include <vector>
 
 #include "content/browser/renderer_host/event_with_latency_info.h"
-#include "content/common/content_export.h"
 #include "third_party/blink/public/mojom/input/input_event_result.mojom-shared.h"
 
 namespace ui {
@@ -33,7 +32,7 @@
 // WebTouchPoint.position or WebTouchPoint.screenPosition.  Is's up to the
 // caller to do any co-ordinate system mapping (typically to get them into
 // the Aura EventDispatcher co-ordinate system).
-CONTENT_EXPORT bool MakeUITouchEventsFromWebTouchEvents(
+bool MakeUITouchEventsFromWebTouchEvents(
     const TouchEventWithLatencyInfo& touch,
     std::vector<std::unique_ptr<ui::TouchEvent>>* list,
     TouchEventCoordinateSystem coordinate_system);
diff --git a/content/browser/resource_context_impl.h b/content/browser/resource_context_impl.h
index ddf75862..5ae6476ff 100644
--- a/content/browser/resource_context_impl.h
+++ b/content/browser/resource_context_impl.h
@@ -5,7 +5,6 @@
 #ifndef CONTENT_BROWSER_RESOURCE_CONTEXT_IMPL_H_
 #define CONTENT_BROWSER_RESOURCE_CONTEXT_IMPL_H_
 
-#include "content/common/content_export.h"
 #include "content/public/browser/resource_context.h"
 
 namespace content {
@@ -17,7 +16,7 @@
 // public API.
 
 // Initialize the above data on the ResourceContext from a given BrowserContext.
-CONTENT_EXPORT void InitializeResourceContext(BrowserContext* browser_context);
+void InitializeResourceContext(BrowserContext* browser_context);
 
 }  // namespace content
 
diff --git a/content/browser/sandbox_host_linux.h b/content/browser/sandbox_host_linux.h
index d47ef829..0126ebd 100644
--- a/content/browser/sandbox_host_linux.h
+++ b/content/browser/sandbox_host_linux.h
@@ -11,13 +11,12 @@
 #include "base/no_destructor.h"
 #include "base/threading/simple_thread.h"
 #include "content/browser/sandbox_ipc_linux.h"
-#include "content/common/content_export.h"
 
 namespace content {
 
 // This is a singleton object which handles sandbox requests from the
 // sandboxed processes.
-class CONTENT_EXPORT SandboxHostLinux {
+class SandboxHostLinux {
  public:
   // Returns the singleton instance.
   static SandboxHostLinux* GetInstance();
diff --git a/content/browser/scheduler/responsiveness/message_loop_observer.h b/content/browser/scheduler/responsiveness/message_loop_observer.h
index fb57737..13bab3f 100644
--- a/content/browser/scheduler/responsiveness/message_loop_observer.h
+++ b/content/browser/scheduler/responsiveness/message_loop_observer.h
@@ -6,7 +6,6 @@
 #define CONTENT_BROWSER_SCHEDULER_RESPONSIVENESS_MESSAGE_LOOP_OBSERVER_H_
 
 #include "base/task/task_observer.h"
-#include "content/common/content_export.h"
 
 namespace base {
 struct PendingTask;
@@ -18,7 +17,7 @@
 // This object is not thread safe. It must be constructed and destroyed on the
 // same thread. The callbacks will occur synchronously from WillProcessTask()
 // and DidProcessTask().
-class CONTENT_EXPORT MessageLoopObserver : base::TaskObserver {
+class MessageLoopObserver : base::TaskObserver {
  public:
   using WillProcessTaskCallback =
       base::RepeatingCallback<void(const base::PendingTask* task,
diff --git a/content/browser/service_worker/service_worker_consts.h b/content/browser/service_worker/service_worker_consts.h
index 8852d823..f047e52 100644
--- a/content/browser/service_worker/service_worker_consts.h
+++ b/content/browser/service_worker/service_worker_consts.h
@@ -8,11 +8,10 @@
 #include <stdint.h>
 
 #include "base/time/time.h"
-#include "content/common/content_export.h"
 
 namespace content {
 
-struct CONTENT_EXPORT ServiceWorkerConsts {
+struct ServiceWorkerConsts {
   static const char kBadMessageFromNonWindow[];
   static const char kBadMessageGetRegistrationForReadyDuplicated[];
   static const char kBadMessageImproperOrigins[];
diff --git a/content/browser/service_worker/service_worker_identifiability_metrics.h b/content/browser/service_worker/service_worker_identifiability_metrics.h
index 24ccdda..8034071d 100644
--- a/content/browser/service_worker/service_worker_identifiability_metrics.h
+++ b/content/browser/service_worker/service_worker_identifiability_metrics.h
@@ -12,7 +12,6 @@
 
 #include "content/browser/service_worker/service_worker_context_core_observer.h"
 #include "content/browser/service_worker/service_worker_info.h"
-#include "content/common/content_export.h"
 #include "services/metrics/public/cpp/ukm_source_id.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_client.mojom.h"
 
@@ -30,7 +29,7 @@
 // Used to emit a UKM event to associate each worker with each client that it
 // has the potential to communicate with (whether or not they actually do). Only
 // created when the identifiability study is active.
-class CONTENT_EXPORT ServiceWorkerIdentifiabilityMetrics
+class ServiceWorkerIdentifiabilityMetrics
     : public ServiceWorkerContextCoreObserver {
  public:
   ServiceWorkerIdentifiabilityMetrics();
diff --git a/content/browser/service_worker/service_worker_installed_script_loader.h b/content/browser/service_worker/service_worker_installed_script_loader.h
index 6237126..f99752b 100644
--- a/content/browser/service_worker/service_worker_installed_script_loader.h
+++ b/content/browser/service_worker/service_worker_installed_script_loader.h
@@ -6,7 +6,6 @@
 #define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_INSTALLED_SCRIPT_LOADER_H_
 
 #include "content/browser/service_worker/service_worker_installed_script_reader.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/system/data_pipe.h"
@@ -26,7 +25,7 @@
 // - a service worker that was new when it started and became installed while
 //   running requests an installed script, e.g., importScripts('a.js') after
 //   installation.
-class CONTENT_EXPORT ServiceWorkerInstalledScriptLoader
+class ServiceWorkerInstalledScriptLoader
     : public network::mojom::URLLoader,
       public ServiceWorkerInstalledScriptReader::Client,
       public mojo::DataPipeDrainer::Client {
diff --git a/content/browser/service_worker/service_worker_quota_client.h b/content/browser/service_worker/service_worker_quota_client.h
index b1530f40..cfcb847a 100644
--- a/content/browser/service_worker/service_worker_quota_client.h
+++ b/content/browser/service_worker/service_worker_quota_client.h
@@ -10,7 +10,6 @@
 #include "base/sequence_checker.h"
 #include "base/thread_annotations.h"
 #include "components/services/storage/public/cpp/storage_key_quota_client.h"
-#include "content/common/content_export.h"
 #include "storage/browser/quota/quota_client_type.h"
 #include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
 
@@ -25,8 +24,7 @@
  public:
   // `context` must outlive this instance. This is true because `context` owns
   // this instance.
-  CONTENT_EXPORT explicit ServiceWorkerQuotaClient(
-      ServiceWorkerContextCore& context);
+  explicit ServiceWorkerQuotaClient(ServiceWorkerContextCore& context);
 
   ServiceWorkerQuotaClient(const ServiceWorkerQuotaClient&) = delete;
   ServiceWorkerQuotaClient& operator=(const ServiceWorkerQuotaClient&) = delete;
diff --git a/content/browser/service_worker/service_worker_register_job.h b/content/browser/service_worker/service_worker_register_job.h
index 6185eb1..1406ad2 100644
--- a/content/browser/service_worker/service_worker_register_job.h
+++ b/content/browser/service_worker/service_worker_register_job.h
@@ -14,7 +14,6 @@
 #include "content/browser/service_worker/service_worker_register_job_base.h"
 #include "content/browser/service_worker/service_worker_registration.h"
 #include "content/browser/service_worker/service_worker_update_checker.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/global_routing_id.h"
 #include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
@@ -49,7 +48,7 @@
       RegistrationCallback;
 
   // For registration jobs.
-  CONTENT_EXPORT ServiceWorkerRegisterJob(
+  ServiceWorkerRegisterJob(
       ServiceWorkerContextCore* context,
       const GURL& script_url,
       const blink::mojom::ServiceWorkerRegistrationOptions& options,
@@ -59,13 +58,12 @@
       const GlobalRenderFrameHostId& requesting_frame_id);
 
   // For update jobs.
-  CONTENT_EXPORT ServiceWorkerRegisterJob(
-      ServiceWorkerContextCore* context,
-      ServiceWorkerRegistration* registration,
-      bool force_bypass_cache,
-      bool skip_script_comparison,
-      blink::mojom::FetchClientSettingsObjectPtr
-          outside_fetch_client_settings_object);
+  ServiceWorkerRegisterJob(ServiceWorkerContextCore* context,
+                           ServiceWorkerRegistration* registration,
+                           bool force_bypass_cache,
+                           bool skip_script_comparison,
+                           blink::mojom::FetchClientSettingsObjectPtr
+                               outside_fetch_client_settings_object);
 
   ServiceWorkerRegisterJob(const ServiceWorkerRegisterJob&) = delete;
   ServiceWorkerRegisterJob& operator=(const ServiceWorkerRegisterJob&) = delete;
diff --git a/content/browser/service_worker/service_worker_type_converters.h b/content/browser/service_worker/service_worker_type_converters.h
index 0ca17c8c..9941c44 100644
--- a/content/browser/service_worker/service_worker_type_converters.h
+++ b/content/browser/service_worker/service_worker_type_converters.h
@@ -6,14 +6,13 @@
 #define CONTENT_BROWSER_SERVICE_WORKER_SERVICE_WORKER_TYPE_CONVERTERS_H_
 
 #include "content/browser/service_worker/service_worker_version.h"
-#include "content/common/content_export.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_state.mojom.h"
 
 namespace mojo {
 
 template <>
-struct CONTENT_EXPORT TypeConverter<blink::mojom::ServiceWorkerState,
-                                    content::ServiceWorkerVersion::Status> {
+struct TypeConverter<blink::mojom::ServiceWorkerState,
+                     content::ServiceWorkerVersion::Status> {
   static blink::mojom::ServiceWorkerState Convert(
       content::ServiceWorkerVersion::Status status);
 };
diff --git a/content/browser/service_worker/service_worker_version.cc b/content/browser/service_worker/service_worker_version.cc
index 75926a2b..ac495278 100644
--- a/content/browser/service_worker/service_worker_version.cc
+++ b/content/browser/service_worker/service_worker_version.cc
@@ -1914,7 +1914,8 @@
   params->user_agent = (origin_trial_tokens_ &&
                         origin_trial_tokens_->contains("UserAgentReduction"))
                            ? browser_client->GetReducedUserAgent()
-                           : browser_client->GetUserAgent();
+                           : browser_client->GetUserAgentBasedOnPolicy(
+                                 context_->wrapper()->browser_context());
   params->ua_metadata = browser_client->GetUserAgentMetadata();
   params->is_installed = IsInstalled(status_);
   params->script_url_to_skip_throttling = updated_script_url_;
diff --git a/content/browser/shared_storage/shared_storage_document_service_impl.h b/content/browser/shared_storage/shared_storage_document_service_impl.h
index a4e9052..fb00dd05 100644
--- a/content/browser/shared_storage/shared_storage_document_service_impl.h
+++ b/content/browser/shared_storage/shared_storage_document_service_impl.h
@@ -5,7 +5,6 @@
 #ifndef CONTENT_BROWSER_SHARED_STORAGE_SHARED_STORAGE_DOCUMENT_SERVICE_IMPL_H_
 #define CONTENT_BROWSER_SHARED_STORAGE_SHARED_STORAGE_DOCUMENT_SERVICE_IMPL_H_
 
-#include "content/common/content_export.h"
 #include "content/public/browser/document_user_data.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
@@ -20,7 +19,7 @@
 // Handle renderer-initiated shared storage access and worklet operations. The
 // worklet operations (i.e. addModule and runOperation) will be dispatched to
 // the `SharedStorageWorkletHost` to be handled.
-class CONTENT_EXPORT SharedStorageDocumentServiceImpl final
+class SharedStorageDocumentServiceImpl final
     : public DocumentUserData<SharedStorageDocumentServiceImpl>,
       public blink::mojom::SharedStorageDocumentService {
  public:
diff --git a/content/browser/sms/sms_queue.h b/content/browser/sms/sms_queue.h
index 239fbb3..89bdbaba 100644
--- a/content/browser/sms/sms_queue.h
+++ b/content/browser/sms/sms_queue.h
@@ -8,14 +8,13 @@
 #include <map>
 
 #include "base/observer_list.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/sms_fetcher.h"
 #include "url/origin.h"
 
 namespace content {
 
 // SmsQueue manages the queue of pending requests for each origin.
-class CONTENT_EXPORT SmsQueue {
+class SmsQueue {
  public:
   SmsQueue();
 
diff --git a/content/browser/speech/speech_recognition_dispatcher_host.h b/content/browser/speech/speech_recognition_dispatcher_host.h
index 560177eb..3bd45ec 100644
--- a/content/browser/speech/speech_recognition_dispatcher_host.h
+++ b/content/browser/speech/speech_recognition_dispatcher_host.h
@@ -9,7 +9,6 @@
 #include <string>
 
 #include "base/memory/weak_ptr.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/browser_message_filter.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/speech_recognition_event_listener.h"
@@ -34,8 +33,7 @@
 // SpeechRecognitionDispatcherHost is an implementation of the SpeechRecognizer
 // interface that allows a RenderFrame to start a speech recognition session
 // in the browser process, by communicating with SpeechRecognitionManager.
-class CONTENT_EXPORT SpeechRecognitionDispatcherHost
-    : public blink::mojom::SpeechRecognizer {
+class SpeechRecognitionDispatcherHost : public blink::mojom::SpeechRecognizer {
  public:
   SpeechRecognitionDispatcherHost(int render_process_id, int render_frame_id);
 
diff --git a/content/browser/speech/speech_recognition_engine_unittest.cc b/content/browser/speech/speech_recognition_engine_unittest.cc
index 2f71fb4..288c6d6 100644
--- a/content/browser/speech/speech_recognition_engine_unittest.cc
+++ b/content/browser/speech/speech_recognition_engine_unittest.cc
@@ -696,9 +696,9 @@
 void SpeechRecognitionEngineTest::ExpectFramedChunk(
     const std::string& chunk, uint32_t type) {
   uint32_t value;
-  base::ReadBigEndian(&chunk[0], &value);
+  base::ReadBigEndian(reinterpret_cast<const uint8_t*>(&chunk[0]), &value);
   EXPECT_EQ(chunk.size() - 8, value);
-  base::ReadBigEndian(&chunk[4], &value);
+  base::ReadBigEndian(reinterpret_cast<const uint8_t*>(&chunk[4]), &value);
   EXPECT_EQ(type, value);
 }
 
diff --git a/content/browser/ssl/ssl_client_auth_handler.h b/content/browser/ssl/ssl_client_auth_handler.h
index 8a9c2c76..13869df 100644
--- a/content/browser/ssl/ssl_client_auth_handler.h
+++ b/content/browser/ssl/ssl_client_auth_handler.h
@@ -10,7 +10,6 @@
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/web_contents.h"
 #include "net/ssl/client_cert_identity.h"
@@ -32,7 +31,7 @@
  public:
   // Delegate interface for SSLClientAuthHandler. Method implementations may
   // delete the handler when called.
-  class CONTENT_EXPORT Delegate {
+  class Delegate {
    public:
     Delegate() {}
 
diff --git a/content/browser/ssl/ssl_error_handler.h b/content/browser/ssl/ssl_error_handler.h
index 655bcfd..41d277e 100644
--- a/content/browser/ssl/ssl_error_handler.h
+++ b/content/browser/ssl/ssl_error_handler.h
@@ -7,7 +7,6 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/global_request_id.h"
 #include "net/ssl/ssl_info.h"
@@ -28,7 +27,7 @@
 // call exactly one of those methods exactly once.
 class SSLErrorHandler {
  public:
-  class CONTENT_EXPORT Delegate {
+  class Delegate {
    public:
     // Called when SSLErrorHandler decides to cancel the request because of
     // the SSL error.
@@ -68,7 +67,7 @@
   bool fatal() const { return fatal_; }
 
   // Cancels the associated net::URLRequest.
-  CONTENT_EXPORT void CancelRequest();
+  void CancelRequest();
 
   // Continue the net::URLRequest ignoring any previous errors.  Note that some
   // errors cannot be ignored, in which case this will result in the request
diff --git a/content/browser/ssl/ssl_manager.h b/content/browser/ssl/ssl_manager.h
index 093c313..4f2af99 100644
--- a/content/browser/ssl/ssl_manager.h
+++ b/content/browser/ssl/ssl_manager.h
@@ -9,7 +9,6 @@
 
 #include "base/memory/weak_ptr.h"
 #include "content/browser/ssl/ssl_error_handler.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/global_request_id.h"
 #include "content/public/browser/ssl_status.h"
 #include "net/base/net_errors.h"
@@ -34,7 +33,7 @@
 // There is one SSLManager per tab.
 // The security state (secure/insecure) is stored in the navigation entry.
 // Along with it are stored any SSL error code and the associated cert.
-class CONTENT_EXPORT SSLManager {
+class SSLManager {
  public:
   // Entry point for SSLCertificateErrors.  This function begins the process
   // of resolving a certificate error during an SSL connection.  SSLManager
diff --git a/content/browser/tracing/background_tracing_rule.h b/content/browser/tracing/background_tracing_rule.h
index 0b87d67a..9c3a3e3 100644
--- a/content/browser/tracing/background_tracing_rule.h
+++ b/content/browser/tracing/background_tracing_rule.h
@@ -9,13 +9,12 @@
 
 #include "base/values.h"
 #include "content/browser/tracing/background_tracing_config_impl.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/background_tracing_manager.h"
 #include "third_party/perfetto/protos/perfetto/trace/chrome/chrome_metadata.pbzero.h"
 
 namespace content {
 
-class CONTENT_EXPORT BackgroundTracingRule {
+class BackgroundTracingRule {
  public:
   using MetadataProto =
       perfetto::protos::pbzero::BackgroundTracingMetadata::TriggerRule;
diff --git a/content/browser/web_package/signed_exchange_validity_pinger.h b/content/browser/web_package/signed_exchange_validity_pinger.h
index 2e58865..5f9a839 100644
--- a/content/browser/web_package/signed_exchange_validity_pinger.h
+++ b/content/browser/web_package/signed_exchange_validity_pinger.h
@@ -8,7 +8,6 @@
 #include "base/callback.h"
 #include "base/time/time.h"
 #include "base/unguessable_token.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/system/data_pipe_drainer.h"
 #include "services/network/public/mojom/url_loader.mojom.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
@@ -29,9 +28,8 @@
 // sends a HEAD request to the URL, wait for the response and then calls
 // the given |callback| when it's done, regardless of whether it was success
 // or not.
-class CONTENT_EXPORT SignedExchangeValidityPinger
-    : public network::mojom::URLLoaderClient,
-      public mojo::DataPipeDrainer::Client {
+class SignedExchangeValidityPinger : public network::mojom::URLLoaderClient,
+                                     public mojo::DataPipeDrainer::Client {
  public:
   static std::unique_ptr<SignedExchangeValidityPinger> CreateAndStart(
       const GURL& validity_url,
diff --git a/content/browser/webauth/virtual_authenticator.h b/content/browser/webauth/virtual_authenticator.h
index 1a5aa51..0133257a 100644
--- a/content/browser/webauth/virtual_authenticator.h
+++ b/content/browser/webauth/virtual_authenticator.h
@@ -12,7 +12,6 @@
 #include "base/containers/span.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
-#include "content/common/content_export.h"
 #include "device/fido/fido_constants.h"
 #include "device/fido/virtual_fido_device.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -27,8 +26,7 @@
 // This class has very little logic itself, it merely stores a unique ID and the
 // state of the authenticator, whereas performing all cryptographic operations
 // is delegated to the VirtualFidoDevice class.
-class CONTENT_EXPORT VirtualAuthenticator
-    : public blink::test::mojom::VirtualAuthenticator {
+class VirtualAuthenticator : public blink::test::mojom::VirtualAuthenticator {
  public:
   explicit VirtualAuthenticator(
       const blink::test::mojom::VirtualAuthenticatorOptions& options);
diff --git a/content/browser/webauth/virtual_authenticator_manager_impl.h b/content/browser/webauth/virtual_authenticator_manager_impl.h
index ce765e3..b2846ec4d 100644
--- a/content/browser/webauth/virtual_authenticator_manager_impl.h
+++ b/content/browser/webauth/virtual_authenticator_manager_impl.h
@@ -12,7 +12,6 @@
 
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
-#include "content/common/content_export.h"
 #include "device/fido/fido_discovery_factory.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
@@ -26,7 +25,7 @@
 // Implements the Mojo interface representing a virtual authenticator manager
 // for the Web Authentication API. Allows setting up and configurating virtual
 // authenticator devices for testing.
-class CONTENT_EXPORT VirtualAuthenticatorManagerImpl
+class VirtualAuthenticatorManagerImpl
     : public blink::test::mojom::VirtualAuthenticatorManager {
  public:
   class Observer : public base::CheckedObserver {
diff --git a/content/browser/webauth/virtual_discovery.h b/content/browser/webauth/virtual_discovery.h
index f127a2b..201ecda 100644
--- a/content/browser/webauth/virtual_discovery.h
+++ b/content/browser/webauth/virtual_discovery.h
@@ -10,7 +10,6 @@
 
 #include "base/memory/weak_ptr.h"
 #include "base/strings/string_piece.h"
-#include "content/common/content_export.h"
 #include "device/fido/fido_device_discovery.h"
 
 namespace device {
@@ -21,7 +20,7 @@
 
 // A fully automated FidoDeviceDiscovery implementation, which is disconnected
 // from the real world, and discovers VirtualFidoDevice instances.
-class CONTENT_EXPORT VirtualFidoDiscovery
+class VirtualFidoDiscovery
     : public ::device::FidoDeviceDiscovery,
       public base::SupportsWeakPtr<VirtualFidoDiscovery> {
  public:
diff --git a/content/browser/webauth/virtual_fido_discovery_factory.h b/content/browser/webauth/virtual_fido_discovery_factory.h
index a3541a4..87eaaa7 100644
--- a/content/browser/webauth/virtual_fido_discovery_factory.h
+++ b/content/browser/webauth/virtual_fido_discovery_factory.h
@@ -11,7 +11,6 @@
 
 #include "base/memory/weak_ptr.h"
 #include "content/browser/webauth/virtual_authenticator_manager_impl.h"
-#include "content/common/content_export.h"
 #include "device/fido/fido_discovery_factory.h"
 #include "device/fido/virtual_fido_device.h"
 
@@ -33,7 +32,7 @@
 // Its lifetime is limited to the duration of a WebAuthn request. Note that this
 // differs from VirtualAuthenticatorManagerImpl which is instantiated and
 // destroyed in response to operations on the Virtual Authenticator API.
-class CONTENT_EXPORT VirtualFidoDiscoveryFactory
+class VirtualFidoDiscoveryFactory
     : public device::FidoDiscoveryFactory,
       public VirtualAuthenticatorManagerImpl::Observer {
  public:
diff --git a/content/browser/webid/redirect_uri_data.h b/content/browser/webid/redirect_uri_data.h
index 3c4fca4..5d3233b1 100644
--- a/content/browser/webid/redirect_uri_data.h
+++ b/content/browser/webid/redirect_uri_data.h
@@ -9,7 +9,6 @@
 #include <string>
 
 #include "base/supports_user_data.h"
-#include "content/common/content_export.h"
 
 namespace content {
 
@@ -17,7 +16,7 @@
 
 // This class holds on to the needed OpenID connect redirect callbacks to help
 // connect the IDP response to the appropriate RP.
-class CONTENT_EXPORT RedirectUriData : public base::SupportsUserData::Data {
+class RedirectUriData : public base::SupportsUserData::Data {
  public:
   explicit RedirectUriData(std::string redirect_uri);
   ~RedirectUriData() override;
diff --git a/content/browser/webui/url_data_source_impl.h b/content/browser/webui/url_data_source_impl.h
index e7e0c55..6ff89195 100644
--- a/content/browser/webui/url_data_source_impl.h
+++ b/content/browser/webui/url_data_source_impl.h
@@ -12,7 +12,6 @@
 #include "base/memory/weak_ptr.h"
 #include "base/task/sequenced_task_runner_helpers.h"
 #include "content/browser/webui/url_data_manager.h"
-#include "content/common/content_export.h"
 
 namespace content {
 class URLDataManagerBackend;
@@ -44,7 +43,7 @@
 // pointers and should never be deleted on the IO thread, since their calls
 // are handled almost always on the UI thread and there's a possibility of a
 // data race.  The |DeleteDataSource| trait above is used to enforce this.
-class CONTENT_EXPORT URLDataSourceImpl
+class URLDataSourceImpl
     : public base::RefCountedThreadSafe<URLDataSourceImpl,
                                         DeleteURLDataSource> {
  public:
diff --git a/content/browser/webui/web_ui_controller_factory_registry.h b/content/browser/webui/web_ui_controller_factory_registry.h
index 8e18ac2..8de3cbd 100644
--- a/content/browser/webui/web_ui_controller_factory_registry.h
+++ b/content/browser/webui/web_ui_controller_factory_registry.h
@@ -6,15 +6,13 @@
 #define CONTENT_BROWSER_WEBUI_WEB_UI_CONTROLLER_FACTORY_REGISTRY_H_
 
 #include "base/memory/singleton.h"
-#include "content/common/content_export.h"
 #include "content/public/browser/web_ui_controller_factory.h"
 
 namespace content {
 
 // A singleton which holds on to all the registered WebUIControllerFactory
 // instances.
-class CONTENT_EXPORT WebUIControllerFactoryRegistry
-    : public WebUIControllerFactory {
+class WebUIControllerFactoryRegistry : public WebUIControllerFactory {
  public:
   static WebUIControllerFactoryRegistry* GetInstance();
 
diff --git a/content/browser/worker_host/shared_worker_content_settings_proxy_impl.h b/content/browser/worker_host/shared_worker_content_settings_proxy_impl.h
index 94617580..95e721d 100644
--- a/content/browser/worker_host/shared_worker_content_settings_proxy_impl.h
+++ b/content/browser/worker_host/shared_worker_content_settings_proxy_impl.h
@@ -6,7 +6,6 @@
 #define CONTENT_BROWSER_WORKER_HOST_SHARED_WORKER_CONTENT_SETTINGS_PROXY_IMPL_H_
 
 #include "base/callback.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "third_party/blink/public/mojom/worker/worker_content_settings_proxy.mojom.h"
@@ -22,7 +21,7 @@
 // at the moment.
 // SharedWorkerHost owns this class, so the lifetime of this class is strongly
 // associated to it.
-class CONTENT_EXPORT SharedWorkerContentSettingsProxyImpl
+class SharedWorkerContentSettingsProxyImpl
     : public blink::mojom::WorkerContentSettingsProxy {
  public:
   SharedWorkerContentSettingsProxyImpl(
diff --git a/content/browser/worker_host/shared_worker_host.cc b/content/browser/worker_host/shared_worker_host.cc
index 9bef3a98..74cd75d2 100644
--- a/content/browser/worker_host/shared_worker_host.cc
+++ b/content/browser/worker_host/shared_worker_host.cc
@@ -302,7 +302,8 @@
   factory_.Bind(std::move(factory));
   factory_->CreateSharedWorker(
       std::move(info), token_, instance_.storage_key().origin(),
-      GetContentClient()->browser()->GetUserAgent(),
+      GetContentClient()->browser()->GetUserAgentBasedOnPolicy(
+          GetProcessHost()->GetBrowserContext()),
       GetContentClient()->browser()->GetReducedUserAgent(),
       GetContentClient()->browser()->GetUserAgentMetadata(),
       devtools_handle_->pause_on_start(), devtools_handle_->dev_tools_token(),
diff --git a/content/child/child_thread_impl.h b/content/child/child_thread_impl.h
index a322619..ae6bdc6 100644
--- a/content/child/child_thread_impl.h
+++ b/content/child/child_thread_impl.h
@@ -19,7 +19,6 @@
 #include "components/variations/child_process_field_trial_syncer.h"
 #include "content/common/associated_interfaces.mojom.h"
 #include "content/common/child_process.mojom.h"
-#include "content/common/content_export.h"
 #include "content/public/child/child_thread.h"
 #include "ipc/ipc.mojom.h"
 #include "ipc/ipc_buildflags.h"  // For BUILDFLAG(IPC_MESSAGE_LOG_ENABLED).
@@ -62,10 +61,9 @@
 class InProcessChildThreadParams;
 
 // The main thread of a child process derives from this class.
-class CONTENT_EXPORT ChildThreadImpl : public IPC::Listener,
-                                       virtual public ChildThread {
+class ChildThreadImpl : public IPC::Listener, virtual public ChildThread {
  public:
-  struct CONTENT_EXPORT Options;
+  struct Options;
 
   // Creates the thread.
   explicit ChildThreadImpl(base::RepeatingClosure quit_closure);
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index bfe74a31..363e374 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -405,6 +405,7 @@
            blink::features::kAutoExpandDetailsElement},
           {"UserAgentClientHintFullVersionList",
            blink::features::kUserAgentClientHintFullVersionList},
+          {"UserAgentReduction", blink::features::kReduceUserAgent},
       };
   for (const auto& mapping : runtimeFeatureNameToChromiumFeatureMapping) {
     SetRuntimeFeatureFromChromiumFeature(
diff --git a/content/common/input/input_injector_mojom_traits.h b/content/common/input/input_injector_mojom_traits.h
index f4dbb34..f29228fb8 100644
--- a/content/common/input/input_injector_mojom_traits.h
+++ b/content/common/input/input_injector_mojom_traits.h
@@ -5,7 +5,6 @@
 #ifndef CONTENT_COMMON_INPUT_INPUT_INJECTOR_MOJOM_TRAITS_H_
 #define CONTENT_COMMON_INPUT_INPUT_INJECTOR_MOJOM_TRAITS_H_
 
-#include "content/common/content_export.h"
 #include "content/common/input/input_injector.mojom.h"
 #include "content/common/input/synthetic_pinch_gesture_params.h"
 #include "content/common/input/synthetic_pointer_action_list_params.h"
@@ -19,9 +18,8 @@
 namespace mojo {
 
 template <>
-struct CONTENT_EXPORT
-    EnumTraits<content::mojom::PointerActionType,
-               content::SyntheticPointerActionParams::PointerActionType> {
+struct EnumTraits<content::mojom::PointerActionType,
+                  content::SyntheticPointerActionParams::PointerActionType> {
   static content::mojom::PointerActionType ToMojom(
       content::SyntheticPointerActionParams::PointerActionType input);
   static bool FromMojom(
@@ -30,9 +28,8 @@
 };
 
 template <>
-struct CONTENT_EXPORT
-    EnumTraits<content::mojom::SyntheticButton,
-               content::SyntheticPointerActionParams::Button> {
+struct EnumTraits<content::mojom::SyntheticButton,
+                  content::SyntheticPointerActionParams::Button> {
   static content::mojom::SyntheticButton ToMojom(
       content::SyntheticPointerActionParams::Button input);
   static bool FromMojom(content::mojom::SyntheticButton input,
@@ -40,8 +37,8 @@
 };
 
 template <>
-struct CONTENT_EXPORT StructTraits<content::mojom::SyntheticSmoothDragDataView,
-                                   content::SyntheticSmoothDragGestureParams> {
+struct StructTraits<content::mojom::SyntheticSmoothDragDataView,
+                    content::SyntheticSmoothDragGestureParams> {
   static content::mojom::GestureSourceType gesture_source_type(
       const content::SyntheticSmoothDragGestureParams& r) {
     return r.gesture_source_type;
@@ -67,8 +64,8 @@
 };
 
 template <>
-struct CONTENT_EXPORT StructTraits<content::mojom::SyntheticPinchDataView,
-                                   content::SyntheticPinchGestureParams> {
+struct StructTraits<content::mojom::SyntheticPinchDataView,
+                    content::SyntheticPinchGestureParams> {
   static float scale_factor(const content::SyntheticPinchGestureParams& r) {
     return r.scale_factor;
   }
@@ -88,9 +85,8 @@
 };
 
 template <>
-struct CONTENT_EXPORT
-    StructTraits<content::mojom::SyntheticSmoothScrollDataView,
-                 content::SyntheticSmoothScrollGestureParams> {
+struct StructTraits<content::mojom::SyntheticSmoothScrollDataView,
+                    content::SyntheticSmoothScrollGestureParams> {
   static content::mojom::GestureSourceType gesture_source_type(
       const content::SyntheticSmoothScrollGestureParams& r) {
     return r.gesture_source_type;
@@ -141,8 +137,8 @@
 };
 
 template <>
-struct CONTENT_EXPORT StructTraits<content::mojom::SyntheticTapDataView,
-                                   content::SyntheticTapGestureParams> {
+struct StructTraits<content::mojom::SyntheticTapDataView,
+                    content::SyntheticTapGestureParams> {
   static content::mojom::GestureSourceType gesture_source_type(
       const content::SyntheticTapGestureParams& r) {
     return r.gesture_source_type;
@@ -162,9 +158,8 @@
 };
 
 template <>
-struct CONTENT_EXPORT
-    StructTraits<content::mojom::SyntheticPointerActionParamsDataView,
-                 content::SyntheticPointerActionParams> {
+struct StructTraits<content::mojom::SyntheticPointerActionParamsDataView,
+                    content::SyntheticPointerActionParams> {
   static content::SyntheticPointerActionParams::PointerActionType
   pointer_action_type(const content::SyntheticPointerActionParams& r) {
     return r.pointer_action_type_;
@@ -232,9 +227,8 @@
 };
 
 template <>
-struct CONTENT_EXPORT
-    StructTraits<content::mojom::SyntheticPointerActionDataView,
-                 content::SyntheticPointerActionListParams> {
+struct StructTraits<content::mojom::SyntheticPointerActionDataView,
+                    content::SyntheticPointerActionListParams> {
   static content::mojom::GestureSourceType gesture_source_type(
       const content::SyntheticPointerActionListParams& r) {
     return r.gesture_source_type;
diff --git a/content/common/input/synthetic_gesture_params.h b/content/common/input/synthetic_gesture_params.h
index 9190692..c5c4609 100644
--- a/content/common/input/synthetic_gesture_params.h
+++ b/content/common/input/synthetic_gesture_params.h
@@ -5,7 +5,6 @@
 #ifndef CONTENT_COMMON_INPUT_SYNTHETIC_GESTURE_PARAMS_H_
 #define CONTENT_COMMON_INPUT_SYNTHETIC_GESTURE_PARAMS_H_
 
-#include "content/common/content_export.h"
 #include "content/common/input/input_injector.mojom-shared.h"
 
 namespace content {
@@ -16,7 +15,7 @@
 // The logic for dispatching input events that implement the gesture lives
 // in separate classes in content/browser/renderer_host/input/.
 //
-struct CONTENT_EXPORT SyntheticGestureParams {
+struct SyntheticGestureParams {
   SyntheticGestureParams();
   SyntheticGestureParams(const SyntheticGestureParams& other);
   virtual ~SyntheticGestureParams();
diff --git a/content/common/mojo_core_library_support.h b/content/common/mojo_core_library_support.h
index 0ee2d6db..4919c96 100644
--- a/content/common/mojo_core_library_support.h
+++ b/content/common/mojo_core_library_support.h
@@ -6,7 +6,6 @@
 #define CONTENT_COMMON_MOJO_CORE_LIBRARY_SUPPORT_H_
 
 #include "base/files/file_path.h"
-#include "content/common/content_export.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace content {
@@ -14,12 +13,12 @@
 // Indicates whether the calling process was launched with the option to
 // initialize Mojo Core from a shared library rather than the statically linked
 // implementation.
-CONTENT_EXPORT bool IsMojoCoreSharedLibraryEnabled();
+bool IsMojoCoreSharedLibraryEnabled();
 
 // Returns the path to the Mojo Core shared library passed in on the command
 // line for the calling process, or null if the process was launched without a
 // Mojo Core library path on the command line.
-CONTENT_EXPORT absl::optional<base::FilePath> GetMojoCoreSharedLibraryPath();
+absl::optional<base::FilePath> GetMojoCoreSharedLibraryPath();
 
 }  // namespace content
 
diff --git a/content/common/set_process_title.h b/content/common/set_process_title.h
index 95db97d9..618d4bd9 100644
--- a/content/common/set_process_title.h
+++ b/content/common/set_process_title.h
@@ -5,8 +5,6 @@
 #ifndef CONTENT_COMMON_SET_PROCESS_TITLE_H_
 #define CONTENT_COMMON_SET_PROCESS_TITLE_H_
 
-#include "content/common/content_export.h"
-
 namespace content {
 
 // Sets OS-specific process title information based on the command line. This
@@ -23,7 +21,7 @@
 // makes the process name that shows up in "ps" etc. for the child processes
 // show as "exe" instead of "chrome" or something reasonable. This function
 // will try to fix it so the "effective" command line shows up instead.
-CONTENT_EXPORT void SetProcessTitleFromCommandLine(const char** main_argv);
+void SetProcessTitleFromCommandLine(const char** main_argv);
 
 }  // namespace content
 
diff --git a/content/common/shared_file_util.h b/content/common/shared_file_util.h
index a7ba1f9..8bd24c5 100644
--- a/content/common/shared_file_util.h
+++ b/content/common/shared_file_util.h
@@ -10,12 +10,11 @@
 
 #include "base/command_line.h"
 #include "base/component_export.h"
-#include "content/common/content_export.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace content {
 
-class CONTENT_EXPORT SharedFileSwitchValueBuilder final {
+class SharedFileSwitchValueBuilder final {
  public:
   void AddEntry(const std::string& key_str, int key_id);
   const std::string& switch_value() const { return switch_value_; }
@@ -24,7 +23,6 @@
   std::string switch_value_;
 };
 
-CONTENT_EXPORT
 absl::optional<std::map<int, std::string>> ParseSharedFileSwitchValue(
     const std::string& value);
 
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 217b7e7..fb35c99 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -878,7 +878,7 @@
     network::mojom::NetworkContextParams* network_context_params,
     cert_verifier::mojom::CertVerifierCreationParams*
         cert_verifier_creation_params) {
-  network_context_params->user_agent = GetUserAgent();
+  network_context_params->user_agent = GetUserAgentBasedOnPolicy(context);
   network_context_params->accept_language = "en-us,en";
 }
 
@@ -1070,6 +1070,11 @@
   return std::string();
 }
 
+std::string ContentBrowserClient::GetUserAgentBasedOnPolicy(
+    content::BrowserContext* content) {
+  return GetUserAgent();
+}
+
 std::string ContentBrowserClient::GetReducedUserAgent() {
   return GetUserAgent();
 }
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 18327d5..9c780cf 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -1886,10 +1886,16 @@
   // Used as part of the user agent string.
   virtual std::string GetProduct();
 
-  // Returns the user agent.Content may cache this value.
+  // Returns the user agent. This can also return the reduced user agent, based
+  // on blink::features::kUserAgentReduction. Content may cache this value.
   virtual std::string GetUserAgent();
 
-  // Returns the reduced user agent string. Defaults to |GetUserAgent| Content
+  // Returns the user agent, allowing for preferences (i.e. enterprise policy).
+  // Default to the non-context |GetUserAgent| above.
+  virtual std::string GetUserAgentBasedOnPolicy(
+      content::BrowserContext* context);
+
+  // Returns the reduced user agent string. Defaults to |GetUserAgent|. Content
   // may cache this value.
   virtual std::string GetReducedUserAgent();
 
diff --git a/content/renderer/discardable_memory_utils.h b/content/renderer/discardable_memory_utils.h
index c46dc04..6227b7e 100644
--- a/content/renderer/discardable_memory_utils.h
+++ b/content/renderer/discardable_memory_utils.h
@@ -15,12 +15,10 @@
 #include "base/synchronization/lock.h"
 #include "build/build_config.h"
 #include "components/discardable_memory/client/client_discardable_shared_memory_manager.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/remote.h"
 
 namespace content {
 
-CONTENT_EXPORT
 scoped_refptr<discardable_memory::ClientDiscardableSharedMemoryManager>
 CreateDiscardableMemoryAllocator();
 
diff --git a/content/renderer/frame_owner_properties_converter.h b/content/renderer/frame_owner_properties_converter.h
index 4123af20..e6e37d5 100644
--- a/content/renderer/frame_owner_properties_converter.h
+++ b/content/renderer/frame_owner_properties_converter.h
@@ -5,7 +5,6 @@
 #ifndef CONTENT_RENDERER_FRAME_OWNER_PROPERTIES_CONVERTER_H_
 #define CONTENT_RENDERER_FRAME_OWNER_PROPERTIES_CONVERTER_H_
 
-#include "content/common/content_export.h"
 #include "third_party/blink/public/mojom/frame/frame_owner_properties.mojom.h"
 #include "third_party/blink/public/web/web_frame_owner_properties.h"
 
@@ -19,15 +18,15 @@
 // blink::mojom::blink::FrameOwnerProperties can be used directly inside blink.
 
 template <>
-struct CONTENT_EXPORT TypeConverter<blink::WebFrameOwnerProperties,
-                                    blink::mojom::FrameOwnerProperties> {
+struct TypeConverter<blink::WebFrameOwnerProperties,
+                     blink::mojom::FrameOwnerProperties> {
   static blink::WebFrameOwnerProperties Convert(
       const blink::mojom::FrameOwnerProperties& mojo_properties);
 };
 
 template <>
-struct CONTENT_EXPORT TypeConverter<blink::mojom::FrameOwnerPropertiesPtr,
-                                    blink::WebFrameOwnerProperties> {
+struct TypeConverter<blink::mojom::FrameOwnerPropertiesPtr,
+                     blink::WebFrameOwnerProperties> {
   static blink::mojom::FrameOwnerPropertiesPtr Convert(
       const blink::WebFrameOwnerProperties& properties);
 };
diff --git a/content/renderer/media/cast_renderer_client_factory.h b/content/renderer/media/cast_renderer_client_factory.h
index fc05abb..f0cb425 100644
--- a/content/renderer/media/cast_renderer_client_factory.h
+++ b/content/renderer/media/cast_renderer_client_factory.h
@@ -9,7 +9,6 @@
 
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
-#include "content/common/content_export.h"
 #include "media/base/renderer.h"
 #include "media/base/renderer_factory.h"
 #include "ui/gfx/color_space.h"
@@ -24,7 +23,7 @@
 // Creates a renderer for chromecast.
 // This class creates a cast specific MojoRenderer from a MojoRendererFactory,
 // and wraps it within a DecryptingRenderer.
-class CONTENT_EXPORT CastRendererClientFactory : public media::RendererFactory {
+class CastRendererClientFactory : public media::RendererFactory {
  public:
   CastRendererClientFactory(
       media::MediaLog* media_log,
diff --git a/content/renderer/media/cast_renderer_factory.h b/content/renderer/media/cast_renderer_factory.h
index b768b6b..74351d5 100644
--- a/content/renderer/media/cast_renderer_factory.h
+++ b/content/renderer/media/cast_renderer_factory.h
@@ -9,7 +9,6 @@
 #include <vector>
 
 #include "base/callback_forward.h"
-#include "content/common/content_export.h"
 #include "media/base/renderer_factory.h"
 
 namespace blink {
@@ -29,7 +28,7 @@
 
 // RendererFactory implementation for Cast. This class is similar to
 // DefaultRendererFactory, but provides its own CastAudioRenderer for audio.
-class CONTENT_EXPORT CastRendererFactory final : public media::RendererFactory {
+class CastRendererFactory final : public media::RendererFactory {
  public:
   using GetGpuFactoriesCB =
       base::RepeatingCallback<media::GpuVideoAcceleratorFactories*()>;
diff --git a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h
index 6b02c4b8..e4a98e8 100644
--- a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h
+++ b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.h
@@ -18,7 +18,6 @@
 #include "base/synchronization/waitable_event.h"
 #include "base/unguessable_token.h"
 #include "components/viz/common/gpu/context_lost_observer.h"
-#include "content/common/content_export.h"
 #include "media/base/supported_video_decoder_config.h"
 #include "media/mojo/mojom/interface_factory.mojom.h"
 #include "media/mojo/mojom/video_decoder.mojom.h"
@@ -54,7 +53,7 @@
 // the |task_runner_|, as provided during construction.
 // |context_provider| should not support locking and will be bound to
 // |task_runner_| where all the operations on the context should also happen.
-class CONTENT_EXPORT GpuVideoAcceleratorFactoriesImpl
+class GpuVideoAcceleratorFactoriesImpl
     : public media::GpuVideoAcceleratorFactories,
       public viz::ContextLostObserver {
  public:
diff --git a/content/renderer/media/media_interface_factory.h b/content/renderer/media/media_interface_factory.h
index ec059c35..b05a894 100644
--- a/content/renderer/media/media_interface_factory.h
+++ b/content/renderer/media/media_interface_factory.h
@@ -10,7 +10,6 @@
 #include "base/task/single_thread_task_runner.h"
 #include "base/unguessable_token.h"
 #include "build/build_config.h"
-#include "content/common/content_export.h"
 #include "media/media_buildflags.h"
 #include "media/mojo/buildflags.h"
 #include "media/mojo/mojom/interface_factory.mojom.h"
@@ -28,8 +27,7 @@
 // MediaInterfaceFactory is an implementation of media::mojom::InterfaceFactory
 // that provides thread safety and handles disconnection error automatically.
 // The Create* methods can be called on any thread.
-class CONTENT_EXPORT MediaInterfaceFactory final
-    : public media::mojom::InterfaceFactory {
+class MediaInterfaceFactory final : public media::mojom::InterfaceFactory {
  public:
   explicit MediaInterfaceFactory(
       blink::BrowserInterfaceBrokerProxy* interface_broker);
diff --git a/content/renderer/media/media_permission_dispatcher.h b/content/renderer/media/media_permission_dispatcher.h
index 0b86d4a..c6d233c 100644
--- a/content/renderer/media/media_permission_dispatcher.h
+++ b/content/renderer/media/media_permission_dispatcher.h
@@ -12,7 +12,6 @@
 #include "base/callback.h"
 #include "base/memory/ref_counted.h"
 #include "base/memory/weak_ptr.h"
-#include "content/common/content_export.h"
 #include "content/renderer/render_frame_impl.h"
 #include "media/base/media_permission.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -25,7 +24,7 @@
 namespace content {
 
 // MediaPermission implementation using content PermissionService.
-class CONTENT_EXPORT MediaPermissionDispatcher : public media::MediaPermission {
+class MediaPermissionDispatcher : public media::MediaPermission {
  public:
   explicit MediaPermissionDispatcher(RenderFrameImpl* render_frame);
 
diff --git a/content/renderer/media/render_media_client.h b/content/renderer/media/render_media_client.h
index 6e5d48d..8b18159 100644
--- a/content/renderer/media/render_media_client.h
+++ b/content/renderer/media/render_media_client.h
@@ -5,7 +5,6 @@
 #ifndef CONTENT_RENDERER_MEDIA_RENDER_MEDIA_CLIENT_H_
 #define CONTENT_RENDERER_MEDIA_RENDER_MEDIA_CLIENT_H_
 
-#include "content/common/content_export.h"
 #include "media/base/audio_parameters.h"
 #include "media/base/media_client.h"
 
@@ -13,7 +12,7 @@
 
 // RenderMediaClient is purely plumbing to make content embedder customizations
 // visible to the lower media layer.
-class CONTENT_EXPORT RenderMediaClient : public media::MediaClient {
+class RenderMediaClient : public media::MediaClient {
  public:
   RenderMediaClient(const RenderMediaClient&) = delete;
   RenderMediaClient& operator=(const RenderMediaClient&) = delete;
diff --git a/content/renderer/media/render_media_event_handler.h b/content/renderer/media/render_media_event_handler.h
index 3077c62..7e30202f 100644
--- a/content/renderer/media/render_media_event_handler.h
+++ b/content/renderer/media/render_media_event_handler.h
@@ -7,7 +7,6 @@
 
 #include <vector>
 
-#include "content/common/content_export.h"
 #include "content/common/media/media_log_records.mojom.h"
 #include "content/renderer/media/batching_media_log.h"
 #include "mojo/public/cpp/bindings/remote.h"
@@ -16,8 +15,7 @@
 
 // RenderMediaEventHandler is an implementation of
 // BatchingMediaLog::EventHandler that forwards events to the browser process.
-class CONTENT_EXPORT RenderMediaEventHandler
-    : public BatchingMediaLog::EventHandler {
+class RenderMediaEventHandler : public BatchingMediaLog::EventHandler {
  public:
   RenderMediaEventHandler();
   ~RenderMediaEventHandler() override;
diff --git a/content/renderer/pepper/pepper_audio_encoder_host.h b/content/renderer/pepper/pepper_audio_encoder_host.h
index c88c133..d6f3cfb3 100644
--- a/content/renderer/pepper/pepper_audio_encoder_host.h
+++ b/content/renderer/pepper/pepper_audio_encoder_host.h
@@ -11,7 +11,6 @@
 
 #include "base/memory/ref_counted.h"
 #include "base/numerics/safe_math.h"
-#include "content/common/content_export.h"
 #include "ppapi/c/pp_codecs.h"
 #include "ppapi/host/host_message_context.h"
 #include "ppapi/host/resource_host.h"
@@ -23,7 +22,7 @@
 
 class RendererPpapiHost;
 
-class CONTENT_EXPORT PepperAudioEncoderHost
+class PepperAudioEncoderHost
     : public ppapi::host::ResourceHost,
       public ppapi::MediaStreamBufferManager::Delegate {
  public:
diff --git a/content/renderer/pepper/pepper_plugin_instance_impl.cc b/content/renderer/pepper/pepper_plugin_instance_impl.cc
index f08e521..ec8f6dd 100644
--- a/content/renderer/pepper/pepper_plugin_instance_impl.cc
+++ b/content/renderer/pepper/pepper_plugin_instance_impl.cc
@@ -21,7 +21,6 @@
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
-#include "build/chromeos_buildflags.h"
 #include "cc/layers/texture_layer.h"
 #include "content/common/content_constants_internal.h"
 #include "content/public/common/content_constants.h"
@@ -133,7 +132,7 @@
 #include "printing/metafile_skia.h"          // nogncheck
 #endif
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if defined(OS_CHROMEOS)
 #include "ui/events/keycodes/keyboard_codes_posix.h"
 #endif
 
@@ -322,7 +321,7 @@
 // all keys sent to them. This can prevent keystrokes from working for things
 // like screen brightness and volume control.
 bool IsReservedSystemInputEvent(const blink::WebInputEvent& event) {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if defined(OS_CHROMEOS)
   if (event.GetType() != WebInputEvent::Type::kKeyDown &&
       event.GetType() != WebInputEvent::Type::kKeyUp)
     return false;
@@ -342,7 +341,7 @@
   }
 #else
   return false;
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+#endif  // defined(OS_CHROMEOS)
 }
 
 void PrintPDFOutput(PP_Resource print_output,
diff --git a/content/renderer/pepper/pepper_video_decoder_host.h b/content/renderer/pepper/pepper_video_decoder_host.h
index b29a6d9..19abdde 100644
--- a/content/renderer/pepper/pepper_video_decoder_host.h
+++ b/content/renderer/pepper/pepper_video_decoder_host.h
@@ -13,7 +13,6 @@
 #include <set>
 #include <vector>
 
-#include "content/common/content_export.h"
 #include "gpu/command_buffer/common/mailbox.h"
 #include "media/video/video_decode_accelerator.h"
 #include "ppapi/c/pp_codecs.h"
@@ -26,9 +25,8 @@
 class RendererPpapiHost;
 class VideoDecoderShim;
 
-class CONTENT_EXPORT PepperVideoDecoderHost
-    : public ppapi::host::ResourceHost,
-      public media::VideoDecodeAccelerator::Client {
+class PepperVideoDecoderHost : public ppapi::host::ResourceHost,
+                               public media::VideoDecodeAccelerator::Client {
  public:
   PepperVideoDecoderHost(RendererPpapiHost* host,
                          PP_Instance instance,
diff --git a/content/renderer/pepper/pepper_video_encoder_host.h b/content/renderer/pepper/pepper_video_encoder_host.h
index 9ec723e..f6b54747 100644
--- a/content/renderer/pepper/pepper_video_encoder_host.h
+++ b/content/renderer/pepper/pepper_video_encoder_host.h
@@ -14,7 +14,6 @@
 #include "base/memory/ref_counted.h"
 #include "base/memory/shared_memory_mapping.h"
 #include "base/memory/unsafe_shared_memory_region.h"
-#include "content/common/content_export.h"
 #include "content/renderer/pepper/video_encoder_shim.h"
 #include "gpu/command_buffer/client/gpu_control_client.h"
 #include "ppapi/c/pp_codecs.h"
@@ -36,11 +35,10 @@
 
 class RendererPpapiHost;
 
-class CONTENT_EXPORT PepperVideoEncoderHost
-    : public ppapi::host::ResourceHost,
-      public VideoEncoderShim::Client,
-      public ppapi::MediaStreamBufferManager::Delegate,
-      public gpu::GpuControlClient {
+class PepperVideoEncoderHost : public ppapi::host::ResourceHost,
+                               public VideoEncoderShim::Client,
+                               public ppapi::MediaStreamBufferManager::Delegate,
+                               public gpu::GpuControlClient {
  public:
   PepperVideoEncoderHost(RendererPpapiHost* host,
                          PP_Instance instance,
diff --git a/content/renderer/pepper/pepper_websocket_host.h b/content/renderer/pepper/pepper_websocket_host.h
index 62f80f51..151514f8 100644
--- a/content/renderer/pepper/pepper_websocket_host.h
+++ b/content/renderer/pepper/pepper_websocket_host.h
@@ -11,7 +11,6 @@
 #include <queue>
 
 #include "base/memory/ref_counted.h"
-#include "content/common/content_export.h"
 #include "ppapi/host/host_message_context.h"
 #include "ppapi/host/resource_host.h"
 #include "ppapi/proxy/resource_message_params.h"
@@ -22,9 +21,8 @@
 
 class RendererPpapiHost;
 
-class CONTENT_EXPORT PepperWebSocketHost
-    : public ppapi::host::ResourceHost,
-      public ::blink::WebPepperSocketClient {
+class PepperWebSocketHost : public ppapi::host::ResourceHost,
+                            public ::blink::WebPepperSocketClient {
  public:
   explicit PepperWebSocketHost(RendererPpapiHost* host,
                                PP_Instance instance,
diff --git a/content/renderer/pepper/ppb_image_data_impl.h b/content/renderer/pepper/ppb_image_data_impl.h
index ab12851f..8cb3bb2d 100644
--- a/content/renderer/pepper/ppb_image_data_impl.h
+++ b/content/renderer/pepper/ppb_image_data_impl.h
@@ -11,7 +11,6 @@
 
 #include "base/memory/shared_memory_mapping.h"
 #include "base/memory/unsafe_shared_memory_region.h"
-#include "content/common/content_export.h"
 #include "ppapi/c/ppb_image_data.h"
 #include "ppapi/shared_impl/ppb_image_data_shared.h"
 #include "ppapi/shared_impl/resource.h"
@@ -23,10 +22,9 @@
 
 namespace content {
 
-class CONTENT_EXPORT PPB_ImageData_Impl
-    : public ppapi::Resource,
-      public ppapi::PPB_ImageData_Shared,
-      public ppapi::thunk::PPB_ImageData_API {
+class PPB_ImageData_Impl : public ppapi::Resource,
+                           public ppapi::PPB_ImageData_Shared,
+                           public ppapi::thunk::PPB_ImageData_API {
  public:
   // We delegate most of our implementation to a back-end class that either uses
   // a PlatformCanvas (for most trusted stuff) or bare shared memory (for use by
diff --git a/content/renderer/service_worker/embedded_worker_instance_client_impl.h b/content/renderer/service_worker/embedded_worker_instance_client_impl.h
index f8e2936..65d66bd 100644
--- a/content/renderer/service_worker/embedded_worker_instance_client_impl.h
+++ b/content/renderer/service_worker/embedded_worker_instance_client_impl.h
@@ -8,7 +8,6 @@
 #include <memory>
 
 #include "content/child/child_thread_impl.h"
-#include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "third_party/blink/public/mojom/service_worker/embedded_worker.mojom.h"
@@ -27,7 +26,7 @@
 // service worker to stop and then deletes itself.
 //
 // Created and lives on a ThreadPool background thread.
-class CONTENT_EXPORT EmbeddedWorkerInstanceClientImpl
+class EmbeddedWorkerInstanceClientImpl
     : public blink::mojom::EmbeddedWorkerInstanceClient {
  public:
   // Enum for UMA to record when StartWorker is received.
diff --git a/content/renderer/service_worker/service_worker_context_client.h b/content/renderer/service_worker/service_worker_context_client.h
index 88f5311..dc943b1 100644
--- a/content/renderer/service_worker/service_worker_context_client.h
+++ b/content/renderer/service_worker/service_worker_context_client.h
@@ -17,7 +17,6 @@
 #include "base/gtest_prod_util.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/time/time.h"
-#include "content/common/content_export.h"
 #include "ipc/ipc_listener.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
@@ -71,8 +70,7 @@
 //
 // Unless otherwise noted (here or in base class documentation), all methods
 // are called on the worker thread.
-class CONTENT_EXPORT ServiceWorkerContextClient
-    : public blink::WebServiceWorkerContextClient {
+class ServiceWorkerContextClient : public blink::WebServiceWorkerContextClient {
  public:
   // Called on the initiator thread.
   // - |is_starting_installed_worker| is true if the script is already installed
diff --git a/content/renderer/service_worker/service_worker_network_provider_for_frame.h b/content/renderer/service_worker/service_worker_network_provider_for_frame.h
index d0dfc1cd..79afbdf 100644
--- a/content/renderer/service_worker/service_worker_network_provider_for_frame.h
+++ b/content/renderer/service_worker/service_worker_network_provider_for_frame.h
@@ -7,7 +7,6 @@
 
 #include <memory>
 
-#include "content/common/content_export.h"
 #include "content/renderer/service_worker/service_worker_provider_context.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "third_party/blink/public/mojom/service_worker/controller_service_worker.mojom-forward.h"
@@ -18,7 +17,7 @@
 class RenderFrameImpl;
 
 // The WebServiceWorkerNetworkProvider implementation used for frames.
-class CONTENT_EXPORT ServiceWorkerNetworkProviderForFrame final
+class ServiceWorkerNetworkProviderForFrame final
     : public blink::WebServiceWorkerNetworkProvider {
  public:
   // Creates a network provider for |frame|.
diff --git a/content/renderer/webgraphicscontext3d_provider_impl.h b/content/renderer/webgraphicscontext3d_provider_impl.h
index 0f0a9a8..b7466c4 100644
--- a/content/renderer/webgraphicscontext3d_provider_impl.h
+++ b/content/renderer/webgraphicscontext3d_provider_impl.h
@@ -9,7 +9,6 @@
 #include "base/containers/flat_map.h"
 #include "base/memory/ref_counted.h"
 #include "components/viz/common/gpu/context_provider.h"
-#include "content/common/content_export.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
 #include "gpu/command_buffer/client/raster_interface.h"
 #include "gpu/command_buffer/client/webgpu_interface.h"
@@ -29,7 +28,7 @@
 
 namespace content {
 
-class CONTENT_EXPORT WebGraphicsContext3DProviderImpl
+class WebGraphicsContext3DProviderImpl
     : public blink::WebGraphicsContext3DProvider,
       public viz::ContextLostObserver {
  public:
diff --git a/content/services/auction_worklet/auction_v8_helper.cc b/content/services/auction_worklet/auction_v8_helper.cc
index 7a77c23..25e7dca 100644
--- a/content/services/auction_worklet/auction_v8_helper.cc
+++ b/content/services/auction_worklet/auction_v8_helper.cc
@@ -77,34 +77,27 @@
 }
 
 // Helper class to notify debugger of context creation/destruction.
-// Does nothing if passed in `inspector` is nullptr or `context_group_id` is
-// kNoDebugContextGroupId.
+// Does nothing if passed in `inspector` is nullptr or `debug_id` is nullptr.
 class DebugContextScope {
  public:
   DebugContextScope(v8_inspector::V8Inspector* inspector,
                     v8::Local<v8::Context> context,
-                    int context_group_id,
+                    const AuctionV8Helper::DebugId* debug_id,
                     const std::string& name)
-      : inspector_(inspector),
-        context_(context),
-        context_group_id_(context_group_id) {
-    if (!inspector_ ||
-        context_group_id_ == AuctionV8Helper::kNoDebugContextGroupId) {
+      : inspector_(inspector), context_(context), debug_id_(debug_id) {
+    if (!inspector_ || !debug_id_)
       return;
-    }
 
     v8_inspector::V8ContextInfo context_info(
-        context_, context_group_id_,
+        context_, debug_id_->context_group_id(),
         v8_inspector::StringView(reinterpret_cast<const uint8_t*>(name.data()),
                                  name.size()));
     inspector_->contextCreated(context_info);
   }
 
   ~DebugContextScope() {
-    if (!inspector_ ||
-        context_group_id_ == AuctionV8Helper::kNoDebugContextGroupId) {
+    if (!inspector_ || !debug_id_)
       return;
-    }
 
     inspector_->contextDestroyed(context_);
   }
@@ -115,7 +108,7 @@
  private:
   v8_inspector::V8Inspector* const inspector_;
   const v8::Local<v8::Context> context_;
-  const int context_group_id_;
+  const AuctionV8Helper::DebugId* const debug_id_;
 };
 
 }  // namespace
@@ -267,8 +260,6 @@
 constexpr base::TimeDelta AuctionV8Helper::kScriptTimeout =
     base::Milliseconds(50);
 
-const int AuctionV8Helper::kNoDebugContextGroupId;
-
 AuctionV8Helper::FullIsolateScope::FullIsolateScope(AuctionV8Helper* v8_helper)
     : locker_(v8_helper->isolate()),
       isolate_scope_(v8_helper->isolate()),
@@ -276,6 +267,19 @@
 
 AuctionV8Helper::FullIsolateScope::~FullIsolateScope() = default;
 
+AuctionV8Helper::DebugId::DebugId(AuctionV8Helper* v8_helper)
+    : v8_helper_(v8_helper),
+      context_group_id_(v8_helper->AllocContextGroupId()) {}
+
+void AuctionV8Helper::DebugId::SetResumeCallback(
+    base::OnceClosure resume_callback) {
+  v8_helper_->SetResumeCallback(context_group_id_, std::move(resume_callback));
+}
+
+AuctionV8Helper::DebugId::~DebugId() {
+  v8_helper_->FreeContextGroupId(context_group_id_);
+}
+
 // static
 scoped_refptr<AuctionV8Helper> AuctionV8Helper::Create(
     scoped_refptr<base::SingleThreadTaskRunner> v8_runner) {
@@ -421,13 +425,13 @@
 v8::MaybeLocal<v8::UnboundScript> AuctionV8Helper::Compile(
     const std::string& src,
     const GURL& src_url,
-    int context_group_id,
+    const DebugId* debug_id,
     absl::optional<std::string>& error_out) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   v8::Isolate* v8_isolate = isolate();
 
   DebugContextScope maybe_debug(inspector(), v8_isolate->GetCurrentContext(),
-                                context_group_id, src_url.spec());
+                                debug_id, src_url.spec());
 
   v8::MaybeLocal<v8::String> src_string = CreateUtf8String(src);
   v8::MaybeLocal<v8::String> origin_string = CreateUtf8String(src_url.spec());
@@ -452,7 +456,7 @@
 v8::MaybeLocal<v8::Value> AuctionV8Helper::RunScript(
     v8::Local<v8::Context> context,
     v8::Local<v8::UnboundScript> script,
-    int context_group_id,
+    const DebugId* debug_id,
     base::StringPiece function_name,
     base::span<v8::Local<v8::Value>> args,
     std::vector<std::string>& error_out) {
@@ -460,8 +464,7 @@
   DCHECK_EQ(isolate(), context->GetIsolate());
 
   std::string script_name = FormatScriptName(script);
-  DebugContextScope maybe_debug(inspector(), context, context_group_id,
-                                script_name);
+  DebugContextScope maybe_debug(inspector(), context, debug_id, script_name);
   ScopedConsoleTarget direct_console(this, script_name, &error_out);
 
   v8::Local<v8::String> v8_function_name;
@@ -517,12 +520,12 @@
 }
 
 void AuctionV8Helper::MaybeTriggerInstrumentationBreakpoint(
-    int context_group_id,
+    const DebugId& debug_id,
     const std::string& name) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (devtools_agent_) {
-    devtools_agent_->MaybeTriggerInstrumentationBreakpoint(context_group_id,
-                                                           name);
+    devtools_agent_->MaybeTriggerInstrumentationBreakpoint(
+        debug_id.context_group_id(), name);
   }
 }
 
@@ -532,9 +535,8 @@
   script_timeout_ = script_timeout;
 }
 
-int AuctionV8Helper::AllocContextGroupIdAndSetResumeCallback(
-    base::OnceClosure resume_callback) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+int AuctionV8Helper::AllocContextGroupId() {
+  base::AutoLock hold_lock(context_groups_lock_);
   while (true) {
     if (last_context_group_id_ == std::numeric_limits<int>::max())
       last_context_group_id_ = 0;
@@ -542,33 +544,59 @@
     DCHECK_GT(candidate, 0);
 
     if (resume_callbacks_.find(candidate) == resume_callbacks_.end()) {
-      resume_callbacks_.emplace(candidate, std::move(resume_callback));
+      resume_callbacks_.emplace(candidate, base::OnceClosure());
       return candidate;
     }
   }
 }
 
-void AuctionV8Helper::FreeContextGroupId(int context_group_id) {
+void AuctionV8Helper::SetResumeCallback(int context_group_id,
+                                        base::OnceClosure resume_callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  base::AutoLock hold_lock(context_groups_lock_);
+  auto it = resume_callbacks_.find(context_group_id);
+  DCHECK(it != resume_callbacks_.end());
+  DCHECK(it->second.is_null());
+  it->second = std::move(resume_callback);
+}
+
+void AuctionV8Helper::FreeContextGroupId(int context_group_id) {
+  base::AutoLock hold_lock(context_groups_lock_);
   size_t removed = resume_callbacks_.erase(context_group_id);
   DCHECK_EQ(1u, removed);
 }
 
 void AuctionV8Helper::Resume(int context_group_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  auto it = resume_callbacks_.find(context_group_id);
-  if (it == resume_callbacks_.end())
-    return;
+  base::OnceClosure resume_closure;
+  {
+    base::AutoLock hold_lock(context_groups_lock_);
+    auto it = resume_callbacks_.find(context_group_id);
+    if (it == resume_callbacks_.end())
+      return;
 
-  if (it->second)
-    std::move(it->second).Run();
+    resume_closure = std::move(it->second);
+  }
+
+  if (resume_closure)
+    std::move(resume_closure).Run();
+}
+
+void AuctionV8Helper::SetLastContextGroupIdForTesting(int new_last_id) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  base::AutoLock hold_lock(context_groups_lock_);
+  last_context_group_id_ = new_last_id;
 }
 
 void AuctionV8Helper::ResumeAllForTesting() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   std::vector<int> live_ids;
-  for (const auto& kv : resume_callbacks_)
-    live_ids.push_back(kv.first);
+  {
+    base::AutoLock hold_lock(context_groups_lock_);
+    for (const auto& kv : resume_callbacks_)
+      live_ids.push_back(kv.first);
+  }
 
   for (int id : live_ids)
     Resume(id);
@@ -577,7 +605,7 @@
 void AuctionV8Helper::ConnectDevToolsAgent(
     mojo::PendingReceiver<blink::mojom::DevToolsAgent> agent,
     scoped_refptr<base::SequencedTaskRunner> mojo_sequence,
-    int context_group_id) {
+    const DebugId& debug_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!devtools_agent_) {
     devtools_agent_ = std::make_unique<AuctionV8DevToolsAgent>(
@@ -585,7 +613,7 @@
     v8_inspector_ =
         v8_inspector::V8Inspector::create(isolate(), devtools_agent_.get());
   }
-  devtools_agent_->Connect(std::move(agent), context_group_id);
+  devtools_agent_->Connect(std::move(agent), debug_id.context_group_id());
 }
 
 v8_inspector::V8Inspector* AuctionV8Helper::inspector() {
diff --git a/content/services/auction_worklet/auction_v8_helper.h b/content/services/auction_worklet/auction_v8_helper.h
index c7d4b469..ed4dc0b 100644
--- a/content/services/auction_worklet/auction_v8_helper.h
+++ b/content/services/auction_worklet/auction_v8_helper.h
@@ -12,10 +12,13 @@
 
 #include "base/compiler_specific.h"
 #include "base/containers/span.h"
+#include "base/memory/ref_counted.h"
 #include "base/memory/ref_counted_delete_on_sequence.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/sequence_checker.h"
 #include "base/strings/string_piece.h"
+#include "base/synchronization/lock.h"
+#include "base/task/sequenced_task_runner.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/time/time.h"
 #include "content/services/auction_worklet/console.h"
@@ -49,18 +52,16 @@
 // Currently, multiple AuctionV8Helpers can be in use at once, each will have
 // its own V8 isolate.  All AuctionV8Helpers are assumed to be created on the
 // same thread (V8 startup is done only once per process, and not behind a
-// lock).  After creation, all operations on the helper must be done on the
-// thread represented by the `v8_runner` argument to Create(); it's the caller's
-// responsibility to ensure the methods are invoked there.
+// lock).  After creation, all public operations on the helper must be done on
+// the thread represented by the `v8_runner` argument to Create(). It's the
+// caller's responsibility to ensure that all other methods are used from the v8
+// runner.
 class AuctionV8Helper
     : public base::RefCountedDeleteOnSequence<AuctionV8Helper> {
  public:
   // Timeout for script execution.
   static const base::TimeDelta kScriptTimeout;
 
-  // Debugger context group ID asking for no debugging.
-  static const int kNoDebugContextGroupId = 0;
-
   // Helper class to set up v8 scopes to use Isolate. All methods expect a
   // FullIsolateScope to be have been created on the current thread, and a
   // context to be entered.
@@ -77,6 +78,34 @@
     const v8::HandleScope handle_scope_;
   };
 
+  // A wrapper for identifiers used to associate V8 context's with debugging
+  // primitives.  Passed to methods like Compile and RunScript.
+  //
+  // This class is thread-safe, except SetResumeCallback must be used from V8
+  // thread.
+  class DebugId : public base::RefCountedThreadSafe<DebugId> {
+   public:
+    explicit DebugId(AuctionV8Helper* v8_helper);
+
+    // Returns V8 context group ID associated with this debug id.
+    int context_group_id() const { return context_group_id_; }
+
+    // Sets the callback to use to resume a worklet that's paused on startup.
+    // Must be called from the V8 thread.
+    //
+    // `resume_callback` will be invoked on the V8 thread; and should probably
+    // be bound to a a WeakPtr, since the invocation is ultimately via debugger
+    // mojo pipes, making its timing hard to relate to worklet lifetime.
+    void SetResumeCallback(base::OnceClosure resume_callback);
+
+   private:
+    friend class base::RefCountedThreadSafe<DebugId>;
+    ~DebugId();
+
+    const scoped_refptr<AuctionV8Helper> v8_helper_;
+    const int context_group_id_;
+  };
+
   explicit AuctionV8Helper(const AuctionV8Helper&) = delete;
   AuctionV8Helper& operator=(const AuctionV8Helper&) = delete;
 
@@ -154,7 +183,7 @@
   v8::MaybeLocal<v8::UnboundScript> Compile(
       const std::string& src,
       const GURL& src_url,
-      int context_group_id,
+      const DebugId* debug_id,
       absl::optional<std::string>& error_out);
 
   // Binds a script and runs it in the passed in context, returning the result.
@@ -162,8 +191,8 @@
   // functions contained within the context, so is likely not safe to use in
   // other contexts without sanitization.
   //
-  // If `context_group_id` is not kNoDebugContextGroupId (0), and a debugger
-  // connection has been instantiated, will notify debugger of `context`.
+  // If `debug_id` is not nullptr, and a debugger connection has been
+  // instantiated, will notify debugger of `context`.
   //
   // Assumes passed in context is the active context. Passed in context must be
   // using the Helper's isolate.
@@ -174,17 +203,17 @@
   // In case of an error or console output sets `error_out`.
   v8::MaybeLocal<v8::Value> RunScript(v8::Local<v8::Context> context,
                                       v8::Local<v8::UnboundScript> script,
-                                      int context_group_id,
+                                      const DebugId* debug_id,
                                       base::StringPiece function_name,
                                       base::span<v8::Local<v8::Value>> args,
                                       std::vector<std::string>& error_out);
 
-  // If any debugging session targeting `context_group_id` has set an active
+  // If any debugging session targeting `debug_id` has set an active
   // DOM instrumentation breakpoint `name`, asks for v8 to do a debugger pause
   // on the next statement.
   //
   // Expected to be run before a corresponding RunScript.
-  void MaybeTriggerInstrumentationBreakpoint(int context_group_id,
+  void MaybeTriggerInstrumentationBreakpoint(const DebugId& debug_id,
                                              const std::string& name);
 
   void set_script_timeout_for_testing(base::TimeDelta script_timeout);
@@ -205,37 +234,19 @@
     return console_script_name_;
   }
 
-  // V8 Debug functionality identifies what to operate on via numeric
-  // "context group IDs".
-
-  // Grabs an ID for a particular consumer, and sets the callback to use to
-  // resume its execution if it was paused on start.  Since Resume() can be
-  // called over Mojo pipes that are unordered with respect to main worklet Mojo
-  // pipes, the callback should probably be bound to a WeakPtr.
-  //
-  // Returned ID will be a positive integer.
-  int AllocContextGroupIdAndSetResumeCallback(
-      base::OnceClosure resume_callback);
-
-  // Frees up an ID that'll no longer be in use.
-  void FreeContextGroupId(int context_group_id);
-
   // Invokes the registered resume callback for given ID. Does nothing if it
   // was already invoked.
   void Resume(int context_group_id);
 
   // Overrides what ID will be remembered as last returned to help check the
   // allocation algorithm.
-  void SetLastContextGroupIdForTesting(int new_last_id) {
-    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-    last_context_group_id_ = new_last_id;
-  }
+  void SetLastContextGroupIdForTesting(int new_last_id);
 
   // Calls Resume on all registered context group IDs.
   void ResumeAllForTesting();
 
   // Establishes a debugger connection, initializing debugging objects if
-  // needed, and associating the connection with the given `context_group_id`.
+  // needed, and associating the connection with the given `debug_id`.
   //
   // The debugger Mojo objects will primarily live on the v8 thread, but
   // `mojo_sequence` will be used for a secondary communication channel in case
@@ -245,7 +256,7 @@
   void ConnectDevToolsAgent(
       mojo::PendingReceiver<blink::mojom::DevToolsAgent> agent,
       scoped_refptr<base::SequencedTaskRunner> mojo_sequence,
-      int context_group_id);
+      const DebugId& debug_id);
 
   // Returns the v8 inspector if one has been set. null if ConnectDevToolsAgent
   // (or SetV8InspectorForTesting) hasn't been called.
@@ -293,6 +304,13 @@
 
   void CreateIsolate();
 
+  // These methods are used by DebugId, and except SetResumeCallback can be
+  // called from any thread.
+  int AllocContextGroupId();
+  void SetResumeCallback(int context_group_id,
+                         base::OnceClosure resume_callback);
+  void FreeContextGroupId(int context_group_id);
+
   static std::string FormatExceptionMessage(v8::Local<v8::Context> context,
                                             v8::Local<v8::Message> message);
   static std::string FormatValue(v8::Isolate* isolate,
@@ -318,11 +336,12 @@
   ScriptTimeoutHelper* timeout_helper_ GUARDED_BY_CONTEXT(sequence_checker_) =
       nullptr;
 
-  int last_context_group_id_ GUARDED_BY_CONTEXT(sequence_checker_) = 0;
+  base::Lock context_groups_lock_;
+  int last_context_group_id_ GUARDED_BY(context_groups_lock_) = 0;
 
   // This is keyed by group IDs, and is used to keep track of what's valid.
   std::map<int, base::OnceClosure> resume_callbacks_
-      GUARDED_BY_CONTEXT(sequence_checker_);
+      GUARDED_BY(context_groups_lock_);
 
   std::unique_ptr<DebugCommandQueue> debug_command_queue_
       GUARDED_BY_CONTEXT(sequence_checker_);
diff --git a/content/services/auction_worklet/auction_v8_helper_unittest.cc b/content/services/auction_worklet/auction_v8_helper_unittest.cc
index 9278e46..d4ce77e 100644
--- a/content/services/auction_worklet/auction_v8_helper_unittest.cc
+++ b/content/services/auction_worklet/auction_v8_helper_unittest.cc
@@ -49,17 +49,19 @@
   ~AuctionV8HelperTest() override = default;
 
   void CompileAndRunScriptOnV8Thread(
-      int context_group_id,
+      scoped_refptr<AuctionV8Helper::DebugId> debug_id,
       const std::string& function_name,
       const GURL& url,
       const std::string& body,
       bool expect_success = true,
       base::OnceClosure done = base::OnceClosure(),
       int* result_out = nullptr) {
+    DCHECK(debug_id);
     helper_->v8_runner()->PostTask(
         FROM_HERE,
         base::BindOnce(
-            [](scoped_refptr<AuctionV8Helper> helper, int context_group_id,
+            [](scoped_refptr<AuctionV8Helper> helper,
+               scoped_refptr<AuctionV8Helper::DebugId> debug_id,
                std::string function_name, GURL url, std::string body,
                bool expect_success, base::OnceClosure done, int* result_out) {
               AuctionV8Helper::FullIsolateScope isolate_scope(helper.get());
@@ -68,7 +70,7 @@
                 v8::Context::Scope ctx(helper->scratch_context());
                 absl::optional<std::string> error_msg;
                 ASSERT_TRUE(
-                    helper->Compile(body, url, context_group_id, error_msg)
+                    helper->Compile(body, url, debug_id.get(), error_msg)
                         .ToLocal(&script));
                 EXPECT_FALSE(error_msg.has_value());
               }
@@ -78,12 +80,11 @@
               v8::Local<v8::Value> result;
               // This is here since it needs to be before RunScript() ---
               // doing it before Compile() doesn't work.
-              helper->MaybeTriggerInstrumentationBreakpoint(context_group_id,
-                                                            "start");
-              helper->MaybeTriggerInstrumentationBreakpoint(context_group_id,
+              helper->MaybeTriggerInstrumentationBreakpoint(*debug_id, "start");
+              helper->MaybeTriggerInstrumentationBreakpoint(*debug_id,
                                                             "start2");
               bool success = helper
-                                 ->RunScript(context, script, context_group_id,
+                                 ->RunScript(context, script, debug_id.get(),
                                              function_name,
                                              base::span<v8::Local<v8::Value>>(),
                                              error_msgs)
@@ -104,25 +105,27 @@
               if (!done.is_null())
                 std::move(done).Run();
             },
-            helper_, context_group_id, function_name, url, body, expect_success,
-            std::move(done), result_out));
+            helper_, std::move(debug_id), function_name, url, body,
+            expect_success, std::move(done), result_out));
   }
 
   void ConnectToDevToolsAgent(
-      int context_group_id,
+      scoped_refptr<AuctionV8Helper::DebugId> debug_id,
       mojo::PendingReceiver<blink::mojom::DevToolsAgent> agent_receiver) {
+    DCHECK(debug_id);
     helper_->v8_runner()->PostTask(
         FROM_HERE,
         base::BindOnce(
             [](scoped_refptr<AuctionV8Helper> helper,
                mojo::PendingReceiver<blink::mojom::DevToolsAgent>
                    agent_receiver,
-               scoped_refptr<base::SequencedTaskRunner> mojo_thread, int id) {
+               scoped_refptr<base::SequencedTaskRunner> mojo_thread,
+               scoped_refptr<AuctionV8Helper::DebugId> debug_id) {
               helper->ConnectDevToolsAgent(std::move(agent_receiver),
-                                           std::move(mojo_thread), id);
+                                           std::move(mojo_thread), *debug_id);
             },
             helper_, std::move(agent_receiver),
-            base::SequencedTaskRunnerHandle::Get(), context_group_id));
+            base::SequencedTaskRunnerHandle::Get(), std::move(debug_id)));
   }
 
  protected:
@@ -138,11 +141,11 @@
   {
     v8::Context::Scope ctx(helper_->scratch_context());
     absl::optional<std::string> error_msg;
-    ASSERT_TRUE(
-        helper_
-            ->Compile("function foo() { return 1;}", GURL("https://foo.test/"),
-                      AuctionV8Helper::kNoDebugContextGroupId, error_msg)
-            .ToLocal(&script));
+    ASSERT_TRUE(helper_
+                    ->Compile("function foo() { return 1;}",
+                              GURL("https://foo.test/"),
+                              /*debug_id=*/nullptr, error_msg)
+                    .ToLocal(&script));
     EXPECT_FALSE(error_msg.has_value());
   }
 
@@ -153,7 +156,7 @@
     v8::Local<v8::Value> result;
     ASSERT_TRUE(helper_
                     ->RunScript(context, script,
-                                AuctionV8Helper::kNoDebugContextGroupId, "foo",
+                                /*debug_id=*/nullptr, "foo",
                                 base::span<v8::Local<v8::Value>>(), error_msgs)
                     .ToLocal(&result));
     int int_result = 0;
@@ -198,15 +201,14 @@
     absl::optional<std::string> compile_error;
     ASSERT_TRUE(helper_
                     ->Compile(hanging_script.script, GURL("https://foo.test/"),
-                              AuctionV8Helper::kNoDebugContextGroupId,
-                              compile_error)
+                              /*debug_id=*/nullptr, compile_error)
                     .ToLocal(&script));
     EXPECT_FALSE(compile_error.has_value());
 
     std::vector<std::string> error_msgs;
-    v8::MaybeLocal<v8::Value> result = helper_->RunScript(
-        context, script, AuctionV8Helper::kNoDebugContextGroupId, "foo",
-        base::span<v8::Local<v8::Value>>(), error_msgs);
+    v8::MaybeLocal<v8::Value> result =
+        helper_->RunScript(context, script, /*debug_id=*/nullptr, "foo",
+                           base::span<v8::Local<v8::Value>>(), error_msgs);
     EXPECT_TRUE(result.IsEmpty());
     EXPECT_THAT(
         error_msgs,
@@ -228,18 +230,18 @@
   v8::Context::Scope context_scope(context);
   v8::Local<v8::UnboundScript> script;
   absl::optional<std::string> compile_error;
-  ASSERT_TRUE(
-      helper_
-          ->Compile("function foo() { return 1;}", GURL("https://foo.test/"),
-                    AuctionV8Helper::kNoDebugContextGroupId, compile_error)
-          .ToLocal(&script));
+  ASSERT_TRUE(helper_
+                  ->Compile("function foo() { return 1;}",
+                            GURL("https://foo.test/"),
+                            /*debug_id=*/nullptr, compile_error)
+                  .ToLocal(&script));
   EXPECT_FALSE(compile_error.has_value());
 
   std::vector<std::string> error_msgs;
   v8::Local<v8::Value> result;
   ASSERT_TRUE(helper_
                   ->RunScript(context, script,
-                              AuctionV8Helper::kNoDebugContextGroupId, "foo",
+                              /*debug_id=*/nullptr, "foo",
                               base::span<v8::Local<v8::Value>>(), error_msgs)
                   .ToLocal(&result));
   EXPECT_TRUE(error_msgs.empty());
@@ -260,14 +262,13 @@
   ASSERT_TRUE(helper_
                   ->Compile("function foo() { return Date();}",
                             GURL("https://foo.test/"),
-                            AuctionV8Helper::kNoDebugContextGroupId,
-                            compile_error)
+                            /*debug_id=*/nullptr, compile_error)
                   .ToLocal(&script));
   EXPECT_FALSE(compile_error.has_value());
   std::vector<std::string> error_msgs;
   EXPECT_TRUE(helper_
                   ->RunScript(context, script,
-                              AuctionV8Helper::kNoDebugContextGroupId, "foo",
+                              /*debug_id=*/nullptr, "foo",
                               base::span<v8::Local<v8::Value>>(), error_msgs)
                   .IsEmpty());
   ASSERT_EQ(1u, error_msgs.size());
@@ -283,7 +284,7 @@
   absl::optional<std::string> error_msg;
   ASSERT_FALSE(helper_
                    ->Compile("function foo() { ", GURL("https://foo.test/"),
-                             AuctionV8Helper::kNoDebugContextGroupId, error_msg)
+                             /*debug_id=*/nullptr, error_msg)
                    .ToLocal(&script));
   ASSERT_TRUE(error_msg.has_value());
   EXPECT_THAT(error_msg.value(), StartsWith("https://foo.test/:1 "));
@@ -299,8 +300,7 @@
     ASSERT_TRUE(helper_
                     ->Compile("\n\nthrow new Error('I am an error');",
                               GURL("https://foo.test/"),
-                              AuctionV8Helper::kNoDebugContextGroupId,
-                              error_msg)
+                              /*debug_id=*/nullptr, error_msg)
                     .ToLocal(&script));
     EXPECT_FALSE(error_msg.has_value());
   }
@@ -311,7 +311,7 @@
   v8::Local<v8::Value> result;
   ASSERT_FALSE(helper_
                    ->RunScript(context, script,
-                               AuctionV8Helper::kNoDebugContextGroupId, "foo",
+                               /*debug_id=*/nullptr, "foo",
                                base::span<v8::Local<v8::Value>>(), error_msgs)
                    .ToLocal(&result));
   EXPECT_THAT(
@@ -325,11 +325,11 @@
   {
     v8::Context::Scope ctx(helper_->scratch_context());
     absl::optional<std::string> error_msg;
-    ASSERT_TRUE(
-        helper_
-            ->Compile("function foo() { return 1;}", GURL("https://foo.test/"),
-                      AuctionV8Helper::kNoDebugContextGroupId, error_msg)
-            .ToLocal(&script));
+    ASSERT_TRUE(helper_
+                    ->Compile("function foo() { return 1;}",
+                              GURL("https://foo.test/"),
+                              /*debug_id=*/nullptr, error_msg)
+                    .ToLocal(&script));
     EXPECT_FALSE(error_msg.has_value());
   }
 
@@ -340,7 +340,7 @@
   v8::Local<v8::Value> result;
   ASSERT_FALSE(helper_
                    ->RunScript(context, script,
-                               AuctionV8Helper::kNoDebugContextGroupId, "bar",
+                               /*debug_id=*/nullptr, "bar",
                                base::span<v8::Local<v8::Value>>(), error_msgs)
                    .ToLocal(&result));
 
@@ -358,8 +358,7 @@
     ASSERT_TRUE(helper_
                     ->Compile("function foo() { return notfound;}",
                               GURL("https://foo.test/"),
-                              AuctionV8Helper::kNoDebugContextGroupId,
-                              error_msg)
+                              /*debug_id=*/nullptr, error_msg)
                     .ToLocal(&script));
     EXPECT_FALSE(error_msg.has_value());
   }
@@ -371,7 +370,7 @@
   v8::Local<v8::Value> result;
   ASSERT_FALSE(helper_
                    ->RunScript(context, script,
-                               AuctionV8Helper::kNoDebugContextGroupId, "foo",
+                               /*debug_id=*/nullptr, "foo",
                                base::span<v8::Local<v8::Value>>(), error_msgs)
                    .ToLocal(&result));
   ASSERT_EQ(1u, error_msgs.size());
@@ -400,8 +399,7 @@
     absl::optional<std::string> error_msg;
     ASSERT_TRUE(helper_
                     ->Compile(kScript, GURL("https://foo.test/"),
-                              AuctionV8Helper::kNoDebugContextGroupId,
-                              error_msg)
+                              /*debug_id=*/nullptr, error_msg)
                     .ToLocal(&script));
     EXPECT_FALSE(error_msg.has_value());
   }
@@ -413,7 +411,7 @@
   v8::Local<v8::Value> result;
   ASSERT_FALSE(helper_
                    ->RunScript(context, script,
-                               AuctionV8Helper::kNoDebugContextGroupId, "foo",
+                               /*debug_id=*/nullptr, "foo",
                                base::span<v8::Local<v8::Value>>(), error_msgs)
                    .ToLocal(&result));
   ASSERT_EQ(error_msgs.size(), 6u);
@@ -434,7 +432,7 @@
   ASSERT_TRUE(helper_
                   ->Compile("function foo() { return 1;}",
                             GURL("https://foo.test:8443/foo.js?v=3"),
-                            AuctionV8Helper::kNoDebugContextGroupId, error_msg)
+                            /*debug_id=*/nullptr, error_msg)
                   .ToLocal(&script));
   EXPECT_EQ("https://foo.test:8443/foo.js?v=3",
             helper_->FormatScriptName(script));
@@ -445,67 +443,64 @@
   base::RepeatingClosure count_resume_callback_invocation =
       base::BindLambdaForTesting([&]() { ++resume_callback_invocations; });
 
-  int id1 = helper_->AllocContextGroupIdAndSetResumeCallback(
-      count_resume_callback_invocation);
-  EXPECT_NE(AuctionV8Helper::kNoDebugContextGroupId, id1);
+  auto id1 = base::MakeRefCounted<AuctionV8Helper::DebugId>(helper_.get());
+  id1->SetResumeCallback(count_resume_callback_invocation);
+  EXPECT_GT(id1->context_group_id(), 0);
   ASSERT_EQ(0, resume_callback_invocations);
 
   // Invoking resume the first time invokes the callback.
-  helper_->Resume(id1);
+  helper_->Resume(id1->context_group_id());
   ASSERT_EQ(1, resume_callback_invocations);
 
   // Later invocations don't do anything.
-  helper_->Resume(id1);
+  helper_->Resume(id1->context_group_id());
   ASSERT_EQ(1, resume_callback_invocations);
 
-  helper_->FreeContextGroupId(id1);
   // ... including after free.
-  helper_->Resume(id1);
+  int save_id1 = id1->context_group_id();
+  id1.reset();
+  helper_->Resume(save_id1);
   ASSERT_EQ(1, resume_callback_invocations);
 
   // Or before allocation.
-  helper_->Resume(id1 + 1);
+  helper_->Resume(save_id1 + 1);
   ASSERT_EQ(1, resume_callback_invocations);
 
   // Try with free before Resume call, too.
-  int id2 = helper_->AllocContextGroupIdAndSetResumeCallback(
-      count_resume_callback_invocation);
-  EXPECT_NE(AuctionV8Helper::kNoDebugContextGroupId, id2);
+  auto id2 = base::MakeRefCounted<AuctionV8Helper::DebugId>(helper_.get());
+  id2->SetResumeCallback(count_resume_callback_invocation);
+  EXPECT_GT(id2->context_group_id(), 0);
   ASSERT_EQ(1, resume_callback_invocations);
-  helper_->FreeContextGroupId(id2);
-  helper_->Resume(id2);
+  int save_id2 = id2->context_group_id();
+  id2.reset();
+  helper_->Resume(save_id2);
   ASSERT_EQ(1, resume_callback_invocations);
 
   // Rudimentary test that two live IDs aren't the same.
-  int id3 = helper_->AllocContextGroupIdAndSetResumeCallback(
-      count_resume_callback_invocation);
-  int id4 = helper_->AllocContextGroupIdAndSetResumeCallback(
-      count_resume_callback_invocation);
-  EXPECT_NE(AuctionV8Helper::kNoDebugContextGroupId, id3);
-  EXPECT_NE(AuctionV8Helper::kNoDebugContextGroupId, id4);
-  EXPECT_NE(id3, id4);
-  helper_->Resume(id4);
+  auto id3 = base::MakeRefCounted<AuctionV8Helper::DebugId>(helper_.get());
+  id3->SetResumeCallback(count_resume_callback_invocation);
+  auto id4 = base::MakeRefCounted<AuctionV8Helper::DebugId>(helper_.get());
+  id4->SetResumeCallback(count_resume_callback_invocation);
+  int save_id3 = id3->context_group_id();
+  int save_id4 = id4->context_group_id();
+  EXPECT_GT(save_id3, 0);
+  EXPECT_GT(save_id4, 0);
+  EXPECT_NE(save_id3, save_id4);
+  helper_->Resume(save_id4);
   ASSERT_EQ(2, resume_callback_invocations);
-  helper_->Resume(id3);
+  helper_->Resume(save_id3);
   ASSERT_EQ(3, resume_callback_invocations);
-  helper_->FreeContextGroupId(id3);
-  helper_->FreeContextGroupId(id4);
 }
 
 TEST_F(AuctionV8HelperTest, AllocWrap) {
   // Check what the ID allocator does when numbers wrap around and collide.
-  int id1 =
-      helper_->AllocContextGroupIdAndSetResumeCallback(base::OnceClosure());
-  EXPECT_GT(id1, 0);
+  auto id1 = base::MakeRefCounted<AuctionV8Helper::DebugId>(helper_.get());
+  EXPECT_GT(id1->context_group_id(), 0);
   helper_->SetLastContextGroupIdForTesting(std::numeric_limits<int>::max());
-  int id2 =
-      helper_->AllocContextGroupIdAndSetResumeCallback(base::OnceClosure());
+  auto id2 = base::MakeRefCounted<AuctionV8Helper::DebugId>(helper_.get());
   // `id2` should be positive and distinct from `id1`.
-  EXPECT_GT(id2, 0);
-  EXPECT_NE(id1, id2);
-
-  helper_->FreeContextGroupId(id1);
-  helper_->FreeContextGroupId(id2);
+  EXPECT_GT(id2->context_group_id(), 0);
+  EXPECT_NE(id1->context_group_id(), id2->context_group_id());
 }
 
 TEST_F(AuctionV8HelperTest, DebuggerBasics) {
@@ -518,8 +513,9 @@
   helper_ = AuctionV8Helper::Create(AuctionV8Helper::CreateTaskRunner());
   ScopedInspectorSupport inspector_support(helper_.get());
 
-  int id = AllocContextGroupIdAndWait(helper_);
-  TestChannel* channel = inspector_support.ConnectDebuggerSession(id);
+  auto id = base::MakeRefCounted<AuctionV8Helper::DebugId>(helper_.get());
+  TestChannel* channel =
+      inspector_support.ConnectDebuggerSession(id->context_group_id());
   channel->RunCommandAndWaitForResult(
       1, "Runtime.enable", R"({"id":1,"method":"Runtime.enable","params":{}})");
   channel->RunCommandAndWaitForResult(
@@ -573,8 +569,6 @@
       source_response.value.FindStringPath("result.scriptSource");
   ASSERT_TRUE(parsed_src);
   EXPECT_EQ(kScriptSrc, *parsed_src);
-
-  FreeContextGroupIdAndWait(helper_, id);
 }
 
 TEST_F(AuctionV8HelperTest, DebugCompileError) {
@@ -586,8 +580,9 @@
   helper_ = AuctionV8Helper::Create(AuctionV8Helper::CreateTaskRunner());
   ScopedInspectorSupport inspector_support(helper_.get());
 
-  int id = AllocContextGroupIdAndWait(helper_);
-  TestChannel* channel = inspector_support.ConnectDebuggerSession(id);
+  auto id = base::MakeRefCounted<AuctionV8Helper::DebugId>(helper_.get());
+  TestChannel* channel =
+      inspector_support.ConnectDebuggerSession(id->context_group_id());
   channel->RunCommandAndWaitForResult(
       1, "Runtime.enable", R"({"id":1,"method":"Runtime.enable","params":{}})");
   channel->RunCommandAndWaitForResult(
@@ -597,15 +592,16 @@
   helper_->v8_runner()->PostTask(
       FROM_HERE,
       base::BindOnce(
-          [](scoped_refptr<AuctionV8Helper> helper, int context_group_id,
-             std::string url, std::string body) {
+          [](scoped_refptr<AuctionV8Helper> helper,
+             scoped_refptr<AuctionV8Helper::DebugId> debug_id, std::string url,
+             std::string body) {
             AuctionV8Helper::FullIsolateScope isolate_scope(helper.get());
             v8::Local<v8::UnboundScript> script;
             {
               v8::Context::Scope ctx(helper->scratch_context());
               absl::optional<std::string> error_msg;
               ASSERT_FALSE(
-                  helper->Compile(body, GURL(url), context_group_id, error_msg)
+                  helper->Compile(body, GURL(url), debug_id.get(), error_msg)
                       .ToLocal(&script));
             }
           },
@@ -620,8 +616,6 @@
 
   TestChannel::Event context_destroyed_event =
       channel->WaitForMethodNotification("Runtime.executionContextDestroyed");
-
-  FreeContextGroupIdAndWait(helper_, id);
 }
 
 TEST_F(AuctionV8HelperTest, DevToolsDebuggerBasics) {
@@ -639,7 +633,7 @@
     v8_scope_.reset();
     helper_ = AuctionV8Helper::Create(AuctionV8Helper::CreateTaskRunner());
 
-    int id = AllocContextGroupIdAndWait(helper_);
+    auto id = base::MakeRefCounted<AuctionV8Helper::DebugId>(helper_.get());
 
     mojo::Remote<blink::mojom::DevToolsAgent> agent_remote;
     ConnectToDevToolsAgent(id, agent_remote.BindNewPipeAndPassReceiver());
@@ -726,8 +720,6 @@
     // Produced value changed by the write to `multiplier`.
     result_run_loop.Run();
     EXPECT_EQ(30, result);
-
-    FreeContextGroupIdAndWait(helper_, id);
   }
 }
 
@@ -749,7 +741,7 @@
       v8_scope_.reset();
       helper_ = AuctionV8Helper::Create(AuctionV8Helper::CreateTaskRunner());
 
-      int id = AllocContextGroupIdAndWait(helper_);
+      auto id = base::MakeRefCounted<AuctionV8Helper::DebugId>(helper_.get());
 
       mojo::Remote<blink::mojom::DevToolsAgent> agent_remote;
       ConnectToDevToolsAgent(id, agent_remote.BindNewPipeAndPassReceiver());
@@ -847,8 +839,6 @@
       // Wait for result.
       result_run_loop.Run();
       EXPECT_EQ(42, result);
-
-      FreeContextGroupIdAndWait(helper_, id);
     }
   }
 }
@@ -861,7 +851,7 @@
     v8_scope_.reset();
     helper_ = AuctionV8Helper::Create(AuctionV8Helper::CreateTaskRunner());
 
-    int id = AllocContextGroupIdAndWait(helper_);
+    auto id = base::MakeRefCounted<AuctionV8Helper::DebugId>(helper_.get());
 
     mojo::Remote<blink::mojom::DevToolsAgent> agent_remote;
     ConnectToDevToolsAgent(id, agent_remote.BindNewPipeAndPassReceiver());
@@ -873,7 +863,6 @@
             TestDevToolsAgentClient::Channel::kMain, 1, "NoSuchThing.enable",
             R"({"id":1,"method":"NoSuchThing.enable","params":{}})");
     EXPECT_TRUE(result.value.FindDictKey("error"));
-    FreeContextGroupIdAndWait(helper_, id);
   }
 }
 
@@ -885,7 +874,7 @@
   v8_scope_.reset();
   helper_ = AuctionV8Helper::Create(AuctionV8Helper::CreateTaskRunner());
 
-  int id = AllocContextGroupIdAndWait(helper_);
+  auto id = base::MakeRefCounted<AuctionV8Helper::DebugId>(helper_.get());
 
   mojo::Remote<blink::mojom::DevToolsAgent> agent_remote;
   ConnectToDevToolsAgent(id, agent_remote.BindNewPipeAndPassReceiver());
@@ -894,7 +883,7 @@
                                        use_binary_protocol);
   task_environment_.RunUntilIdle();
 
-  FreeContextGroupIdAndWait(helper_, id);
+  id.reset();
   helper_.reset();
   task_environment_.RunUntilIdle();
 }
@@ -932,7 +921,7 @@
   v8_scope_.reset();
   helper_ = AuctionV8Helper::Create(AuctionV8Helper::CreateTaskRunner());
 
-  int id = AllocContextGroupIdAndWait(helper_);
+  auto id = base::MakeRefCounted<AuctionV8Helper::DebugId>(helper_.get());
   mojo::Remote<blink::mojom::DevToolsAgent> agent_remote;
   ConnectToDevToolsAgent(id, agent_remote.BindNewPipeAndPassReceiver());
 
@@ -978,8 +967,6 @@
 
   result_run_loop.Run();
   EXPECT_EQ(3, result);
-
-  FreeContextGroupIdAndWait(helper_, id);
 }
 
 TEST_F(AuctionV8HelperTest, DebugTimeout) {
@@ -1000,7 +987,7 @@
   v8_scope_.reset();
   helper_ = AuctionV8Helper::Create(AuctionV8Helper::CreateTaskRunner());
 
-  int id = AllocContextGroupIdAndWait(helper_);
+  auto id = base::MakeRefCounted<AuctionV8Helper::DebugId>(helper_.get());
   mojo::Remote<blink::mojom::DevToolsAgent> agent_remote;
   ConnectToDevToolsAgent(id, agent_remote.BindNewPipeAndPassReceiver());
 
@@ -1044,8 +1031,6 @@
 
   result_run_loop.Run();
   EXPECT_EQ(-1, result);
-
-  FreeContextGroupIdAndWait(helper_, id);
 }
 
 }  // namespace auction_worklet
diff --git a/content/services/auction_worklet/bidder_worklet.cc b/content/services/auction_worklet/bidder_worklet.cc
index d6b27cc4..680e243 100644
--- a/content/services/auction_worklet/bidder_worklet.cc
+++ b/content/services/auction_worklet/bidder_worklet.cc
@@ -151,7 +151,8 @@
     mojom::BiddingInterestGroupPtr bidding_interest_group)
     : v8_runner_(v8_helper->v8_runner()),
       v8_helper_(v8_helper),
-      context_group_id_(AuctionV8Helper::kNoDebugContextGroupId),
+      debug_id_(
+          base::MakeRefCounted<AuctionV8Helper::DebugId>(v8_helper.get())),
       // TODO(mmenke): Remove up the value_or() for script_source_url_; auction
       // worklets shouldn't be created when there's no bidding URL.
       script_source_url_(
@@ -166,12 +167,14 @@
   url_loader_factory_.Bind(std::move(pending_url_loader_factory));
 
   v8_state_ = std::unique_ptr<V8State, base::OnTaskRunnerDeleter>(
-      new V8State(v8_helper, script_source_url_, weak_ptr_factory_.GetWeakPtr(),
+      new V8State(v8_helper, debug_id_, script_source_url_,
+                  weak_ptr_factory_.GetWeakPtr(),
                   std::move(bidding_interest_group)),
       base::OnTaskRunnerDeleter(v8_runner_));
 
   paused_ = pause_for_debugger_on_start;
-  // DeliverContextGroupIdOnUserThread will call StartIfReady().
+  if (!paused_)
+    Start();
 }
 
 BidderWorklet::~BidderWorklet() {
@@ -184,6 +187,10 @@
   FailAllPendingTasks();
 }
 
+int BidderWorklet::context_group_id_for_testing() const {
+  return debug_id_->context_group_id();
+}
+
 void BidderWorklet::GenerateBid(
     const absl::optional<std::string>& auction_signals_json,
     const absl::optional<std::string>& per_buyer_signals_json,
@@ -276,10 +283,12 @@
 
 BidderWorklet::V8State::V8State(
     scoped_refptr<AuctionV8Helper> v8_helper,
+    scoped_refptr<AuctionV8Helper::DebugId> debug_id,
     const GURL& script_source_url,
     base::WeakPtr<BidderWorklet> parent,
     mojom::BiddingInterestGroupPtr bidding_interest_group)
     : v8_helper_(std::move(v8_helper)),
+      debug_id_(std::move(debug_id)),
       parent_(std::move(parent)),
       user_thread_(base::SequencedTaskRunnerHandle::Get()),
       bidding_interest_group_(std::move(bidding_interest_group)),
@@ -352,9 +361,9 @@
   // value indicates no exception.
   std::vector<std::string> errors_out;
   v8_helper_->MaybeTriggerInstrumentationBreakpoint(
-      context_group_id_, "beforeBidderWorkletReportingStart");
+      *debug_id_, "beforeBidderWorkletReportingStart");
   if (v8_helper_
-          ->RunScript(context, worklet_script_.Get(isolate), context_group_id_,
+          ->RunScript(context, worklet_script_.Get(isolate), debug_id_.get(),
                       "reportWin", args, errors_out)
           .IsEmpty()) {
     PostReportWinCallbackToUserThread(std::move(callback),
@@ -481,9 +490,9 @@
   v8::Local<v8::Value> generate_bid_result;
   std::vector<std::string> errors_out;
   v8_helper_->MaybeTriggerInstrumentationBreakpoint(
-      context_group_id_, "beforeBidderWorkletBiddingStart");
+      *debug_id_, "beforeBidderWorkletBiddingStart");
   if (!v8_helper_
-           ->RunScript(context, worklet_script_.Get(isolate), context_group_id_,
+           ->RunScript(context, worklet_script_.Get(isolate), debug_id_.get(),
                        "generateBid", args, errors_out)
            .ToLocal(&generate_bid_result)) {
     PostErrorBidCallbackToUserThread(std::move(callback),
@@ -605,24 +614,17 @@
 void BidderWorklet::V8State::ConnectDevToolsAgent(
     mojo::PendingReceiver<blink::mojom::DevToolsAgent> agent) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
-  v8_helper_->ConnectDevToolsAgent(std::move(agent), user_thread_,
-                                   context_group_id_);
+  v8_helper_->ConnectDevToolsAgent(std::move(agent), user_thread_, *debug_id_);
 }
 
 BidderWorklet::V8State::~V8State() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
-  v8_helper_->FreeContextGroupId(context_group_id_);
 }
 
 void BidderWorklet::V8State::FinishInit() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
-  context_group_id_ = v8_helper_->AllocContextGroupIdAndSetResumeCallback(
-      base::BindOnce(&BidderWorklet::V8State::PostResumeToUserThread, parent_,
-                     user_thread_));
-  user_thread_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&BidderWorklet::DeliverContextGroupIdOnUserThread, parent_,
-                     context_group_id_));
+  debug_id_->SetResumeCallback(base::BindOnce(
+      &BidderWorklet::V8State::PostResumeToUserThread, parent_, user_thread_));
 }
 
 // static
@@ -661,18 +663,15 @@
     return;
 
   paused_ = false;
-  StartIfReady();
+  Start();
 }
 
-void BidderWorklet::StartIfReady() {
+void BidderWorklet::Start() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
-  if (paused_ || context_group_id_ == AuctionV8Helper::kNoDebugContextGroupId) {
-    return;
-  }
+  DCHECK(!paused_);
 
   worklet_loader_ = std::make_unique<WorkletLoader>(
-      url_loader_factory_.get(), script_source_url_, v8_helper_,
-      context_group_id_,
+      url_loader_factory_.get(), script_source_url_, v8_helper_, debug_id_,
       base::BindOnce(&BidderWorklet::OnScriptDownloaded,
                      base::Unretained(this)));
 }
@@ -759,13 +758,6 @@
                          weak_ptr_factory_.GetWeakPtr(), task)));
 }
 
-void BidderWorklet::DeliverContextGroupIdOnUserThread(int context_group_id) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
-  context_group_id_ = context_group_id;
-  DCHECK_NE(AuctionV8Helper::kNoDebugContextGroupId, context_group_id_);
-  StartIfReady();
-}
-
 void BidderWorklet::FailAllPendingTasks() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
   while (!generate_bid_tasks_.empty()) {
diff --git a/content/services/auction_worklet/bidder_worklet.h b/content/services/auction_worklet/bidder_worklet.h
index 9a4a464..8ff5d16e 100644
--- a/content/services/auction_worklet/bidder_worklet.h
+++ b/content/services/auction_worklet/bidder_worklet.h
@@ -13,7 +13,9 @@
 
 #include "base/callback.h"
 #include "base/containers/unique_ptr_adapters.h"
+#include "base/memory/scoped_refptr.h"
 #include "base/time/time.h"
+#include "content/services/auction_worklet/auction_v8_helper.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
 #include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
 #include "content/services/auction_worklet/trusted_bidding_signals.h"
@@ -34,8 +36,6 @@
 
 namespace auction_worklet {
 
-class AuctionV8Helper;
-
 // Represents a bidder worklet for FLEDGE
 // (https://github.com/WICG/turtledove/blob/main/FLEDGE.md). Loads and runs the
 // bidder worklet's Javascript.
@@ -69,9 +69,7 @@
   ~BidderWorklet() override;
   BidderWorklet& operator=(const BidderWorklet&) = delete;
 
-  // Warning: The caller may need to spin the event loop for this to get
-  // initialized to a value different from kNoDebugContextGroupId.
-  int context_group_id_for_testing() const { return context_group_id_; }
+  int context_group_id_for_testing() const;
 
   // mojom::BidderWorklet implementation:
   void GenerateBid(const absl::optional<std::string>& auction_signals_json,
@@ -137,6 +135,7 @@
   class V8State {
    public:
     V8State(scoped_refptr<AuctionV8Helper> v8_helper,
+            scoped_refptr<AuctionV8Helper::DebugId> debug_id,
             const GURL& script_source_url,
             base::WeakPtr<BidderWorklet> parent,
             mojom::BiddingInterestGroupPtr bidding_interest_group);
@@ -192,6 +191,7 @@
         scoped_refptr<base::SequencedTaskRunner> user_thread);
 
     const scoped_refptr<AuctionV8Helper> v8_helper_;
+    const scoped_refptr<AuctionV8Helper::DebugId> debug_id_;
     const base::WeakPtr<BidderWorklet> parent_;
     const scoped_refptr<base::SequencedTaskRunner> user_thread_;
 
@@ -203,13 +203,11 @@
 
     const GURL script_source_url_;
 
-    int context_group_id_;
-
     SEQUENCE_CHECKER(v8_sequence_checker_);
   };
 
   void ResumeIfPaused();
-  void StartIfReady();
+  void Start();
 
   void OnScriptDownloaded(WorkletLoader::Result worklet_script,
                           absl::optional<std::string> error_msg);
@@ -228,8 +226,6 @@
 
   void RunReportWin(ReportWinTaskList::iterator task);
 
-  void DeliverContextGroupIdOnUserThread(int context_group_id);
-
   // Fails all pending GenerateBid() and ReportWin() tasks, removing all tasks
   // from both lists.
   void FailAllPendingTasks();
@@ -248,15 +244,12 @@
 
   scoped_refptr<base::SequencedTaskRunner> v8_runner_;
 
-  scoped_refptr<AuctionV8Helper> v8_helper_;
+  const scoped_refptr<AuctionV8Helper> v8_helper_;
+  const scoped_refptr<AuctionV8Helper::DebugId> debug_id_;
   mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory_;
 
   bool paused_;
 
-  // `context_group_id_` starts at kNoDebugContextGroupId, but then gets
-  // initialized after some thread hops.
-  int context_group_id_;
-
   const GURL script_source_url_;
 
   // True until `worklet_loader_` has completed loading (successfully or
diff --git a/content/services/auction_worklet/bidder_worklet_unittest.cc b/content/services/auction_worklet/bidder_worklet_unittest.cc
index a12bf96..6ca17fb9 100644
--- a/content/services/auction_worklet/bidder_worklet_unittest.cc
+++ b/content/services/auction_worklet/bidder_worklet_unittest.cc
@@ -248,13 +248,6 @@
     return bidding_interest_group;
   }
 
-  int LookUpContextGroupId(BidderWorklet* worklet_impl) {
-    task_environment_.RunUntilIdle();
-    int id = worklet_impl->context_group_id_for_testing();
-    CHECK_NE(AuctionV8Helper::kNoDebugContextGroupId, id);
-    return id;
-  }
-
   // Create a BidderWorklet, returning the remote. If `out_bidder_worklet_impl`
   // is non-null, will also stash the actual implementation pointer there.
   // if `url` is empty, uses `interest_group_bidding_url_`.
@@ -1937,7 +1930,7 @@
                     /* pause_for_debugger_on_start=*/true, &worklet_impl);
   GenerateBid(worklet.get());
   // Grab the context group ID to be able to resume.
-  int id = LookUpContextGroupId(worklet_impl);
+  int id = worklet_impl->context_group_id_for_testing();
 
   // Give it a chance to fetch.
   task_environment_.RunUntilIdle();
@@ -1980,7 +1973,7 @@
   task_environment_.RunUntilIdle();
 
   // Grab the context group ID.
-  int id = LookUpContextGroupId(worklet_impl);
+  int id = worklet_impl->context_group_id_for_testing();
 
   // Delete the worklet. No callback should be invoked.
   worklet.reset();
@@ -2024,8 +2017,8 @@
       GURL(kUrl2), /*pause_for_debugger_on_start=*/true, &worklet_impl2);
   GenerateBid(worklet2.get());
 
-  int id1 = LookUpContextGroupId(worklet_impl1);
-  int id2 = LookUpContextGroupId(worklet_impl2);
+  int id1 = worklet_impl1->context_group_id_for_testing();
+  int id2 = worklet_impl2->context_group_id_for_testing();
 
   TestChannel* channel1 = inspector_support.ConnectDebuggerSession(id1);
   TestChannel* channel2 = inspector_support.ConnectDebuggerSession(id2);
@@ -2109,7 +2102,7 @@
       CreateWorklet(interest_group_bidding_url_,
                     /*pause_for_debugger_on_start=*/true, &worklet_impl);
   GenerateBid(worklet.get());
-  int id = LookUpContextGroupId(worklet_impl);
+  int id = worklet_impl->context_group_id_for_testing();
   TestChannel* channel = inspector_support.ConnectDebuggerSession(id);
 
   channel->RunCommandAndWaitForResult(
diff --git a/content/services/auction_worklet/seller_worklet.cc b/content/services/auction_worklet/seller_worklet.cc
index ec38c43..22ab312 100644
--- a/content/services/auction_worklet/seller_worklet.cc
+++ b/content/services/auction_worklet/seller_worklet.cc
@@ -125,20 +125,23 @@
         load_worklet_callback)
     : v8_runner_(v8_helper->v8_runner()),
       v8_helper_(v8_helper),
+      debug_id_(
+          base::MakeRefCounted<AuctionV8Helper::DebugId>(v8_helper.get())),
       pending_url_loader_factory_(std::move(pending_url_loader_factory)),
       script_source_url_(script_source_url),
-      context_group_id_(AuctionV8Helper::kNoDebugContextGroupId),
       v8_state_(nullptr, base::OnTaskRunnerDeleter(v8_runner_)),
       load_worklet_callback_(std::move(load_worklet_callback)) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
   DCHECK(load_worklet_callback_);
 
   v8_state_ = std::unique_ptr<V8State, base::OnTaskRunnerDeleter>(
-      new V8State(v8_helper, script_source_url, weak_ptr_factory_.GetWeakPtr()),
+      new V8State(v8_helper_, debug_id_, script_source_url,
+                  weak_ptr_factory_.GetWeakPtr()),
       base::OnTaskRunnerDeleter(v8_runner_));
 
   paused_ = pause_for_debugger_on_start;
-  // DeliverContextGroupIdOnUserThread will call StartIfReady().
+  if (!paused_)
+    Start();
 }
 
 SellerWorklet::~SellerWorklet() {
@@ -149,6 +152,10 @@
   }
 }
 
+int SellerWorklet::context_group_id_for_testing() const {
+  return debug_id_->context_group_id();
+}
+
 void SellerWorklet::ScoreAd(
     const std::string& ad_metadata_json,
     double bid,
@@ -198,10 +205,13 @@
                      base::Unretained(v8_state_.get()), std::move(agent)));
 }
 
-SellerWorklet::V8State::V8State(scoped_refptr<AuctionV8Helper> v8_helper,
-                                GURL script_source_url,
-                                base::WeakPtr<SellerWorklet> parent)
+SellerWorklet::V8State::V8State(
+    scoped_refptr<AuctionV8Helper> v8_helper,
+    scoped_refptr<AuctionV8Helper::DebugId> debug_id,
+    GURL script_source_url,
+    base::WeakPtr<SellerWorklet> parent)
     : v8_helper_(std::move(v8_helper)),
+      debug_id_(debug_id),
       parent_(std::move(parent)),
       user_thread_(base::SequencedTaskRunnerHandle::Get()),
       script_source_url_(std::move(script_source_url)) {
@@ -285,9 +295,9 @@
   double score;
   std::vector<std::string> errors_out;
   v8_helper_->MaybeTriggerInstrumentationBreakpoint(
-      context_group_id_, "beforeSellerWorkletScoringStart");
+      *debug_id_, "beforeSellerWorkletScoringStart");
   if (!v8_helper_
-           ->RunScript(context, worklet_script_.Get(isolate), context_group_id_,
+           ->RunScript(context, worklet_script_.Get(isolate), debug_id_.get(),
                        "scoreAd", args, errors_out)
            .ToLocal(&score_ad_result)) {
     PostScoreAdCallbackToUserThread(std::move(callback), 0 /* score */,
@@ -368,9 +378,9 @@
   v8::Local<v8::Value> signals_for_winner_value;
   std::vector<std::string> errors_out;
   v8_helper_->MaybeTriggerInstrumentationBreakpoint(
-      context_group_id_, "beforeSellerWorkletReportingStart");
+      *debug_id_, "beforeSellerWorkletReportingStart");
   if (!v8_helper_
-           ->RunScript(context, worklet_script_.Get(isolate), context_group_id_,
+           ->RunScript(context, worklet_script_.Get(isolate), debug_id_.get(),
                        "reportResult", args, errors_out)
            .ToLocal(&signals_for_winner_value)) {
     PostReportResultCallbackToUserThread(
@@ -395,24 +405,17 @@
 void SellerWorklet::V8State::ConnectDevToolsAgent(
     mojo::PendingReceiver<blink::mojom::DevToolsAgent> agent) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
-  v8_helper_->ConnectDevToolsAgent(std::move(agent), user_thread_,
-                                   context_group_id_);
+  v8_helper_->ConnectDevToolsAgent(std::move(agent), user_thread_, *debug_id_);
 }
 
 SellerWorklet::V8State::~V8State() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
-  v8_helper_->FreeContextGroupId(context_group_id_);
 }
 
 void SellerWorklet::V8State::FinishInit() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(v8_sequence_checker_);
-  context_group_id_ = v8_helper_->AllocContextGroupIdAndSetResumeCallback(
-      base::BindOnce(&SellerWorklet::V8State::PostResumeToUserThread, parent_,
-                     user_thread_));
-  user_thread_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&SellerWorklet::DeliverContextGroupIdOnUserThread, parent_,
-                     context_group_id_));
+  debug_id_->SetResumeCallback(base::BindOnce(
+      &SellerWorklet::V8State::PostResumeToUserThread, parent_, user_thread_));
 }
 
 // static
@@ -461,14 +464,12 @@
     return;
 
   paused_ = false;
-  StartIfReady();
+  Start();
 }
 
-void SellerWorklet::StartIfReady() {
+void SellerWorklet::Start() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
-  if (paused_ || context_group_id_ == AuctionV8Helper::kNoDebugContextGroupId) {
-    return;
-  }
+  DCHECK(!paused_);
 
   // Bind URLLoaderFactory. Remote is not needed after this method completes,
   // since requests will continue after the URLLoaderFactory pipe has been
@@ -477,8 +478,7 @@
       std::move(pending_url_loader_factory_));
 
   worklet_loader_ = std::make_unique<WorkletLoader>(
-      url_loader_factory.get(), script_source_url_, std::move(v8_helper_),
-      context_group_id_,
+      url_loader_factory.get(), script_source_url_, v8_helper_, debug_id_,
       base::BindOnce(&SellerWorklet::OnDownloadComplete,
                      base::Unretained(this)));
 }
@@ -503,13 +503,6 @@
   std::move(load_worklet_callback_).Run(success, errors);
 }
 
-void SellerWorklet::DeliverContextGroupIdOnUserThread(int context_group_id) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(user_sequence_checker_);
-  context_group_id_ = context_group_id;
-  DCHECK_NE(AuctionV8Helper::kNoDebugContextGroupId, context_group_id_);
-  StartIfReady();
-}
-
 void SellerWorklet::DeliverScoreAdCallbackOnUserThread(
     ScoreAdCallback callback,
     double score,
diff --git a/content/services/auction_worklet/seller_worklet.h b/content/services/auction_worklet/seller_worklet.h
index 8f10041..3f3eae4 100644
--- a/content/services/auction_worklet/seller_worklet.h
+++ b/content/services/auction_worklet/seller_worklet.h
@@ -12,6 +12,7 @@
 
 #include "base/callback.h"
 #include "base/sequence_checker.h"
+#include "content/services/auction_worklet/auction_v8_helper.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom-forward.h"
 #include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
 #include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
@@ -30,8 +31,6 @@
 
 namespace auction_worklet {
 
-class AuctionV8Helper;
-
 // Represents a seller worklet for FLEDGE
 // (https://github.com/WICG/turtledove/blob/main/FLEDGE.md). Loads and runs the
 // seller worklet's Javascript.
@@ -52,9 +51,7 @@
 
   ~SellerWorklet() override;
 
-  // Warning: The caller may need to spin the event loop for this to get
-  // initialized to a value different from kNoDebugContextGroupId.
-  int context_group_id_for_testing() const { return context_group_id_; }
+  int context_group_id_for_testing() const;
 
   // mojom::SellerWorklet implementation:
   void ScoreAd(const std::string& ad_metadata_json,
@@ -82,6 +79,7 @@
   class V8State {
    public:
     V8State(scoped_refptr<AuctionV8Helper> v8_helper,
+            scoped_refptr<AuctionV8Helper::DebugId> debug_id,
             GURL script_source_url,
             base::WeakPtr<SellerWorklet> parent);
 
@@ -129,6 +127,7 @@
         scoped_refptr<base::SequencedTaskRunner> user_thread);
 
     const scoped_refptr<AuctionV8Helper> v8_helper_;
+    const scoped_refptr<AuctionV8Helper::DebugId> debug_id_;
     const base::WeakPtr<SellerWorklet> parent_;
     const scoped_refptr<base::SequencedTaskRunner> user_thread_;
 
@@ -138,18 +137,15 @@
 
     const GURL script_source_url_;
 
-    int context_group_id_;
-
     SEQUENCE_CHECKER(v8_sequence_checker_);
   };
 
   void ResumeIfPaused();
-  void StartIfReady();
+  void Start();
 
   void OnDownloadComplete(WorkletLoader::Result worklet_script,
                           absl::optional<std::string> error_msg);
 
-  void DeliverContextGroupIdOnUserThread(int context_group_id);
   void DeliverScoreAdCallbackOnUserThread(ScoreAdCallback callback,
                                           double score,
                                           std::vector<std::string> errors);
@@ -161,18 +157,16 @@
 
   scoped_refptr<base::SequencedTaskRunner> v8_runner_;
 
-  // Kept around until Start().
   scoped_refptr<AuctionV8Helper> v8_helper_;
+  scoped_refptr<AuctionV8Helper::DebugId> debug_id_;
+
+  // Kept around until Start().
   mojo::PendingRemote<network::mojom::URLLoaderFactory>
       pending_url_loader_factory_;
 
   const GURL script_source_url_;
   bool paused_;
 
-  // `context_group_id_` starts at kNoDebugContextGroupId, but then gets
-  // initialized after some thread hops.
-  int context_group_id_;
-
   std::unique_ptr<WorkletLoader> worklet_loader_;
 
   // Lives on `v8_runner_`. Since it's deleted there, tasks can be safely
diff --git a/content/services/auction_worklet/seller_worklet_unittest.cc b/content/services/auction_worklet/seller_worklet_unittest.cc
index f4acd27..c7cdf5d0 100644
--- a/content/services/auction_worklet/seller_worklet_unittest.cc
+++ b/content/services/auction_worklet/seller_worklet_unittest.cc
@@ -290,13 +290,6 @@
     load_script_run_loop_->Quit();
   }
 
-  int LookUpContextGroupId(SellerWorklet* worklet_impl) {
-    task_environment_.RunUntilIdle();
-    int id = worklet_impl->context_group_id_for_testing();
-    CHECK_NE(AuctionV8Helper::kNoDebugContextGroupId, id);
-    return id;
-  }
-
  protected:
   base::test::TaskEnvironment task_environment_;
 
@@ -849,7 +842,7 @@
   auto worklet = CreateWorkletImpl(url_, /*pause_for_debugger_on_start=*/true,
                                    &worklet_impl);
   // Grab the context ID to be able to resume.
-  int id = LookUpContextGroupId(worklet_impl);
+  int id = worklet_impl->context_group_id_for_testing();
 
   // Give it a chance to fetch.
   task_environment_.RunUntilIdle();
@@ -880,7 +873,7 @@
   task_environment_.RunUntilIdle();
 
   // Grab the context ID.
-  int id = LookUpContextGroupId(worklet_impl);
+  int id = worklet_impl->context_group_id_for_testing();
 
   // Delete the worklet. is should issue an error callback, so in turn it
   // needs the event loop the callback in the fixture uses.
@@ -927,8 +920,8 @@
   auto worklet2 = CreateWorkletImpl(
       GURL(kUrl2), /*pause_for_debugger_on_start=*/true, &worklet_impl2);
 
-  int id1 = LookUpContextGroupId(worklet_impl1);
-  int id2 = LookUpContextGroupId(worklet_impl2);
+  int id1 = worklet_impl1->context_group_id_for_testing();
+  int id2 = worklet_impl2->context_group_id_for_testing();
 
   TestChannel* channel1 = inspector_support.ConnectDebuggerSession(id1);
   TestChannel* channel2 = inspector_support.ConnectDebuggerSession(id2);
@@ -1009,7 +1002,7 @@
   SellerWorklet* worklet_impl = nullptr;
   auto worklet = CreateWorkletImpl(url_, /*pause_for_debugger_on_start=*/true,
                                    &worklet_impl);
-  int id = LookUpContextGroupId(worklet_impl);
+  int id = worklet_impl->context_group_id_for_testing();
   TestChannel* channel = inspector_support.ConnectDebuggerSession(id);
 
   channel->RunCommandAndWaitForResult(
diff --git a/content/services/auction_worklet/worklet_loader.cc b/content/services/auction_worklet/worklet_loader.cc
index d391882..c0a567a 100644
--- a/content/services/auction_worklet/worklet_loader.cc
+++ b/content/services/auction_worklet/worklet_loader.cc
@@ -49,11 +49,11 @@
     network::mojom::URLLoaderFactory* url_loader_factory,
     const GURL& script_source_url,
     scoped_refptr<AuctionV8Helper> v8_helper,
-    int debug_context_group_id,
+    scoped_refptr<AuctionV8Helper::DebugId> debug_id,
     LoadWorkletCallback load_worklet_callback)
     : script_source_url_(script_source_url),
       v8_helper_(v8_helper),
-      debug_context_group_id_(debug_context_group_id),
+      debug_id_(std::move(debug_id)),
       load_worklet_callback_(std::move(load_worklet_callback)) {
   DCHECK(load_worklet_callback_);
 
@@ -72,19 +72,18 @@
 
   auction_downloader_.reset();
   v8_helper_->v8_runner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&WorkletLoader::HandleDownloadResultOnV8Thread,
-                     script_source_url_, v8_helper_, debug_context_group_id_,
-                     std::move(body), std::move(error_msg),
-                     base::SequencedTaskRunnerHandle::Get(),
-                     weak_ptr_factory_.GetWeakPtr()));
+      FROM_HERE, base::BindOnce(&WorkletLoader::HandleDownloadResultOnV8Thread,
+                                script_source_url_, v8_helper_, debug_id_,
+                                std::move(body), std::move(error_msg),
+                                base::SequencedTaskRunnerHandle::Get(),
+                                weak_ptr_factory_.GetWeakPtr()));
 }
 
 // static
 void WorkletLoader::HandleDownloadResultOnV8Thread(
     GURL script_source_url,
     scoped_refptr<AuctionV8Helper> v8_helper,
-    int debug_context_group_id,
+    scoped_refptr<AuctionV8Helper::DebugId> debug_id,
     std::unique_ptr<std::string> body,
     absl::optional<std::string> error_msg,
     scoped_refptr<base::SequencedTaskRunner> user_thread_task_runner,
@@ -105,8 +104,7 @@
   v8::Context::Scope context_scope(v8_helper->scratch_context());
 
   v8::Local<v8::UnboundScript> local_script;
-  if (v8_helper
-          ->Compile(*body, script_source_url, debug_context_group_id, error_msg)
+  if (v8_helper->Compile(*body, script_source_url, debug_id.get(), error_msg)
           .ToLocal(&local_script)) {
     global_script = Result(v8_helper, v8::Global<v8::UnboundScript>(
                                           v8_helper->isolate(), local_script));
diff --git a/content/services/auction_worklet/worklet_loader.h b/content/services/auction_worklet/worklet_loader.h
index 6af998bb..4a882232 100644
--- a/content/services/auction_worklet/worklet_loader.h
+++ b/content/services/auction_worklet/worklet_loader.h
@@ -75,7 +75,7 @@
   WorkletLoader(network::mojom::URLLoaderFactory* url_loader_factory,
                 const GURL& script_source_url,
                 scoped_refptr<AuctionV8Helper> v8_helper,
-                int debug_context_group_id,
+                scoped_refptr<AuctionV8Helper::DebugId> debug_id,
                 LoadWorkletCallback load_worklet_callback);
   explicit WorkletLoader(const WorkletLoader&) = delete;
   WorkletLoader& operator=(const WorkletLoader&) = delete;
@@ -88,7 +88,7 @@
   static void HandleDownloadResultOnV8Thread(
       GURL script_source_url,
       scoped_refptr<AuctionV8Helper> v8_helper,
-      int debug_context_group_id,
+      scoped_refptr<AuctionV8Helper::DebugId> debug_id,
       std::unique_ptr<std::string> body,
       absl::optional<std::string> error_msg,
       scoped_refptr<base::SequencedTaskRunner> user_thread_task_runner,
@@ -99,7 +99,7 @@
 
   const GURL script_source_url_;
   const scoped_refptr<AuctionV8Helper> v8_helper_;
-  int debug_context_group_id_;
+  const scoped_refptr<AuctionV8Helper::DebugId> debug_id_;
 
   std::unique_ptr<AuctionDownloader> auction_downloader_;
   LoadWorkletCallback load_worklet_callback_;
diff --git a/content/services/auction_worklet/worklet_loader_unittest.cc b/content/services/auction_worklet/worklet_loader_unittest.cc
index d95b156..85de8de 100644
--- a/content/services/auction_worklet/worklet_loader_unittest.cc
+++ b/content/services/auction_worklet/worklet_loader_unittest.cc
@@ -71,7 +71,7 @@
               kValidScript, kAllowFledgeHeader, net::HTTP_NOT_FOUND);
   WorkletLoader worklet_loader(
       &url_loader_factory_, url_, v8_helper_,
-      AuctionV8Helper::kNoDebugContextGroupId,
+      scoped_refptr<AuctionV8Helper::DebugId>(),
       base::BindOnce(&WorkletLoaderTest::LoadWorkletCallback,
                      base::Unretained(this)));
   run_loop_.Run();
@@ -84,7 +84,7 @@
   AddJavascriptResponse(&url_loader_factory_, url_, kInvalidScript);
   WorkletLoader worklet_loader(
       &url_loader_factory_, url_, v8_helper_,
-      AuctionV8Helper::kNoDebugContextGroupId,
+      scoped_refptr<AuctionV8Helper::DebugId>(),
       base::BindOnce(&WorkletLoaderTest::LoadWorkletCallback,
                      base::Unretained(this)));
   run_loop_.Run();
@@ -95,8 +95,9 @@
 
 TEST_F(WorkletLoaderTest, CompileErrorWithDebugger) {
   ScopedInspectorSupport inspector_support(v8_helper_.get());
-  int id = AllocContextGroupIdAndWait(v8_helper_);
-  TestChannel* channel = inspector_support.ConnectDebuggerSession(id);
+  auto id = base::MakeRefCounted<AuctionV8Helper::DebugId>(v8_helper_.get());
+  TestChannel* channel =
+      inspector_support.ConnectDebuggerSession(id->context_group_id());
   channel->RunCommandAndWaitForResult(
       1, "Runtime.enable", R"({"id":1,"method":"Runtime.enable","params":{}})");
   channel->RunCommandAndWaitForResult(
@@ -111,14 +112,13 @@
   run_loop_.Run();
   EXPECT_FALSE(load_succeeded_);
   channel->WaitForMethodNotification("Debugger.scriptFailedToParse");
-  FreeContextGroupIdAndWait(v8_helper_, id);
 }
 
 TEST_F(WorkletLoaderTest, Success) {
   AddJavascriptResponse(&url_loader_factory_, url_, kValidScript);
   WorkletLoader worklet_loader(
       &url_loader_factory_, url_, v8_helper_,
-      AuctionV8Helper::kNoDebugContextGroupId,
+      scoped_refptr<AuctionV8Helper::DebugId>(),
       base::BindOnce(&WorkletLoaderTest::LoadWorkletCallback,
                      base::Unretained(this)));
   run_loop_.Run();
@@ -135,7 +135,7 @@
   std::unique_ptr<WorkletLoader> worklet_loader =
       std::make_unique<WorkletLoader>(
           &url_loader_factory_, url_, v8_helper.get(),
-          AuctionV8Helper::kNoDebugContextGroupId,
+          scoped_refptr<AuctionV8Helper::DebugId>(),
           base::BindLambdaForTesting(
               [&](WorkletLoader::Result worklet_script,
                   absl::optional<std::string> error_msg) {
@@ -159,7 +159,7 @@
   std::unique_ptr<WorkletLoader> worklet_loader =
       std::make_unique<WorkletLoader>(
           &url_loader_factory_, url_, v8_helper.get(),
-          AuctionV8Helper::kNoDebugContextGroupId,
+          scoped_refptr<AuctionV8Helper::DebugId>(),
           base::BindLambdaForTesting(
               [&](WorkletLoader::Result worklet_script,
                   absl::optional<std::string> error_msg) {
@@ -184,7 +184,7 @@
   AddJavascriptResponse(&url_loader_factory_, url_, kValidScript);
   auto worklet_loader = std::make_unique<WorkletLoader>(
       &url_loader_factory_, url_, v8_helper_,
-      AuctionV8Helper::kNoDebugContextGroupId,
+      scoped_refptr<AuctionV8Helper::DebugId>(),
       base::BindOnce([](WorkletLoader::Result worklet_script,
                         absl::optional<std::string> error_msg) {
         ADD_FAILURE() << "Callback should not be invoked since loader deleted";
diff --git a/content/services/auction_worklet/worklet_v8_debug_test_util.cc b/content/services/auction_worklet/worklet_v8_debug_test_util.cc
index a9a5806..b90b49e 100644
--- a/content/services/auction_worklet/worklet_v8_debug_test_util.cc
+++ b/content/services/auction_worklet/worklet_v8_debug_test_util.cc
@@ -164,38 +164,6 @@
   v8_inspector_session_->dispatchProtocolMessage(ToStringView(payload));
 }
 
-int AllocContextGroupIdAndWait(scoped_refptr<AuctionV8Helper> v8_helper) {
-  int result = AuctionV8Helper::kNoDebugContextGroupId;
-  base::RunLoop run_loop;
-  v8_helper->v8_runner()->PostTask(
-      FROM_HERE, base::BindOnce(
-                     [](scoped_refptr<AuctionV8Helper> v8_helper,
-                        int& out_result, base::OnceClosure done_closure) {
-                       out_result =
-                           v8_helper->AllocContextGroupIdAndSetResumeCallback(
-                               base::OnceClosure());
-                       std::move(done_closure).Run();
-                     },
-                     v8_helper, std::ref(result), run_loop.QuitClosure()));
-  run_loop.Run();
-  CHECK_NE(AuctionV8Helper::kNoDebugContextGroupId, result);
-  return result;
-}
-
-void FreeContextGroupIdAndWait(scoped_refptr<AuctionV8Helper> v8_helper,
-                               int context_group_id) {
-  base::RunLoop run_loop;
-  v8_helper->v8_runner()->PostTask(
-      FROM_HERE, base::BindOnce(
-                     [](scoped_refptr<AuctionV8Helper> v8_helper,
-                        int context_group_id, base::OnceClosure done_closure) {
-                       v8_helper->FreeContextGroupId(context_group_id);
-                       std::move(done_closure).Run();
-                     },
-                     v8_helper, context_group_id, run_loop.QuitClosure()));
-  run_loop.Run();
-}
-
 ScopedInspectorSupport::ScopedInspectorSupport(AuctionV8Helper* v8_helper)
     : v8_state_(new V8State,
                 base::OnTaskRunnerDeleter(v8_helper->v8_runner())) {
diff --git a/content/services/auction_worklet/worklet_v8_debug_test_util.h b/content/services/auction_worklet/worklet_v8_debug_test_util.h
index 643f567a..5640dba 100644
--- a/content/services/auction_worklet/worklet_v8_debug_test_util.h
+++ b/content/services/auction_worklet/worklet_v8_debug_test_util.h
@@ -124,15 +124,6 @@
   std::list<Event> events_ GUARDED_BY(lock_);
 };
 
-// Thread-hop wrapper around
-// AuctionV8Helper::AllocContextGroupIdAndSetResumeCallback. Sets a null
-// callback.
-int AllocContextGroupIdAndWait(scoped_refptr<AuctionV8Helper> v8_helper);
-
-// Thread-hop wrapper around AuctionV8Helper::FreeContextGroupId().
-void FreeContextGroupIdAndWait(scoped_refptr<AuctionV8Helper> v8_helper,
-                               int context_group_id);
-
 // Class that helps set a V8Inspector w/a TestInspectorClient on an
 // AuctionV8Helper, and clean it up properly. Assumes v8 thread is separate from
 // main thread it runs on. Also helps with connecting debugger sessions.
diff --git a/content/shell/BUILD.gn b/content/shell/BUILD.gn
index 8138890..c47c8e5 100644
--- a/content/shell/BUILD.gn
+++ b/content/shell/BUILD.gn
@@ -427,6 +427,7 @@
     "$root_gen_dir/ui/resources/webui_generated_resources.pak",
     "$root_gen_dir/ui/resources/webui_resources.pak",
     "$root_gen_dir/ui/strings/app_locale_settings_en-US.pak",
+    "$root_gen_dir/ui/strings/ax_strings_en-US.pak",
     "$root_gen_dir/ui/strings/ui_strings_en-US.pak",
   ]
 
diff --git a/content/test/data/accessibility/aria/aria-directory-expected-mac.txt b/content/test/data/accessibility/aria/aria-directory-expected-mac.txt
index 13554e81..ea1a6c13 100644
--- a/content/test/data/accessibility/aria/aria-directory-expected-mac.txt
+++ b/content/test/data/accessibility/aria/aria-directory-expected-mac.txt
@@ -1,2 +1,2 @@
 AXWebArea AXRoleDescription='HTML content'
-++AXList AXSubrole=AXContentList AXRoleDescription='list'
+++AXList AXSubrole=AXContentList AXRoleDescription='content list'
diff --git a/content/test/data/accessibility/aria/aria-region-expected-mac.txt b/content/test/data/accessibility/aria/aria-region-expected-mac.txt
index c6d0676..3cfc419e 100644
--- a/content/test/data/accessibility/aria/aria-region-expected-mac.txt
+++ b/content/test/data/accessibility/aria/aria-region-expected-mac.txt
@@ -10,5 +10,5 @@
 ++++++AXStaticText AXRoleDescription='text' AXValue='Named ARIA region#2 gets the region role.'
 ++AXGroup AXSubrole=AXLandmarkRegion AXDescription='Named region' AXRoleDescription='region'
 ++++AXStaticText AXRoleDescription='text' AXValue='Named ARIA region#3 gets the region role.'
-++AXGroup AXSubrole=AXLandmarkRegion AXRoleDescription='Regioneque region'
+++AXGroup AXSubrole=AXLandmarkRegion AXRoleDescription='regioneque region'
 ++++AXStaticText AXRoleDescription='text' AXValue='An aria-rolescription works on a nameless role=region.'
\ No newline at end of file
diff --git a/content/test/data/accessibility/aria/aria-roledescription-expected-mac.txt b/content/test/data/accessibility/aria/aria-roledescription-expected-mac.txt
index a737b12..2d3176da 100644
--- a/content/test/data/accessibility/aria/aria-roledescription-expected-mac.txt
+++ b/content/test/data/accessibility/aria/aria-roledescription-expected-mac.txt
@@ -1,10 +1,10 @@
 AXWebArea AXRoleDescription='HTML content'
 ++AXButton AXRoleDescription='button' AXTitle='Native button'
 ++AXButton AXRoleDescription='button' AXTitle='ARIA button'
-++AXButton AXRoleDescription='Clicky' AXTitle='Clicky button'
+++AXButton AXRoleDescription='clicky' AXTitle='Clicky button'
 ++AXGroup AXRoleDescription='group'
 ++++AXStaticText AXRoleDescription='text' AXValue='foo'
 ++AXGroup AXDescription='bar' AXRoleDescription='group'
 ++++AXStaticText AXRoleDescription='text' AXValue='bar'
-++AXGroup AXRoleDescription='Texty'
+++AXGroup AXRoleDescription='texty'
 ++++AXStaticText AXRoleDescription='text' AXValue='baz'
diff --git a/content/test/data/accessibility/aria/aria-term-expected-mac.txt b/content/test/data/accessibility/aria/aria-term-expected-mac.txt
index 853394ef..4261aad7 100644
--- a/content/test/data/accessibility/aria/aria-term-expected-mac.txt
+++ b/content/test/data/accessibility/aria/aria-term-expected-mac.txt
@@ -1,5 +1,5 @@
 AXWebArea AXRoleDescription='HTML content'
-++AXList AXRoleDescription='list'
+++AXList AXRoleDescription='content list'
 ++++AXGroup AXSubrole=AXTerm AXRoleDescription='term'
 ++++++AXStaticText AXRoleDescription='text' AXValue='Term1'
 ++++AXGroup AXRoleDescription='definition'
diff --git a/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-mac.txt b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-mac.txt
index 5e290d51..a920ca4 100644
--- a/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-mac.txt
+++ b/content/test/data/accessibility/aria/aria-treeitem-nested-in-lists-expected-mac.txt
@@ -1,12 +1,12 @@
 AXWebArea AXRoleDescription='HTML content'
 ++AXOutline AXARIASetSize=5 AXRoleDescription='outline'
-++++AXRow AXARIAPosInSet=2 AXARIASetSize=5 AXIndex=0 AXRoleDescription='row' AXTitle='treeitem 2 of 5, level 1'
+++++AXRow AXARIAPosInSet=2 AXARIASetSize=5 AXIndex=0 AXRoleDescription='outline row' AXTitle='treeitem 2 of 5, level 1'
 ++++++AXStaticText AXRoleDescription='text' AXValue='treeitem 2 of 5, level 1'
-++++AXRow AXARIAPosInSet=3 AXARIASetSize=5 AXIndex=1 AXRoleDescription='row' AXTitle='treeitem 3 of 5, level 1'
+++++AXRow AXARIAPosInSet=3 AXARIASetSize=5 AXIndex=1 AXRoleDescription='outline row' AXTitle='treeitem 3 of 5, level 1'
 ++++++AXStaticText AXRoleDescription='text' AXValue='treeitem 3 of 5, level 1'
-++++AXRow AXARIAPosInSet=1 AXARIASetSize=2 AXIndex=2 AXRoleDescription='row' AXTitle='treeitem 1 of 2, level 2'
+++++AXRow AXARIAPosInSet=1 AXARIASetSize=2 AXIndex=2 AXRoleDescription='outline row' AXTitle='treeitem 1 of 2, level 2'
 ++++++AXStaticText AXRoleDescription='text' AXValue='treeitem 1 of 2, level 2'
-++++AXRow AXARIAPosInSet=1 AXARIASetSize=1 AXIndex=3 AXRoleDescription='row' AXTitle='treeitem 1 of 1, level 3'
+++++AXRow AXARIAPosInSet=1 AXARIASetSize=1 AXIndex=3 AXRoleDescription='outline row' AXTitle='treeitem 1 of 1, level 3'
 ++++++AXStaticText AXRoleDescription='text' AXValue='treeitem 1 of 1, level 3'
-++++AXRow AXARIAPosInSet=2 AXARIASetSize=2 AXIndex=4 AXRoleDescription='row' AXTitle='treeitem 2 of 2, level 2'
+++++AXRow AXARIAPosInSet=2 AXARIASetSize=2 AXIndex=4 AXRoleDescription='outline row' AXTitle='treeitem 2 of 2, level 2'
 ++++++AXStaticText AXRoleDescription='text' AXValue='treeitem 2 of 2, level 2'
\ No newline at end of file
diff --git a/content/test/data/accessibility/html/input-password-expected-mac.txt b/content/test/data/accessibility/html/input-password-expected-mac.txt
index eea000a..eb12341 100644
--- a/content/test/data/accessibility/html/input-password-expected-mac.txt
+++ b/content/test/data/accessibility/html/input-password-expected-mac.txt
@@ -1,3 +1,3 @@
 AXWebArea AXRoleDescription='HTML content'
 ++AXGroup AXRoleDescription='group'
-++++AXTextField AXSubrole=AXSecureTextField AXRoleDescription='text field' AXValue='%E2%80%A2%E2%80%A2%E2%80%A2%E2%80%A2%E2%80%A2%E2%80%A2'
\ No newline at end of file
+++++AXTextField AXSubrole=AXSecureTextField AXRoleDescription='secure text field' AXValue='%E2%80%A2%E2%80%A2%E2%80%A2%E2%80%A2%E2%80%A2%E2%80%A2'
\ No newline at end of file
diff --git a/content/test/data/accessibility/mac/methods/accessibility-role-description-expected.txt b/content/test/data/accessibility/mac/methods/accessibility-role-description-expected.txt
new file mode 100644
index 0000000..041a936e
--- /dev/null
+++ b/content/test/data/accessibility/mac/methods/accessibility-role-description-expected.txt
@@ -0,0 +1,35 @@
+unlabelled_img.accessibilityRoleDescription='Unlabeled image'
+aria_roledescription.accessibilityRoleDescription='slidy slider button'
+document.accessibilityRoleDescription='HTML content'
+link.accessibilityRoleDescription='link'
+heading.accessibilityRoleDescription='heading'
+article.accessibilityRoleDescription='article'
+banner.accessibilityRoleDescription='banner'
+checkbox.accessibilityRoleDescription='checkbox'
+comment.accessibilityRoleDescription='comment'
+complementary.accessibilityRoleDescription='complementary'
+contentinfo.accessibilityRoleDescription='contentinfo'
+description_list.accessibilityRoleDescription='definition list'
+description_list_detail.accessibilityRoleDescription='definition'
+description_list_term.accessibilityRoleDescription='term'
+details.AXChildren[0].accessibilityRoleDescription='disclosure triangle'
+figure.accessibilityRoleDescription='figure'
+footer.accessibilityRoleDescription='footer'
+form.accessibilityRoleDescription='form'
+header.accessibilityRoleDescription='banner'
+main.accessibilityRoleDescription='main'
+mark.accessibilityRoleDescription='highlight'
+math.accessibilityRoleDescription='math'
+mathml_math.accessibilityRoleDescription='math'
+navigation.accessibilityRoleDescription='navigation'
+region.accessibilityRoleDescription='region'
+spinbutton.accessibilityRoleDescription='stepper'
+status.accessibilityRoleDescription='status'
+searchbox.accessibilityRoleDescription='search text field'
+suggestion.accessibilityRoleDescription='suggestion'
+switch.accessibilityRoleDescription='switch'
+tab.accessibilityRoleDescription='tab'
+term.accessibilityRoleDescription='term'
+toggle_button.accessibilityRoleDescription='toggle button'
+fallback_role.accessibilityRoleDescription='text field'
+fallback_subrole.accessibilityRoleDescription='secure text field'
diff --git a/content/test/data/accessibility/mac/methods/accessibility-role-description.html b/content/test/data/accessibility/mac/methods/accessibility-role-description.html
new file mode 100644
index 0000000..bc9267df
--- /dev/null
+++ b/content/test/data/accessibility/mac/methods/accessibility-role-description.html
@@ -0,0 +1,77 @@
+<!--
+@SCRIPT:
+  unlabelled_img.accessibilityRoleDescription
+  aria_roledescription.accessibilityRoleDescription
+  document.accessibilityRoleDescription
+  link.accessibilityRoleDescription
+  heading.accessibilityRoleDescription
+  article.accessibilityRoleDescription
+  banner.accessibilityRoleDescription
+  checkbox.accessibilityRoleDescription
+  comment.accessibilityRoleDescription
+  complementary.accessibilityRoleDescription
+  contentinfo.accessibilityRoleDescription
+  description_list.accessibilityRoleDescription
+  description_list_detail.accessibilityRoleDescription
+  description_list_term.accessibilityRoleDescription
+  details.AXChildren[0].accessibilityRoleDescription
+  figure.accessibilityRoleDescription
+  footer.accessibilityRoleDescription
+  form.accessibilityRoleDescription
+  header.accessibilityRoleDescription
+  main.accessibilityRoleDescription
+  mark.accessibilityRoleDescription
+  math.accessibilityRoleDescription
+  mathml_math.accessibilityRoleDescription
+  navigation.accessibilityRoleDescription
+  region.accessibilityRoleDescription
+  spinbutton.accessibilityRoleDescription
+  status.accessibilityRoleDescription
+  searchbox.accessibilityRoleDescription
+  suggestion.accessibilityRoleDescription
+  switch.accessibilityRoleDescription
+  tab.accessibilityRoleDescription
+  term.accessibilityRoleDescription
+  toggle_button.accessibilityRoleDescription
+  fallback_role.accessibilityRoleDescription
+  fallback_subrole.accessibilityRoleDescription
+-->
+<!DOCTYPE html>
+<div id="unlabelled_img" id="img" role="img"><img src="composite-part.jpg"></div>
+<div id="aria_roledescription" role="button" aria-roledescription="slidy slider button"></div>
+<a id="link" href="chromium.org">Chromium</a>
+<h1 id="heading">Outline</h1>
+<div id="article" role="article">Article</div>
+<div id="banner" role="banner">Banner</div>
+<div id="checkbox" role="checkbox">Checkbox</div>
+<div id="comment" role="comment">Comment</div>
+<div id="complementary" role="complementary">Complementary</div>
+<div id="contentinfo" role="contentinfo">Contentinfo</div>
+<dl id="description_list">
+  <dt id="description_list_term">Name: </dt>
+  <dd id="description_list_detail">John Don</dd>
+</dl>
+<details id="details">
+  <summary>Details</summary>
+  Something small enough to escape casual notice.
+</details>
+<figure id="figure"></figure>
+<footer id="footer"></footer>
+<div id="form" role="form"></div>
+<header id="header"></header>
+<div id="main" role="main">Main</div>
+<div id="mark" role="mark">Mark</div>
+<div id="math" role="math">Math</div>
+<math id="mathml_math"></math>
+<nav id="navigation">Navigation</nav>
+<div id="region" role="region">Region</div>
+<div id="spinbutton" role="spinbutton">SpinButton</div>
+<div id="status" role="status">Status</div>
+<input id="searchbox" type="search">
+<div id="suggestion" role="suggestion">Suggestion</div>
+<div id="switch" role="switch">Switch</div>
+<div id="tab" role="tab">Tab</div>
+<div id="term" role="term">Term</div>
+<div id="toggle_button" role="button" aria-pressed="true">ToggleButton</div>
+<input id="fallback_role">
+<input id="fallback_subrole" type="password">
diff --git a/content/zygote/zygote_main.h b/content/zygote/zygote_main.h
index 1959950..e56dcbea 100644
--- a/content/zygote/zygote_main.h
+++ b/content/zygote/zygote_main.h
@@ -8,15 +8,12 @@
 #include <memory>
 #include <vector>
 
-#include "build/build_config.h"
-#include "content/common/content_export.h"
-
 namespace content {
 
 class ZygoteForkDelegate;
 
 // |delegate| must outlive this call.
-CONTENT_EXPORT bool ZygoteMain(
+bool ZygoteMain(
     std::vector<std::unique_ptr<ZygoteForkDelegate>> fork_delegates);
 
 }  // namespace content
diff --git a/crypto/nss_util_chromeos.cc b/crypto/nss_util_chromeos.cc
index 23945ad..6ccc9d3 100644
--- a/crypto/nss_util_chromeos.cc
+++ b/crypto/nss_util_chromeos.cc
@@ -208,7 +208,7 @@
     state_ = (state_ == State::kTpmTokenInitialized) ? State::kTpmTokenEnabled
                                                      : State::kTpmTokenDisabled;
 
-    tpm_ready_callback_list_.Notify();
+    tpm_ready_callback_list_->Notify();
   }
 
   static void InitializeTPMTokenInThreadPool(CK_SLOT_ID token_slot_id,
@@ -262,7 +262,7 @@
 
     if (!IsInitializationFinished()) {
       // Call back to this method when initialization is finished.
-      tpm_ready_callback_list_.AddUnsafe(
+      tpm_ready_callback_list_->AddUnsafe(
           base::BindOnce(&ChromeOSTokenManager::IsTPMTokenEnabled,
                          base::Unretained(this) /* singleton is leaky */,
                          std::move(callback)));
@@ -412,7 +412,7 @@
 
     if (!IsInitializationFinished()) {
       // Call back to this method when initialization is finished.
-      tpm_ready_callback_list_.AddUnsafe(
+      tpm_ready_callback_list_->AddUnsafe(
           base::BindOnce(&ChromeOSTokenManager::GetSystemNSSKeySlot,
                          base::Unretained(this) /* singleton is leaky */,
                          std::move(callback)));
@@ -429,6 +429,24 @@
 
   void ResetSystemSlotForTesting() { system_slot_.reset(); }
 
+  void ResetTokenManagerForTesting() {
+    // Prevent test failures when two tests in the same process use the same
+    // ChromeOSTokenManager from different threads.
+    DETACH_FROM_THREAD(thread_checker_);
+    state_ = State::kInitializationNotStarted;
+
+    // Configuring chaps_module_ here is not supported yet.
+    CHECK(!chaps_module_);
+
+    // Make sure there are no outstanding callbacks between tests.
+    // OnceClosureList doesn't provide a way to clear the callback list.
+    tpm_ready_callback_list_ = std::make_unique<base::OnceClosureList>();
+
+    chromeos_user_map_.clear();
+    ResetSystemSlotForTesting();  // IN-TEST
+    prepared_test_private_slot_.reset();
+  }
+
   void SetPrivateSoftwareSlotForChromeOSUserForTesting(ScopedPK11Slot slot) {
     DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
@@ -465,7 +483,8 @@
   }
 
   State state_ = State::kInitializationNotStarted;
-  base::OnceClosureList tpm_ready_callback_list_;
+  std::unique_ptr<base::OnceClosureList> tpm_ready_callback_list_ =
+      std::make_unique<base::OnceClosureList>();
 
   SECMODModule* chaps_module_ = nullptr;
   ScopedPK11Slot system_slot_;
@@ -505,6 +524,13 @@
   ChromeOSTokenManagerDataForTesting::GetInstance().test_system_slot.reset();
 }
 
+void ResetTokenManagerForTesting() {
+  if (g_token_manager.IsCreated()) {
+    g_token_manager.Get().ResetTokenManagerForTesting();  // IN-TEST
+  }
+  ResetSystemSlotForTesting();  // IN-TEST
+}
+
 void IsTPMTokenEnabled(base::OnceCallback<void(bool)> callback) {
   g_token_manager.Get().IsTPMTokenEnabled(std::move(callback));
 }
diff --git a/crypto/nss_util_internal.h b/crypto/nss_util_internal.h
index 4ca2558..3caa63dc 100644
--- a/crypto/nss_util_internal.h
+++ b/crypto/nss_util_internal.h
@@ -69,6 +69,11 @@
 // initialization that depended on this previously-configured system slot.
 CRYPTO_EXPORT void ResetSystemSlotForTesting();
 
+// Reset the global ChromeOSTokenManager. This is used between tests, so
+// tests that run in the same process won't hit DCHECKS because they have
+// different BrowserIO threads.
+CRYPTO_EXPORT void ResetTokenManagerForTesting();
+
 // Prepare per-user NSS slot mapping. It is safe to call this function multiple
 // times. Returns true if the user was added, or false if it already existed.
 CRYPTO_EXPORT bool InitializeNSSForChromeOSUser(
diff --git a/docs/standards/positions/GoogleChrome/README.md b/docs/standards/positions/GoogleChrome/README.md
new file mode 100644
index 0000000..6f9f1a1
--- /dev/null
+++ b/docs/standards/positions/GoogleChrome/README.md
@@ -0,0 +1,38 @@
+# Positions from Google Chrome Teams on Web Standards
+
+Google Chrome delegates decisions about what web features we implement and ship
+to feature teams. This directory holds documents explaining the positions of
+those feature teams about features they've decided to implement or not. Not all
+such decisions will be explained here: just the ones where the feature teams
+expect a document to help future discussions start from a place of shared
+understanding.
+
+These positions are explicitly *not* Chromium-wide positions, or even explicitly
+Chrome-wide. Other Chromium-based browsers may disagree.
+
+All positions here are subject to change as ownership changes or as owners
+encounter new evidence.
+
+Status: as yet, no positions have been published.
+
+## Process for publishing positions
+
+This directory isn't intended to record positions on many features. See
+https://chromestatus.com/ for a more complete list of features and their status
+in Chrome. Instead, this is intended to support transparency and discoverability
+in areas of significant debate or repeated questions. Focus on technical
+disagreements and on explaining how the position could be changed. When in
+doubt, don't publish a position.
+
+Get positions approved by the tech lead of the team that would decide whether to
+ship the feature that the position is about. In the code review adding a
+position, try to cc people who were involved in the discussions leading up to
+the position, especially those who disagree, to ensure you're addressing the
+strongest version of their arguments.
+
+Be sure to add any new features to this README.
+
+There is no systematic way for people outside of the Chrome team to request a
+formal position document. If you want to know what Chrome thinks of a feature,
+ask someone who's involved with its working or community group. If you can't
+figure out who owns a feature, email blink-dev@chromium.org to ask.
diff --git a/docs/standards/positions/OWNERS b/docs/standards/positions/OWNERS
new file mode 100644
index 0000000..7cfbd94
--- /dev/null
+++ b/docs/standards/positions/OWNERS
@@ -0,0 +1,10 @@
+# These owners help ensure that any published positions follow the guidelines in
+# the READMEs; they do not approve the positions themselves.
+
+jyasskin@chromium.org
+sshruthi@chromium.org
+
+# Override the OWNERS: * from the parent directory.
+set noparent
+file://ENG_REVIEW_OWNERS
+file://third_party/blink/API_OWNERS
diff --git a/extensions/BUILD.gn b/extensions/BUILD.gn
index e810629e..b15b5cca 100644
--- a/extensions/BUILD.gn
+++ b/extensions/BUILD.gn
@@ -203,6 +203,7 @@
     "$root_gen_dir/third_party/blink/public/strings/blink_strings_en-US.pak",
     "$root_gen_dir/ui/resources/ui_resources_100_percent.pak",
     "$root_gen_dir/ui/strings/app_locale_settings_en-US.pak",
+    "$root_gen_dir/ui/strings/ax_strings_en-US.pak",
     "$root_gen_dir/ui/strings/ui_strings_en-US.pak",
   ]
 
diff --git a/fuchsia/engine/BUILD.gn b/fuchsia/engine/BUILD.gn
index c336effc..f76c8638 100644
--- a/fuchsia/engine/BUILD.gn
+++ b/fuchsia/engine/BUILD.gn
@@ -7,7 +7,6 @@
 import("//build/config/fuchsia/generate_runner_scripts.gni")
 import("//build/config/fuchsia/symbol_archive.gni")
 import("//build/config/locales.gni")
-import("//mojo/public/tools/bindings/mojom.gni")
 import("//testing/test.gni")
 import("//third_party/fuchsia-sdk/sdk/build/fidl_library.gni")
 import("//tools/grit/repack.gni")
@@ -17,15 +16,6 @@
   visibility = [ ":*" ]
 }
 
-mojom("mojom") {
-  sources = [ "url_request_rewrite.mojom" ]
-  public_deps = [
-    "//media/mojo/mojom",
-    "//mojo/public/mojom/base",
-  ]
-  visibility = [ ":*" ]
-}
-
 fidl_library("fidl") {
   library_name = "chromium.internal"
   sources = [ "fidl/dev_tools.fidl" ]
@@ -95,7 +85,6 @@
   deps = [
     ":audio_device_factory",
     ":fidl",
-    ":mojom",
     ":switches",
     "//base",
     "//base:base_static",
@@ -120,6 +109,10 @@
     "//components/profile_metrics",
     "//components/site_isolation",
     "//components/strings:components_locale_settings",
+    "//components/url_rewrite/browser",
+    "//components/url_rewrite/common",
+    "//components/url_rewrite/mojom",
+    "//components/url_rewrite/renderer",
     "//components/version_info",
     "//content/public/app",
     "//content/public/browser",
@@ -232,8 +225,6 @@
     "browser/theme_manager.h",
     "browser/url_request_rewrite_rules_manager.cc",
     "browser/url_request_rewrite_rules_manager.h",
-    "browser/url_request_rewrite_rules_validation.cc",
-    "browser/url_request_rewrite_rules_validation.h",
     "browser/web_engine_browser_context.cc",
     "browser/web_engine_browser_context.h",
     "browser/web_engine_browser_interface_binders.cc",
@@ -256,17 +247,12 @@
     "common/cast_streaming.h",
     "common/cors_exempt_headers.cc",
     "common/cors_exempt_headers.h",
-    "common/url_request_rewrite_rules.h",
     "common/web_engine_content_client.cc",
     "common/web_engine_content_client.h",
-    "common/web_engine_url_loader_throttle.cc",
-    "common/web_engine_url_loader_throttle.h",
     "context_provider_impl.cc",
     "context_provider_impl.h",
     "context_provider_main.cc",
     "context_provider_main.h",
-    "renderer/url_request_rules_receiver.cc",
-    "renderer/url_request_rules_receiver.h",
     "renderer/web_engine_audio_renderer.cc",
     "renderer/web_engine_audio_renderer.h",
     "renderer/web_engine_content_renderer_client.cc",
@@ -567,8 +553,6 @@
     "browser/media_player_impl_unittest.cc",
     "browser/navigation_policy_throttle_unittest.cc",
     "browser/url_request_rewrite_rules_manager_unittest.cc",
-    "browser/url_request_rewrite_rules_validation_unittest.cc",
-    "common/web_engine_url_loader_throttle_unittest.cc",
     "context_provider_impl_unittest.cc",
     "fake_context.cc",
     "fake_context.h",
@@ -576,12 +560,14 @@
     "test/run_all_unittests.cc",
   ]
   deps = [
-    ":mojom",
     ":switches",
     ":web_engine_core",
     ":web_engine_unittests_fake_instance_manifest",
     "//base/test:test_support",
     "//components/cast_streaming/public/mojom",
+    "//components/url_rewrite/browser",
+    "//components/url_rewrite/common",
+    "//components/url_rewrite/mojom",
     "//content/test:test_support",
     "//fuchsia/base/test:test_support",
     "//fuchsia/engine/web_instance_host",
diff --git a/fuchsia/engine/DEPS b/fuchsia/engine/DEPS
index f4ca7257..77c14d1f 100644
--- a/fuchsia/engine/DEPS
+++ b/fuchsia/engine/DEPS
@@ -1,10 +1,12 @@
 include_rules = [
+  # Do NOT add components/cast or other Cast-specific code to this file.
   "+cc/base/switches.h",
   "+components/cast/message_port",
   "+components/cast_streaming",
   "+components/embedder_support",
   "+components/media_control",
   "+components/on_load_script_injector",
+  "+components/url_rewrite/mojom",
   "+components/version_info",
   "+components/viz/common",
   "+content/public/app",
diff --git a/fuchsia/engine/browser/DEPS b/fuchsia/engine/browser/DEPS
index 141d63c..6cbaa7f6 100644
--- a/fuchsia/engine/browser/DEPS
+++ b/fuchsia/engine/browser/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+  # Do NOT add components/cast or other Cast-specific code to this file.
   "+components/favicon/content",
   "+components/favicon/core",
   "+components/keyed_service/content",
@@ -8,6 +9,9 @@
   "+components/policy/content",
   "+components/site_isolation",
   "+components/strings",
+  "+components/url_rewrite/browser",
+  "+components/url_rewrite/common",
+  "+components/url_rewrite/mojom",
   "+content/public/common",
   "+content/public/browser",
   "+components/permissions",
diff --git a/fuchsia/engine/browser/url_request_rewrite_rules_manager.cc b/fuchsia/engine/browser/url_request_rewrite_rules_manager.cc
index b6943ea..86705245 100644
--- a/fuchsia/engine/browser/url_request_rewrite_rules_manager.cc
+++ b/fuchsia/engine/browser/url_request_rewrite_rules_manager.cc
@@ -4,7 +4,7 @@
 
 #include "fuchsia/engine/browser/url_request_rewrite_rules_manager.h"
 
-#include "fuchsia/engine/browser/url_request_rewrite_rules_validation.h"
+#include "components/url_rewrite/browser/url_request_rewrite_rules_validation.h"
 #include "fuchsia/engine/url_request_rewrite_type_converters.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 
@@ -28,7 +28,8 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   cached_rules_ = base::MakeRefCounted<url_rewrite::UrlRequestRewriteRules>(
-      mojo::ConvertTo<mojom::UrlRequestRewriteRulesPtr>(std::move(rules)));
+      mojo::ConvertTo<url_rewrite::mojom::UrlRequestRewriteRulesPtr>(
+          std::move(rules)));
   if (!url_rewrite::ValidateRules(cached_rules_->data.get())) {
     cached_rules_ = nullptr;
     return ZX_ERR_INVALID_ARGS;
@@ -58,7 +59,8 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   // Register the frame rules receiver.
-  mojo::AssociatedRemote<mojom::UrlRequestRulesReceiver> rules_receiver;
+  mojo::AssociatedRemote<url_rewrite::mojom::UrlRequestRulesReceiver>
+      rules_receiver;
   render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
       &rules_receiver);
   auto iter = active_remotes_.emplace(render_frame_host->GetGlobalId(),
diff --git a/fuchsia/engine/browser/url_request_rewrite_rules_manager.h b/fuchsia/engine/browser/url_request_rewrite_rules_manager.h
index 57be39b..424cdb3 100644
--- a/fuchsia/engine/browser/url_request_rewrite_rules_manager.h
+++ b/fuchsia/engine/browser/url_request_rewrite_rules_manager.h
@@ -9,9 +9,9 @@
 
 #include "base/memory/scoped_refptr.h"
 #include "base/sequence_checker.h"
+#include "components/url_rewrite/common/url_request_rewrite_rules.h"
 #include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/web_contents_observer.h"
-#include "fuchsia/engine/common/url_request_rewrite_rules.h"
 #include "fuchsia/engine/web_engine_export.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 
@@ -56,7 +56,7 @@
 
   // Map of GlobalRoutingID to their current associated remote.
   std::map<content::GlobalRenderFrameHostId,
-           mojo::AssociatedRemote<mojom::UrlRequestRulesReceiver>>
+           mojo::AssociatedRemote<url_rewrite::mojom::UrlRequestRulesReceiver>>
       active_remotes_;
 
   SEQUENCE_CHECKER(sequence_checker_);
diff --git a/fuchsia/engine/browser/url_request_rewrite_rules_manager_unittest.cc b/fuchsia/engine/browser/url_request_rewrite_rules_manager_unittest.cc
index 3c917b2..f4997ef8 100644
--- a/fuchsia/engine/browser/url_request_rewrite_rules_manager_unittest.cc
+++ b/fuchsia/engine/browser/url_request_rewrite_rules_manager_unittest.cc
@@ -49,7 +49,7 @@
   EXPECT_EQ(UpdateRulesFromRewrite(
                 cr_fuchsia::CreateRewriteAddHeaders("Test", "Value")),
             ZX_OK);
-  mojom::UrlRequestRewriteRulesPtr& cached_rules =
+  url_rewrite::mojom::UrlRequestRewriteRulesPtr& cached_rules =
       url_request_rewrite_rules_manager_->GetCachedRules()->data;
   ASSERT_EQ(cached_rules->rules.size(), 1u);
   ASSERT_FALSE(cached_rules->rules[0]->hosts_filter);
@@ -57,7 +57,7 @@
   ASSERT_EQ(cached_rules->rules[0]->actions.size(), 1u);
   ASSERT_TRUE(cached_rules->rules[0]->actions[0]->is_add_headers());
 
-  const std::vector<mojom::UrlHeaderPtr>& headers =
+  const std::vector<url_rewrite::mojom::UrlHeaderPtr>& headers =
       cached_rules->rules[0]->actions[0]->get_add_headers()->headers;
   ASSERT_EQ(headers.size(), 1u);
   ASSERT_EQ(headers[0]->name, "Test");
@@ -69,7 +69,7 @@
   EXPECT_EQ(UpdateRulesFromRewrite(cr_fuchsia::CreateRewriteRemoveHeader(
                 absl::make_optional("Test"), "Header")),
             ZX_OK);
-  mojom::UrlRequestRewriteRulesPtr& cached_rules =
+  url_rewrite::mojom::UrlRequestRewriteRulesPtr& cached_rules =
       url_request_rewrite_rules_manager_->GetCachedRules()->data;
   ASSERT_EQ(cached_rules->rules.size(), 1u);
   ASSERT_FALSE(cached_rules->rules[0]->hosts_filter);
@@ -77,7 +77,7 @@
   ASSERT_EQ(cached_rules->rules[0]->actions.size(), 1u);
   ASSERT_TRUE(cached_rules->rules[0]->actions[0]->is_remove_header());
 
-  const mojom::UrlRequestRewriteRemoveHeaderPtr& remove_header1 =
+  const url_rewrite::mojom::UrlRequestRewriteRemoveHeaderPtr& remove_header1 =
       cached_rules->rules[0]->actions[0]->get_remove_header();
   ASSERT_TRUE(remove_header1->query_pattern);
   ASSERT_EQ(remove_header1->query_pattern.value().compare("Test"), 0);
@@ -87,7 +87,7 @@
   EXPECT_EQ(UpdateRulesFromRewrite(
                 cr_fuchsia::CreateRewriteRemoveHeader(absl::nullopt, "Header")),
             ZX_OK);
-  mojom::UrlRequestRewriteRulesPtr& updated_rules =
+  url_rewrite::mojom::UrlRequestRewriteRulesPtr& updated_rules =
       url_request_rewrite_rules_manager_->GetCachedRules()->data;
   ASSERT_EQ(updated_rules->rules.size(), 1u);
   ASSERT_FALSE(updated_rules->rules[0]->hosts_filter);
@@ -95,7 +95,7 @@
   ASSERT_EQ(updated_rules->rules[0]->actions.size(), 1u);
   ASSERT_TRUE(updated_rules->rules[0]->actions[0]->is_remove_header());
 
-  const mojom::UrlRequestRewriteRemoveHeaderPtr& remove_header2 =
+  const url_rewrite::mojom::UrlRequestRewriteRemoveHeaderPtr& remove_header2 =
       updated_rules->rules[0]->actions[0]->get_remove_header();
   ASSERT_FALSE(remove_header2->query_pattern);
   ASSERT_EQ(remove_header2->header_name.compare("Header"), 0);
@@ -108,7 +108,7 @@
       UpdateRulesFromRewrite(cr_fuchsia::CreateRewriteSubstituteQueryPattern(
           "Pattern", "Substitution")),
       ZX_OK);
-  mojom::UrlRequestRewriteRulesPtr& cached_rules =
+  url_rewrite::mojom::UrlRequestRewriteRulesPtr& cached_rules =
       url_request_rewrite_rules_manager_->GetCachedRules()->data;
   ASSERT_EQ(cached_rules->rules.size(), 1u);
   ASSERT_FALSE(cached_rules->rules[0]->hosts_filter);
@@ -117,7 +117,7 @@
   ASSERT_TRUE(
       cached_rules->rules[0]->actions[0]->is_substitute_query_pattern());
 
-  const mojom::UrlRequestRewriteSubstituteQueryPatternPtr&
+  const url_rewrite::mojom::UrlRequestRewriteSubstituteQueryPatternPtr&
       substitute_query_pattern =
           cached_rules->rules[0]->actions[0]->get_substitute_query_pattern();
   ASSERT_EQ(substitute_query_pattern->pattern.compare("Pattern"), 0);
@@ -130,7 +130,7 @@
   EXPECT_EQ(UpdateRulesFromRewrite(
                 cr_fuchsia::CreateRewriteReplaceUrl("/something", url.spec())),
             ZX_OK);
-  mojom::UrlRequestRewriteRulesPtr& cached_rules =
+  url_rewrite::mojom::UrlRequestRewriteRulesPtr& cached_rules =
       url_request_rewrite_rules_manager_->GetCachedRules()->data;
   ASSERT_EQ(cached_rules->rules.size(), 1u);
   ASSERT_FALSE(cached_rules->rules[0]->hosts_filter);
@@ -138,7 +138,7 @@
   ASSERT_EQ(cached_rules->rules[0]->actions.size(), 1u);
   ASSERT_TRUE(cached_rules->rules[0]->actions[0]->is_replace_url());
 
-  const mojom::UrlRequestRewriteReplaceUrlPtr& replace_url =
+  const url_rewrite::mojom::UrlRequestRewriteReplaceUrlPtr& replace_url =
       cached_rules->rules[0]->actions[0]->get_replace_url();
   ASSERT_EQ(replace_url->url_ends_with.compare("/something"), 0);
   ASSERT_EQ(replace_url->new_url.spec().compare(url.spec()), 0);
@@ -149,7 +149,7 @@
   EXPECT_EQ(UpdateRulesFromRewrite(
                 cr_fuchsia::CreateRewriteAppendToQuery("foo=bar&foo")),
             ZX_OK);
-  mojom::UrlRequestRewriteRulesPtr& cached_rules =
+  url_rewrite::mojom::UrlRequestRewriteRulesPtr& cached_rules =
       url_request_rewrite_rules_manager_->GetCachedRules()->data;
   ASSERT_EQ(cached_rules->rules.size(), 1u);
   ASSERT_FALSE(cached_rules->rules[0]->hosts_filter);
@@ -157,7 +157,7 @@
   ASSERT_EQ(cached_rules->rules[0]->actions.size(), 1u);
   ASSERT_TRUE(cached_rules->rules[0]->actions[0]->is_append_to_query());
 
-  const mojom::UrlRequestRewriteAppendToQueryPtr& append_to_query =
+  const url_rewrite::mojom::UrlRequestRewriteAppendToQueryPtr& append_to_query =
       cached_rules->rules[0]->actions[0]->get_append_to_query();
   ASSERT_EQ(append_to_query->query.compare("foo=bar&foo"), 0);
 }
@@ -235,7 +235,7 @@
   EXPECT_EQ(UpdateRulesFromRewrite(
                 cr_fuchsia::CreateRewriteAddHeaders("Test1", "Value")),
             ZX_OK);
-  mojom::UrlRequestRewriteRulesPtr& cached_rules =
+  url_rewrite::mojom::UrlRequestRewriteRulesPtr& cached_rules =
       url_request_rewrite_rules_manager_->GetCachedRules()->data;
   ASSERT_EQ(cached_rules->rules.size(), 1u);
   ASSERT_EQ(cached_rules->rules[0]->actions.size(), 1u);
@@ -252,7 +252,7 @@
             ZX_OK);
 
   // We should have the new rules.
-  mojom::UrlRequestRewriteRulesPtr& updated_rules =
+  url_rewrite::mojom::UrlRequestRewriteRulesPtr& updated_rules =
       url_request_rewrite_rules_manager_->GetCachedRules()->data;
   ASSERT_EQ(updated_rules->rules.size(), 1u);
   ASSERT_EQ(updated_rules->rules[0]->actions.size(), 1u);
@@ -280,7 +280,7 @@
   EXPECT_EQ(url_request_rewrite_rules_manager_->OnRulesUpdated(std::move(rules),
                                                                []() {}),
             ZX_OK);
-  mojom::UrlRequestRewriteRulesPtr& cached_rules =
+  url_rewrite::mojom::UrlRequestRewriteRulesPtr& cached_rules =
       url_request_rewrite_rules_manager_->GetCachedRules()->data;
 
   ASSERT_EQ(cached_rules->rules.size(), 1u);
diff --git a/fuchsia/engine/browser/url_request_rewrite_rules_validation.h b/fuchsia/engine/browser/url_request_rewrite_rules_validation.h
deleted file mode 100644
index e45b9470..0000000
--- a/fuchsia/engine/browser/url_request_rewrite_rules_validation.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef FUCHSIA_ENGINE_BROWSER_URL_REQUEST_REWRITE_RULES_VALIDATION_H_
-#define FUCHSIA_ENGINE_BROWSER_URL_REQUEST_REWRITE_RULES_VALIDATION_H_
-
-#include "fuchsia/engine/common/url_request_rewrite_rules.h"
-#include "fuchsia/engine/web_engine_export.h"
-
-namespace url_rewrite {
-
-// Returns true if |rules| have valid data in them, false otherwise.
-WEB_ENGINE_EXPORT bool ValidateRules(
-    const mojom::UrlRequestRewriteRules* rules);
-
-}  // namespace url_rewrite
-
-#endif  // FUCHSIA_ENGINE_BROWSER_URL_REQUEST_REWRITE_RULES_VALIDATION_H_
diff --git a/fuchsia/engine/browser/web_engine_content_browser_client.cc b/fuchsia/engine/browser/web_engine_content_browser_client.cc
index 3b248cc6..7daca148 100644
--- a/fuchsia/engine/browser/web_engine_content_browser_client.cc
+++ b/fuchsia/engine/browser/web_engine_content_browser_client.cc
@@ -15,6 +15,8 @@
 #include "components/site_isolation/features.h"
 #include "components/site_isolation/preloaded_isolated_origins.h"
 #include "components/strings/grit/components_locale_settings.h"
+#include "components/url_rewrite/common/url_loader_throttle.h"
+#include "components/url_rewrite/common/url_request_rewrite_rules.h"
 #include "content/public/browser/client_certificate_delegate.h"
 #include "content/public/browser/devtools_manager_delegate.h"
 #include "content/public/browser/navigation_handle.h"
@@ -29,9 +31,8 @@
 #include "fuchsia/engine/browser/web_engine_browser_interface_binders.h"
 #include "fuchsia/engine/browser/web_engine_browser_main_parts.h"
 #include "fuchsia/engine/browser/web_engine_devtools_controller.h"
-#include "fuchsia/engine/common/url_request_rewrite_rules.h"
+#include "fuchsia/engine/common/cors_exempt_headers.h"
 #include "fuchsia/engine/common/web_engine_content_client.h"
-#include "fuchsia/engine/common/web_engine_url_loader_throttle.h"
 #include "fuchsia/engine/switches.h"
 #include "media/base/media_switches.h"
 #include "net/cert/x509_certificate.h"
@@ -274,7 +275,8 @@
   scoped_refptr<url_rewrite::UrlRequestRewriteRules>& rules =
       frame_impl->url_request_rewrite_rules_manager()->GetCachedRules();
   if (rules) {
-    throttles.emplace_back(std::make_unique<WebEngineURLLoaderThrottle>(rules));
+    throttles.emplace_back(std::make_unique<url_rewrite::URLLoaderThrottle>(
+        rules, base::BindRepeating(&IsHeaderCorsExempt)));
   }
   return throttles;
 }
diff --git a/fuchsia/engine/common/url_request_rewrite_rules.h b/fuchsia/engine/common/url_request_rewrite_rules.h
deleted file mode 100644
index 97456c4..0000000
--- a/fuchsia/engine/common/url_request_rewrite_rules.h
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef FUCHSIA_ENGINE_COMMON_URL_REQUEST_REWRITE_RULES_H_
-#define FUCHSIA_ENGINE_COMMON_URL_REQUEST_REWRITE_RULES_H_
-
-#include "base/memory/ref_counted.h"
-#include "fuchsia/engine/url_request_rewrite.mojom.h"
-
-namespace url_rewrite {
-
-using UrlRequestRewriteRules =
-    base::RefCountedData<mojom::UrlRequestRewriteRulesPtr>;
-
-}
-
-#endif  // FUCHSIA_ENGINE_COMMON_URL_REQUEST_REWRITE_RULES_H_
diff --git a/fuchsia/engine/common/web_engine_url_loader_throttle.h b/fuchsia/engine/common/web_engine_url_loader_throttle.h
deleted file mode 100644
index 001b511..0000000
--- a/fuchsia/engine/common/web_engine_url_loader_throttle.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef FUCHSIA_ENGINE_COMMON_WEB_ENGINE_URL_LOADER_THROTTLE_H_
-#define FUCHSIA_ENGINE_COMMON_WEB_ENGINE_URL_LOADER_THROTTLE_H_
-
-#include "base/memory/scoped_refptr.h"
-#include "fuchsia/engine/common/url_request_rewrite_rules.h"
-#include "fuchsia/engine/web_engine_export.h"
-#include "third_party/blink/public/common/loader/url_loader_throttle.h"
-
-// Implements a URLLoaderThrottle for the WebEngine. Applies network request
-// rewrites provided through the fuchsia.web.SetUrlRequestRewriteRules FIDL API.
-class WEB_ENGINE_EXPORT WebEngineURLLoaderThrottle
-    : public blink::URLLoaderThrottle {
- public:
-  explicit WebEngineURLLoaderThrottle(
-      scoped_refptr<url_rewrite::UrlRequestRewriteRules> rules);
-  ~WebEngineURLLoaderThrottle() override;
-
-  WebEngineURLLoaderThrottle(const WebEngineURLLoaderThrottle&) = delete;
-  WebEngineURLLoaderThrottle& operator=(const WebEngineURLLoaderThrottle&) =
-      delete;
-
-  // blink::URLLoaderThrottle implementation.
-  void DetachFromCurrentSequence() override;
-  void WillStartRequest(network::ResourceRequest* request,
-                        bool* defer) override;
-  bool makes_unsafe_redirect() override;
-
- private:
-  // Applies transformations specified by |rule| to |request|, conditional on
-  // the matching criteria of |rule|.
-  void ApplyRule(network::ResourceRequest* request,
-                 const mojom::UrlRequestRulePtr& rule);
-
-  // Applies |rewrite| transformations to |request|.
-  void ApplyRewrite(network::ResourceRequest* request,
-                    const mojom::UrlRequestActionPtr& rewrite);
-
-  // Adds HTTP headers from |add_headers| to |request|.
-  void ApplyAddHeaders(
-      network::ResourceRequest* request,
-      const mojom::UrlRequestRewriteAddHeadersPtr& add_headers);
-
-  scoped_refptr<url_rewrite::UrlRequestRewriteRules> rules_;
-};
-
-#endif  // FUCHSIA_ENGINE_COMMON_WEB_ENGINE_URL_LOADER_THROTTLE_H_
diff --git a/fuchsia/engine/renderer/DEPS b/fuchsia/engine/renderer/DEPS
index 3c4f146..315e6ff9 100644
--- a/fuchsia/engine/renderer/DEPS
+++ b/fuchsia/engine/renderer/DEPS
@@ -1,6 +1,10 @@
 include_rules = [
+  # Do NOT add components/cast or other Cast-specific code to this file.
   "+components/cdm/renderer",
   "+components/memory_pressure",
+  "+components/url_rewrite/common",
+  "+components/url_rewrite/mojom",
+  "+components/url_rewrite/renderer",
   "+content/public/renderer",
   "+media",
   "+mojo/public/cpp/bindings",
diff --git a/fuchsia/engine/renderer/web_engine_render_frame_observer.h b/fuchsia/engine/renderer/web_engine_render_frame_observer.h
index 05eb6ad9..13e63a0a 100644
--- a/fuchsia/engine/renderer/web_engine_render_frame_observer.h
+++ b/fuchsia/engine/renderer/web_engine_render_frame_observer.h
@@ -6,8 +6,8 @@
 #define FUCHSIA_ENGINE_RENDERER_WEB_ENGINE_RENDER_FRAME_OBSERVER_H_
 
 #include "base/callback.h"
+#include "components/url_rewrite/renderer/url_request_rules_receiver.h"
 #include "content/public/renderer/render_frame_observer.h"
-#include "fuchsia/engine/renderer/url_request_rules_receiver.h"
 
 namespace content {
 class RenderFrame;
@@ -29,7 +29,7 @@
   WebEngineRenderFrameObserver& operator=(const WebEngineRenderFrameObserver&) =
       delete;
 
-  UrlRequestRulesReceiver* url_request_rules_receiver() {
+  url_rewrite::UrlRequestRulesReceiver* url_request_rules_receiver() {
     return &url_request_rules_receiver_;
   }
 
@@ -37,7 +37,7 @@
   // content::RenderFrameObserver implementation.
   void OnDestruct() override;
 
-  UrlRequestRulesReceiver url_request_rules_receiver_;
+  url_rewrite::UrlRequestRulesReceiver url_request_rules_receiver_;
 
   base::OnceCallback<void(int)> on_render_frame_deleted_callback_;
 };
diff --git a/fuchsia/engine/renderer/web_engine_url_loader_throttle_provider.cc b/fuchsia/engine/renderer/web_engine_url_loader_throttle_provider.cc
index 46765bd..32508db4 100644
--- a/fuchsia/engine/renderer/web_engine_url_loader_throttle_provider.cc
+++ b/fuchsia/engine/renderer/web_engine_url_loader_throttle_provider.cc
@@ -4,8 +4,10 @@
 
 #include "fuchsia/engine/renderer/web_engine_url_loader_throttle_provider.h"
 
+#include "components/url_rewrite/common/url_loader_throttle.h"
+#include "components/url_rewrite/mojom/url_request_rewrite.mojom.h"
 #include "content/public/renderer/render_frame.h"
-#include "fuchsia/engine/common/web_engine_url_loader_throttle.h"
+#include "fuchsia/engine/common/cors_exempt_headers.h"
 #include "fuchsia/engine/renderer/web_engine_content_renderer_client.h"
 
 WebEngineURLLoaderThrottleProvider::WebEngineURLLoaderThrottleProvider(
@@ -39,7 +41,8 @@
           ->url_request_rules_receiver()
           ->GetCachedRules();
   if (rules) {
-    throttles.emplace_back(std::make_unique<WebEngineURLLoaderThrottle>(rules));
+    throttles.emplace_back(std::make_unique<url_rewrite::URLLoaderThrottle>(
+        rules, base::BindRepeating(&IsHeaderCorsExempt)));
   }
   return throttles;
 }
diff --git a/fuchsia/engine/url_request_rewrite_type_converters.cc b/fuchsia/engine/url_request_rewrite_type_converters.cc
index 85ca2ce3..eddf576a 100644
--- a/fuchsia/engine/url_request_rewrite_type_converters.cc
+++ b/fuchsia/engine/url_request_rewrite_type_converters.cc
@@ -21,19 +21,20 @@
 namespace mojo {
 
 template <>
-struct TypeConverter<mojom::UrlRequestRewriteAddHeadersPtr,
+struct TypeConverter<url_rewrite::mojom::UrlRequestRewriteAddHeadersPtr,
                      fuchsia::web::UrlRequestRewriteAddHeaders> {
-  static mojom::UrlRequestRewriteAddHeadersPtr Convert(
+  static url_rewrite::mojom::UrlRequestRewriteAddHeadersPtr Convert(
       const fuchsia::web::UrlRequestRewriteAddHeaders& input) {
-    mojom::UrlRequestRewriteAddHeadersPtr add_headers =
-        mojom::UrlRequestRewriteAddHeaders::New();
+    url_rewrite::mojom::UrlRequestRewriteAddHeadersPtr add_headers =
+        url_rewrite::mojom::UrlRequestRewriteAddHeaders::New();
     if (input.has_headers()) {
       for (const auto& header : input.headers()) {
         base::StringPiece header_name = cr_fuchsia::BytesAsString(header.name);
         base::StringPiece header_value =
             cr_fuchsia::BytesAsString(header.value);
-        mojom::UrlHeaderPtr url_header = mojom::UrlHeader::New(
-            std::string(header_name), std::string(header_value));
+        url_rewrite::mojom::UrlHeaderPtr url_header =
+            url_rewrite::mojom::UrlHeader::New(std::string(header_name),
+                                               std::string(header_value));
         add_headers->headers.push_back(std::move(url_header));
       }
     }
@@ -42,12 +43,12 @@
 };
 
 template <>
-struct TypeConverter<mojom::UrlRequestRewriteRemoveHeaderPtr,
+struct TypeConverter<url_rewrite::mojom::UrlRequestRewriteRemoveHeaderPtr,
                      fuchsia::web::UrlRequestRewriteRemoveHeader> {
-  static mojom::UrlRequestRewriteRemoveHeaderPtr Convert(
+  static url_rewrite::mojom::UrlRequestRewriteRemoveHeaderPtr Convert(
       const fuchsia::web::UrlRequestRewriteRemoveHeader& input) {
-    mojom::UrlRequestRewriteRemoveHeaderPtr remove_header =
-        mojom::UrlRequestRewriteRemoveHeader::New();
+    url_rewrite::mojom::UrlRequestRewriteRemoveHeaderPtr remove_header =
+        url_rewrite::mojom::UrlRequestRewriteRemoveHeader::New();
     if (input.has_query_pattern())
       remove_header->query_pattern = absl::make_optional(input.query_pattern());
     if (input.has_header_name()) {
@@ -59,12 +60,14 @@
 };
 
 template <>
-struct TypeConverter<mojom::UrlRequestRewriteSubstituteQueryPatternPtr,
-                     fuchsia::web::UrlRequestRewriteSubstituteQueryPattern> {
-  static mojom::UrlRequestRewriteSubstituteQueryPatternPtr Convert(
+struct TypeConverter<
+    url_rewrite::mojom::UrlRequestRewriteSubstituteQueryPatternPtr,
+    fuchsia::web::UrlRequestRewriteSubstituteQueryPattern> {
+  static url_rewrite::mojom::UrlRequestRewriteSubstituteQueryPatternPtr Convert(
       const fuchsia::web::UrlRequestRewriteSubstituteQueryPattern& input) {
-    mojom::UrlRequestRewriteSubstituteQueryPatternPtr substitute_query_pattern =
-        mojom::UrlRequestRewriteSubstituteQueryPattern::New();
+    url_rewrite::mojom::UrlRequestRewriteSubstituteQueryPatternPtr
+        substitute_query_pattern =
+            url_rewrite::mojom::UrlRequestRewriteSubstituteQueryPattern::New();
     if (input.has_pattern())
       substitute_query_pattern->pattern = input.pattern();
     if (input.has_substitution())
@@ -74,12 +77,12 @@
 };
 
 template <>
-struct TypeConverter<mojom::UrlRequestRewriteReplaceUrlPtr,
+struct TypeConverter<url_rewrite::mojom::UrlRequestRewriteReplaceUrlPtr,
                      fuchsia::web::UrlRequestRewriteReplaceUrl> {
-  static mojom::UrlRequestRewriteReplaceUrlPtr Convert(
+  static url_rewrite::mojom::UrlRequestRewriteReplaceUrlPtr Convert(
       const fuchsia::web::UrlRequestRewriteReplaceUrl& input) {
-    mojom::UrlRequestRewriteReplaceUrlPtr replace_url =
-        mojom::UrlRequestRewriteReplaceUrl::New();
+    url_rewrite::mojom::UrlRequestRewriteReplaceUrlPtr replace_url =
+        url_rewrite::mojom::UrlRequestRewriteReplaceUrl::New();
     if (input.has_url_ends_with())
       replace_url->url_ends_with = input.url_ends_with();
     if (input.has_new_url())
@@ -89,12 +92,12 @@
 };
 
 template <>
-struct TypeConverter<mojom::UrlRequestRewriteAppendToQueryPtr,
+struct TypeConverter<url_rewrite::mojom::UrlRequestRewriteAppendToQueryPtr,
                      fuchsia::web::UrlRequestRewriteAppendToQuery> {
-  static mojom::UrlRequestRewriteAppendToQueryPtr Convert(
+  static url_rewrite::mojom::UrlRequestRewriteAppendToQueryPtr Convert(
       const fuchsia::web::UrlRequestRewriteAppendToQuery& input) {
-    mojom::UrlRequestRewriteAppendToQueryPtr append_to_query =
-        mojom::UrlRequestRewriteAppendToQuery::New();
+    url_rewrite::mojom::UrlRequestRewriteAppendToQueryPtr append_to_query =
+        url_rewrite::mojom::UrlRequestRewriteAppendToQuery::New();
     if (input.has_query())
       append_to_query->query = input.query();
     return append_to_query;
@@ -102,44 +105,47 @@
 };
 
 template <>
-struct TypeConverter<mojom::UrlRequestAccessPolicy,
+struct TypeConverter<url_rewrite::mojom::UrlRequestAccessPolicy,
                      fuchsia::web::UrlRequestAction> {
-  static mojom::UrlRequestAccessPolicy Convert(
+  static url_rewrite::mojom::UrlRequestAccessPolicy Convert(
       const fuchsia::web::UrlRequestAction& input) {
     switch (input) {
       case fuchsia::web::UrlRequestAction::ALLOW:
-        return mojom::UrlRequestAccessPolicy::kAllow;
+        return url_rewrite::mojom::UrlRequestAccessPolicy::kAllow;
       case fuchsia::web::UrlRequestAction::DENY:
-        return mojom::UrlRequestAccessPolicy::kDeny;
+        return url_rewrite::mojom::UrlRequestAccessPolicy::kDeny;
     }
   }
 };
 
 template <>
-struct TypeConverter<mojom::UrlRequestActionPtr,
+struct TypeConverter<url_rewrite::mojom::UrlRequestActionPtr,
                      fuchsia::web::UrlRequestRewrite> {
-  static mojom::UrlRequestActionPtr Convert(
+  static url_rewrite::mojom::UrlRequestActionPtr Convert(
       const fuchsia::web::UrlRequestRewrite& input) {
     switch (input.Which()) {
       case fuchsia::web::UrlRequestRewrite::Tag::kAddHeaders:
-        return mojom::UrlRequestAction::NewAddHeaders(
-            mojo::ConvertTo<mojom::UrlRequestRewriteAddHeadersPtr>(
+        return url_rewrite::mojom::UrlRequestAction::NewAddHeaders(
+            mojo::ConvertTo<url_rewrite::mojom::UrlRequestRewriteAddHeadersPtr>(
                 input.add_headers()));
       case fuchsia::web::UrlRequestRewrite::Tag::kRemoveHeader:
-        return mojom::UrlRequestAction::NewRemoveHeader(
-            mojo::ConvertTo<mojom::UrlRequestRewriteRemoveHeaderPtr>(
+        return url_rewrite::mojom::UrlRequestAction::NewRemoveHeader(
+            mojo::ConvertTo<
+                url_rewrite::mojom::UrlRequestRewriteRemoveHeaderPtr>(
                 input.remove_header()));
       case fuchsia::web::UrlRequestRewrite::Tag::kSubstituteQueryPattern:
-        return mojom::UrlRequestAction::NewSubstituteQueryPattern(
-            mojo::ConvertTo<mojom::UrlRequestRewriteSubstituteQueryPatternPtr>(
+        return url_rewrite::mojom::UrlRequestAction::NewSubstituteQueryPattern(
+            mojo::ConvertTo<
+                url_rewrite::mojom::UrlRequestRewriteSubstituteQueryPatternPtr>(
                 input.substitute_query_pattern()));
       case fuchsia::web::UrlRequestRewrite::Tag::kReplaceUrl:
-        return mojom::UrlRequestAction::NewReplaceUrl(
-            mojo::ConvertTo<mojom::UrlRequestRewriteReplaceUrlPtr>(
+        return url_rewrite::mojom::UrlRequestAction::NewReplaceUrl(
+            mojo::ConvertTo<url_rewrite::mojom::UrlRequestRewriteReplaceUrlPtr>(
                 input.replace_url()));
       case fuchsia::web::UrlRequestRewrite::Tag::kAppendToQuery:
-        return mojom::UrlRequestAction::NewAppendToQuery(
-            mojo::ConvertTo<mojom::UrlRequestRewriteAppendToQueryPtr>(
+        return url_rewrite::mojom::UrlRequestAction::NewAppendToQuery(
+            mojo::ConvertTo<
+                url_rewrite::mojom::UrlRequestRewriteAppendToQueryPtr>(
                 input.append_to_query()));
       default:
         return nullptr;
@@ -148,11 +154,12 @@
 };
 
 template <>
-struct TypeConverter<mojom::UrlRequestRulePtr,
+struct TypeConverter<url_rewrite::mojom::UrlRequestRulePtr,
                      fuchsia::web::UrlRequestRewriteRule> {
-  static mojom::UrlRequestRulePtr Convert(
+  static url_rewrite::mojom::UrlRequestRulePtr Convert(
       const fuchsia::web::UrlRequestRewriteRule& input) {
-    mojom::UrlRequestRulePtr rule = mojom::UrlRequestRule::New();
+    url_rewrite::mojom::UrlRequestRulePtr rule =
+        url_rewrite::mojom::UrlRequestRule::New();
 
     if (input.has_hosts_filter()) {
       // Convert host names in case they contain non-ASCII characters.
@@ -174,25 +181,29 @@
       rule->schemes_filter = absl::make_optional(input.schemes_filter());
 
     if (input.has_rewrites()) {
-      rule->actions = mojo::ConvertTo<std::vector<mojom::UrlRequestActionPtr>>(
-          input.rewrites());
+      rule->actions =
+          mojo::ConvertTo<std::vector<url_rewrite::mojom::UrlRequestActionPtr>>(
+              input.rewrites());
     } else if (input.has_action()) {
-      rule->actions = std::vector<mojom::UrlRequestActionPtr>();
-      rule->actions.push_back(mojom::UrlRequestAction::NewPolicy(
-          mojo::ConvertTo<mojom::UrlRequestAccessPolicy>(input.action())));
+      rule->actions = std::vector<url_rewrite::mojom::UrlRequestActionPtr>();
+      rule->actions.push_back(url_rewrite::mojom::UrlRequestAction::NewPolicy(
+          mojo::ConvertTo<url_rewrite::mojom::UrlRequestAccessPolicy>(
+              input.action())));
     }
 
     return rule;
   }
 };
 
-mojom::UrlRequestRewriteRulesPtr
-TypeConverter<mojom::UrlRequestRewriteRulesPtr,
+url_rewrite::mojom::UrlRequestRewriteRulesPtr
+TypeConverter<url_rewrite::mojom::UrlRequestRewriteRulesPtr,
               std::vector<fuchsia::web::UrlRequestRewriteRule>>::
     Convert(const std::vector<fuchsia::web::UrlRequestRewriteRule>& input) {
-  mojom::UrlRequestRewriteRulesPtr rules = mojom::UrlRequestRewriteRules::New();
+  url_rewrite::mojom::UrlRequestRewriteRulesPtr rules =
+      url_rewrite::mojom::UrlRequestRewriteRules::New();
   for (const auto& rule : input) {
-    rules->rules.push_back(mojo::ConvertTo<mojom::UrlRequestRulePtr>(rule));
+    rules->rules.push_back(
+        mojo::ConvertTo<url_rewrite::mojom::UrlRequestRulePtr>(rule));
   }
   return rules;
 }
diff --git a/fuchsia/engine/url_request_rewrite_type_converters.h b/fuchsia/engine/url_request_rewrite_type_converters.h
index 77f55343..e936513 100644
--- a/fuchsia/engine/url_request_rewrite_type_converters.h
+++ b/fuchsia/engine/url_request_rewrite_type_converters.h
@@ -7,7 +7,7 @@
 
 #include <fuchsia/web/cpp/fidl.h>
 
-#include "fuchsia/engine/url_request_rewrite.mojom.h"
+#include "components/url_rewrite/mojom/url_request_rewrite.mojom.h"
 #include "mojo/public/cpp/bindings/type_converter.h"
 
 namespace mojo {
@@ -22,9 +22,9 @@
 // conversion is performed, the Mojo types are used as is to apply the rewrites
 // on URL requests.
 template <>
-struct TypeConverter<mojom::UrlRequestRewriteRulesPtr,
+struct TypeConverter<url_rewrite::mojom::UrlRequestRewriteRulesPtr,
                      std::vector<fuchsia::web::UrlRequestRewriteRule>> {
-  static mojom::UrlRequestRewriteRulesPtr Convert(
+  static url_rewrite::mojom::UrlRequestRewriteRulesPtr Convert(
       const std::vector<fuchsia::web::UrlRequestRewriteRule>& input);
 };
 
diff --git a/headless/BUILD.gn b/headless/BUILD.gn
index 1d414969..fb0093ed 100644
--- a/headless/BUILD.gn
+++ b/headless/BUILD.gn
@@ -62,6 +62,7 @@
     "$root_gen_dir/ui/resources/webui_generated_resources.pak",
     "$root_gen_dir/ui/resources/webui_resources.pak",
     "$root_gen_dir/ui/strings/app_locale_settings_en-US.pak",
+    "$root_gen_dir/ui/strings/ax_strings_en-US.pak",
     "$root_gen_dir/ui/strings/ui_strings_en-US.pak",
   ]
 
diff --git a/infra/config/generated/luci/cr-buildbucket-dev.cfg b/infra/config/generated/luci/cr-buildbucket-dev.cfg
index 7301397..c7a4135 100644
--- a/infra/config/generated/luci/cr-buildbucket-dev.cfg
+++ b/infra/config/generated/luci/cr-buildbucket-dev.cfg
@@ -54,6 +54,10 @@
       build_numbers: YES
       service_account: "chromium-ci-builder-dev@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -108,6 +112,10 @@
       build_numbers: YES
       service_account: "chromium-ci-builder-dev@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -163,6 +171,10 @@
       build_numbers: YES
       service_account: "chromium-ci-builder-dev@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -218,6 +230,10 @@
       build_numbers: YES
       service_account: "chromium-ci-builder-dev@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -272,6 +288,10 @@
       build_numbers: YES
       service_account: "chromium-ci-builder-dev@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -325,6 +345,10 @@
       build_numbers: YES
       service_account: "chromium-ci-builder-dev@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -379,6 +403,10 @@
       build_numbers: YES
       service_account: "chromium-ci-builder-dev@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -433,6 +461,10 @@
       build_numbers: YES
       service_account: "chromium-ci-builder-dev@chops-service-accounts.iam.gserviceaccount.com"
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 100
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index 7b791ce..e6e736a2 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -227,6 +227,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -310,6 +314,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -393,6 +401,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -476,6 +488,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -559,6 +575,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -642,6 +662,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -804,6 +828,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -887,6 +915,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -973,6 +1005,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -1059,6 +1095,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -1145,6 +1185,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -1231,6 +1275,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -1317,6 +1365,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -1403,6 +1455,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -1489,6 +1545,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -1575,6 +1635,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -1661,6 +1725,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -1747,6 +1815,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -1833,6 +1905,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -1919,6 +1995,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -2005,6 +2085,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -2088,6 +2172,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -2174,6 +2262,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -2260,6 +2352,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -2346,6 +2442,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -2432,6 +2532,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -2518,6 +2622,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -2604,6 +2712,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -2690,6 +2802,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -2776,6 +2892,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -2862,6 +2982,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -2948,6 +3072,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -3034,6 +3162,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -3120,6 +3252,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -3206,6 +3342,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -3289,6 +3429,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -3458,6 +3602,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -3544,6 +3692,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -3627,6 +3779,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -3710,6 +3866,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -3793,6 +3953,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -3876,6 +4040,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -3959,6 +4127,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -4042,6 +4214,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -4123,6 +4299,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -4204,6 +4384,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -4287,6 +4471,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -4370,6 +4558,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -4453,6 +4645,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -4536,6 +4732,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -4619,6 +4819,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -4702,6 +4906,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -4785,6 +4993,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -4868,6 +5080,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -4951,6 +5167,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -5034,6 +5254,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -5117,6 +5341,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -5200,6 +5428,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -5283,6 +5515,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -5366,6 +5602,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -5449,6 +5689,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -5532,6 +5776,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -5615,6 +5863,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -5698,6 +5950,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -5781,6 +6037,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -6266,6 +6526,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -6352,6 +6616,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -6438,6 +6706,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -6524,6 +6796,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -6610,6 +6886,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -6694,6 +6974,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -6778,6 +7062,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -6862,6 +7150,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -6946,6 +7238,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -7032,6 +7328,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -7118,6 +7418,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -7204,6 +7508,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -7290,6 +7598,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -7376,6 +7688,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -7462,6 +7778,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -7548,6 +7868,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -7634,6 +7958,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -7718,6 +8046,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -7802,6 +8134,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -7888,6 +8224,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -7974,6 +8314,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -8060,6 +8404,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -8146,6 +8494,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -8232,6 +8584,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -9381,6 +9737,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -9467,6 +9827,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -9553,6 +9917,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -9639,6 +10007,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -9725,6 +10097,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -9811,6 +10187,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -9897,6 +10277,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -9980,6 +10364,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -10066,6 +10454,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -10149,6 +10541,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -10235,6 +10631,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -10321,6 +10721,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -10407,6 +10811,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -10493,6 +10901,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -10579,6 +10991,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -10665,6 +11081,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -10751,6 +11171,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -10837,6 +11261,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -10923,6 +11351,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -11009,6 +11441,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -11095,6 +11531,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -11181,6 +11621,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -11267,6 +11711,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -11353,6 +11801,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -11439,6 +11891,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -11525,6 +11981,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -11611,6 +12071,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -11697,6 +12161,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -11783,6 +12251,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -11869,6 +12341,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -11955,6 +12431,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -12041,6 +12521,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -12127,6 +12611,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -12213,6 +12701,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -12299,6 +12791,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -12382,6 +12878,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -12465,6 +12965,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -12548,6 +13052,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -12630,6 +13138,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -12712,6 +13224,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -12796,6 +13312,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -12881,6 +13401,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -12965,6 +13489,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -13049,6 +13577,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -13130,6 +13662,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -13212,6 +13748,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -13293,6 +13833,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -13379,6 +13923,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -13465,6 +14013,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -13551,6 +14103,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -13637,6 +14193,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -13723,6 +14283,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -13809,6 +14373,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -13895,6 +14463,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -13981,6 +14553,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -14067,6 +14643,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -14153,6 +14733,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -14239,6 +14823,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -14325,6 +14913,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -14411,6 +15003,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -14497,6 +15093,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -14583,6 +15183,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -14669,6 +15273,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -14755,6 +15363,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -14987,6 +15599,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -15073,6 +15689,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -15159,6 +15779,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -15245,6 +15869,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -15331,6 +15959,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -15417,6 +16049,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -15503,6 +16139,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -15589,6 +16229,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -15675,6 +16319,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -15758,6 +16406,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -15841,6 +16493,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -15924,6 +16580,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -16006,6 +16666,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -16092,6 +16756,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -16178,6 +16846,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -16264,6 +16936,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -16347,6 +17023,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -16430,6 +17110,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -16513,6 +17197,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -16599,6 +17287,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -16685,6 +17377,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -16771,6 +17467,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -16857,6 +17557,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -16943,6 +17647,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -17029,6 +17737,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -17115,6 +17827,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -17201,6 +17917,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -17287,6 +18007,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -17373,6 +18097,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -17459,6 +18187,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -17545,6 +18277,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -17631,6 +18367,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -17717,6 +18457,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -17803,6 +18547,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -17888,6 +18636,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -18048,6 +18800,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -18134,6 +18890,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -18220,6 +18980,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -18306,6 +19070,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -18391,6 +19159,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -18476,6 +19248,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -18561,6 +19337,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -18721,6 +19501,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -18806,6 +19590,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -18892,6 +19680,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -18978,6 +19770,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -19064,6 +19860,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -19150,6 +19950,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -19236,6 +20040,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -19321,6 +20129,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -19407,6 +20219,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -19493,6 +20309,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -19656,6 +20476,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -19745,6 +20569,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -19828,6 +20656,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -19911,6 +20743,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -19994,6 +20830,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -20080,6 +20920,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -20166,6 +21010,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -20252,6 +21100,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -20337,6 +21189,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -20419,6 +21275,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -20505,6 +21365,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -20587,6 +21451,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -20669,6 +21537,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -20754,6 +21626,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -20839,6 +21715,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -20924,6 +21804,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -21010,6 +21894,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -21093,6 +21981,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -21176,6 +22068,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -21262,6 +22158,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -21348,6 +22248,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -21434,6 +22338,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -21520,6 +22428,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -21606,6 +22518,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -21692,6 +22608,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -21778,6 +22698,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -21864,6 +22788,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -21950,6 +22878,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -22036,6 +22968,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -22122,6 +23058,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -22208,6 +23148,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -22293,6 +23237,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -22378,6 +23326,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -22460,6 +23412,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -22546,6 +23502,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -22632,6 +23592,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -22718,6 +23682,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -22804,6 +23772,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -22890,6 +23862,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -22976,6 +23952,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -23062,6 +24042,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -23148,6 +24132,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -23313,6 +24301,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -23396,6 +24388,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -23479,6 +24475,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -23565,6 +24565,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -23648,6 +24652,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -24100,6 +25108,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -24186,6 +25198,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -24272,6 +25288,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -24358,6 +25378,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -24445,6 +25469,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -24610,6 +25638,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -24693,6 +25725,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -24779,6 +25815,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -24942,6 +25982,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -25025,6 +26069,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -25111,6 +26159,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -25197,6 +26249,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -25283,6 +26339,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -25369,6 +26429,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -25455,6 +26519,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -25541,6 +26609,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -25627,6 +26699,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -25713,6 +26789,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -25875,6 +26955,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -25961,6 +27045,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -26047,6 +27135,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -26133,6 +27225,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -26219,6 +27315,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -26305,6 +27405,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -26391,6 +27495,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -26477,6 +27585,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -26563,6 +27675,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -26649,6 +27765,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -26734,6 +27854,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -26820,6 +27944,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -26906,6 +28034,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -26992,6 +28124,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -27075,6 +28211,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -27158,6 +28298,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -27244,6 +28388,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -27326,6 +28474,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -27589,6 +28741,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -27672,6 +28828,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -27758,6 +28918,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -27844,6 +29008,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -27930,6 +29098,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -28013,6 +29185,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -28096,6 +29272,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -28179,6 +29359,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -28265,6 +29449,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -28351,6 +29539,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -28434,6 +29626,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -28517,6 +29713,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -28603,6 +29803,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -28689,6 +29893,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -28775,6 +29983,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -28861,6 +30073,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -28947,6 +30163,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -29030,6 +30250,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -29113,6 +30337,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -29197,6 +30425,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -29283,6 +30515,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -29369,6 +30605,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -29455,6 +30695,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -29618,6 +30862,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -29701,6 +30949,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -29784,6 +31036,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -29867,6 +31123,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -29950,6 +31210,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -30033,6 +31297,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -30116,6 +31384,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -30201,6 +31473,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -30287,6 +31563,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -30373,6 +31653,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -30608,6 +31892,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -30693,6 +31981,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -30782,6 +32074,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -30867,6 +32163,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -30957,6 +32257,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -31043,6 +32347,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -31128,6 +32436,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -31217,6 +32529,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -31302,6 +32618,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -31391,6 +32711,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -31476,6 +32800,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -31561,6 +32889,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -31646,6 +32978,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -31731,6 +33067,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -31816,6 +33156,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -31901,6 +33245,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -31987,6 +33335,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -32073,6 +33425,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -32156,6 +33512,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -32239,6 +33599,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -32322,6 +33686,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -32408,6 +33776,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -32929,6 +34301,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -33015,6 +34391,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -33101,6 +34481,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -33186,6 +34570,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -33269,6 +34657,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -33361,6 +34753,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -33444,6 +34840,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -33530,6 +34930,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -33616,6 +35020,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -33699,6 +35107,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -33782,6 +35194,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -33865,6 +35281,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -33948,6 +35368,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -34031,6 +35455,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -34114,6 +35542,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -34200,6 +35632,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -34286,6 +35722,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -34369,6 +35809,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -34455,6 +35899,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -34538,6 +35986,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -34624,6 +36076,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -34707,6 +36163,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -34790,6 +36250,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -34873,6 +36337,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -34959,6 +36427,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -35042,6 +36514,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -35128,6 +36604,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -35211,6 +36691,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -35294,6 +36778,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -35380,6 +36868,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -35463,6 +36955,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -35546,6 +37042,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -35629,6 +37129,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -35715,6 +37219,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -35798,6 +37306,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -35881,6 +37393,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -35964,6 +37480,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -36050,6 +37570,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -36133,6 +37657,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -36218,6 +37746,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -36301,6 +37833,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -36383,6 +37919,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -36469,6 +38009,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -36713,6 +38257,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -36799,6 +38347,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -36885,6 +38437,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -36968,6 +38524,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -37054,6 +38614,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -37138,6 +38702,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -37222,6 +38790,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -37306,6 +38878,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -37392,6 +38968,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -37693,6 +39273,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -37778,6 +39362,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -37863,6 +39451,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -37948,6 +39540,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -38033,6 +39629,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -38118,6 +39718,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -38199,6 +39803,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -38280,6 +39888,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -38365,6 +39977,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -38448,6 +40064,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -38531,6 +40151,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -38614,6 +40238,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -38696,6 +40324,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -38778,6 +40410,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -38862,6 +40498,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -38945,6 +40585,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -39026,6 +40670,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -39107,6 +40755,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -39191,6 +40843,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -39272,6 +40928,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -39353,6 +41013,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -39434,6 +41098,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -39517,6 +41185,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -39600,6 +41272,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -39683,6 +41359,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -39766,6 +41446,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -39849,6 +41533,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -39932,6 +41620,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -40015,6 +41707,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -40098,6 +41794,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -40181,6 +41881,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -40264,6 +41968,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -40350,6 +42058,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -40433,6 +42145,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -40516,6 +42232,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -40978,6 +42698,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -41063,6 +42787,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -41148,6 +42876,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -41233,6 +42965,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -41319,6 +43055,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -41402,6 +43142,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -41485,6 +43229,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -41568,6 +43316,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -41651,6 +43403,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -41894,6 +43650,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -41976,6 +43736,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -42059,6 +43823,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -42145,6 +43913,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -42547,6 +44319,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -42633,6 +44409,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -42716,6 +44496,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -42799,6 +44583,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -42882,6 +44670,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -43249,6 +45041,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -43332,6 +45128,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -43415,6 +45215,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -43498,6 +45302,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -43583,6 +45391,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -43668,6 +45480,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -43753,6 +45569,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -43835,6 +45655,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -43920,6 +45744,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -44003,6 +45831,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -44086,6 +45918,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -44169,6 +46005,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -44252,6 +46092,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -44477,6 +46321,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -44560,6 +46408,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -45124,12 +46976,22 @@
       dimensions: "pool:luci.chromium.findit"
       dimensions: "ssd:1"
       exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
+        cmd: "-properties-optional"
       }
       properties:
         '{'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
+        '  },'
         '  "$build/goma": {'
         '    "rpc_extra_params": "?prod",'
         '    "server_host": "goma.chromium.org",'
@@ -45147,6 +47009,7 @@
         '      "v.test_suite"'
         '    ]'
         '  },'
+        '  "led_builder_is_bootstrapped": true,'
         '  "recipe": "findit/chromium/single_revision"'
         '}'
       execution_timeout_secs: 28800
@@ -45401,6 +47264,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -45447,6 +47314,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -45493,6 +47364,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -45539,6 +47414,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -45585,6 +47464,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -45631,6 +47514,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -45677,6 +47564,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -45723,6 +47614,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -45769,6 +47664,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -45815,6 +47714,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -45861,6 +47764,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -45907,6 +47814,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -45953,6 +47864,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -45999,6 +47914,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46045,6 +47964,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46091,6 +48014,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46137,6 +48064,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46183,6 +48114,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46229,6 +48164,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46280,6 +48219,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46327,6 +48270,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46374,6 +48321,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46420,6 +48371,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46466,6 +48421,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46511,6 +48470,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46557,6 +48520,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46603,6 +48570,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46649,6 +48620,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46695,6 +48670,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46741,6 +48720,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46787,6 +48770,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46833,6 +48820,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46879,6 +48870,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46925,6 +48920,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -46972,6 +48971,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -47019,6 +49022,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -47066,6 +49073,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -47113,6 +49124,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -47160,6 +49175,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -47207,6 +49226,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -47253,6 +49276,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -47299,6 +49326,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -47348,6 +49379,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -47397,6 +49432,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -47444,6 +49483,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -47491,6 +49534,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -47538,6 +49585,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -47585,6 +49636,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -47631,6 +49686,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -47677,6 +49736,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -47747,6 +49810,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -47830,6 +49897,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -48151,6 +50222,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -48238,6 +50313,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -48325,6 +50404,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -48412,6 +50495,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -48673,6 +50760,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -48760,6 +50851,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -48949,6 +51044,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -49036,6 +51135,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -49123,6 +51226,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -49210,6 +51317,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -49297,6 +51408,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -49384,6 +51499,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -49471,6 +51590,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -49558,6 +51681,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -49645,6 +51772,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -49732,6 +51863,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -49819,6 +51954,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -49906,6 +52045,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -49993,6 +52136,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -50254,6 +52401,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -50349,6 +52500,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -50444,6 +52599,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -50729,6 +52888,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -50817,6 +52980,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -50904,6 +53071,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -50991,6 +53162,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -51078,6 +53253,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -51165,6 +53344,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -51256,6 +53439,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -51347,6 +53534,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -51434,6 +53625,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -51706,6 +53901,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -51793,6 +53992,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -51881,6 +54084,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -51968,6 +54175,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -52055,6 +54266,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -52142,6 +54357,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -52229,6 +54448,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -52316,6 +54539,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -52403,6 +54630,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -52490,6 +54721,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -52577,6 +54812,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -52664,6 +54903,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -52751,6 +54994,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -52838,6 +55085,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -52925,6 +55176,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -53012,6 +55267,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -53099,6 +55358,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -53186,6 +55449,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -53274,6 +55541,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -53361,6 +55632,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -53448,6 +55723,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -53536,6 +55815,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -53623,6 +55906,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -53710,6 +55997,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -53797,6 +56088,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -53883,6 +56178,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -53970,6 +56269,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -54057,6 +56360,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -54144,6 +56451,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -54231,6 +56542,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -54525,6 +56840,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -54612,6 +56931,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -54698,6 +57021,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -54785,6 +57112,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -54872,6 +57203,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -54959,6 +57294,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -55046,6 +57385,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -55315,6 +57658,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -55402,6 +57749,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -55447,26 +57798,33 @@
       dimensions: "os:Ubuntu-18.04"
       dimensions: "pool:luci.chromium.try"
       exe {
-        cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
-        cipd_version: "refs/heads/main"
-        cmd: "luciexe"
+        cipd_package: "infra/chromium/bootstrapper/${platform}"
+        cipd_version: "latest"
+        cmd: "bootstrapper"
       }
       properties:
         '{'
-        '  "$build/goma": {'
-        '    "enable_ats": true,'
-        '    "rpc_extra_params": "?prod",'
-        '    "server_host": "goma.chromium.org",'
-        '    "use_luci_auth": true'
+        '  "$bootstrap/exe": {'
+        '    "exe": {'
+        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
+        '      "cipd_version": "refs/heads/main",'
+        '      "cmd": ['
+        '        "luciexe"'
+        '      ]'
+        '    }'
         '  },'
-        '  "$recipe_engine/resultdb/test_presentation": {'
-        '    "column_keys": [],'
-        '    "grouping_keys": ['
-        '      "status",'
-        '      "v.test_suite"'
-        '    ]'
+        '  "$bootstrap/properties": {'
+        '    "properties_file": "infra/config/generated/builders/try/chromeos-arm-generic-rel/properties.textpb",'
+        '    "top_level_project": {'
+        '      "ref": "refs/heads/main",'
+        '      "repo": {'
+        '        "host": "chromium.googlesource.com",'
+        '        "project": "chromium/src"'
+        '      }'
+        '    }'
         '  },'
         '  "builder_group": "tryserver.chromium.chromiumos",'
+        '  "led_builder_is_bootstrapped": true,'
         '  "recipe": "chromium_trybot"'
         '}'
       execution_timeout_secs: 14400
@@ -55488,6 +57846,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -55575,6 +57937,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -55662,6 +58028,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -55833,6 +58203,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -55917,6 +58291,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -56001,6 +58379,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -56085,6 +58467,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -56170,6 +58556,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -56255,6 +58645,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -56340,6 +58734,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -56425,6 +58823,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -56512,6 +58914,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -56686,6 +59092,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -56868,6 +59278,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -57042,6 +59456,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -57129,6 +59547,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -57216,6 +59638,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -57303,6 +59729,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -57390,6 +59820,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -57477,6 +59911,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -57563,6 +60001,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -57649,6 +60091,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -57735,6 +60181,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -57821,6 +60271,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -57904,6 +60358,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -57987,6 +60445,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -58070,6 +60532,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -58153,6 +60619,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -58236,6 +60706,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -58319,6 +60793,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -58402,6 +60880,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -58485,6 +60967,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -58568,6 +61054,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -58651,6 +61141,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -58734,6 +61228,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -58817,6 +61315,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -58900,6 +61402,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -58983,6 +61489,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -59066,6 +61576,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -59149,6 +61663,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -59232,6 +61750,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -59315,6 +61837,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -59398,6 +61924,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -59481,6 +62011,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -59564,6 +62098,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -59647,6 +62185,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -59728,6 +62270,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -59809,6 +62355,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -59890,6 +62440,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -59971,6 +62525,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -60052,6 +62610,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -60133,6 +62695,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -60214,6 +62780,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -60295,6 +62865,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -60376,6 +62950,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -60457,6 +63035,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -60538,6 +63120,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -60619,6 +63205,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -60700,6 +63290,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -60783,6 +63377,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -60866,6 +63464,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -60949,6 +63551,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -61032,6 +63638,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -61115,6 +63725,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -61198,6 +63812,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -61281,6 +63899,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -61364,6 +63986,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -61447,6 +64073,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -61530,6 +64160,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -61613,6 +64247,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -61696,6 +64334,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -61779,6 +64421,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -61862,6 +64508,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -61945,6 +64595,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -62028,6 +64682,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -62109,6 +64767,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -62190,6 +64852,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -62273,6 +64939,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -62448,6 +65118,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -62537,6 +65211,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -62634,6 +65312,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -62726,6 +65408,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -62826,6 +65512,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -62915,6 +65605,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -63004,6 +65698,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -63093,6 +65791,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -63189,6 +65891,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -63278,6 +65984,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -63367,6 +66077,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -63456,6 +66170,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -63545,6 +66263,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -63631,6 +66353,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -63717,6 +66443,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -63804,6 +66534,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -63891,6 +66625,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -63981,6 +66719,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -64155,6 +66897,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -64329,6 +67075,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -64416,6 +67166,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -64503,6 +67257,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -64591,6 +67349,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -64678,6 +67440,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -64765,6 +67531,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -64852,6 +67622,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -64939,6 +67713,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -65026,6 +67804,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -65113,6 +67895,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -65207,6 +67993,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -65294,6 +68084,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -65381,6 +68175,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -65467,6 +68265,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -65554,6 +68356,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -65641,6 +68447,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -65732,6 +68542,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -66199,6 +69013,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -66286,6 +69104,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -66373,6 +69195,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -66460,6 +69286,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -66541,6 +69371,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -66628,6 +69462,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -66715,6 +69553,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -66802,6 +69644,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -66889,6 +69735,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -66977,6 +69827,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -67065,6 +69919,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -67152,6 +70010,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -67239,6 +70101,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -67412,6 +70278,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -67499,6 +70369,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -67586,6 +70460,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -67673,6 +70551,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -67960,6 +70842,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -68050,6 +70936,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -68137,6 +71027,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -68227,6 +71121,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -68317,6 +71215,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -68401,6 +71303,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -68653,6 +71559,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -68741,6 +71651,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -68828,6 +71742,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -68914,6 +71832,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -69001,6 +71923,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -69088,6 +72014,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -69175,6 +72105,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -69262,6 +72196,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -69350,6 +72288,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -69437,6 +72379,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -69524,6 +72470,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -69611,6 +72561,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -69883,6 +72837,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -69970,6 +72928,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -70058,6 +73020,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -70146,6 +73112,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -70320,6 +73290,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -70407,6 +73381,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -70498,6 +73476,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -70585,6 +73567,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -70676,6 +73662,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -70764,6 +73754,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -71035,6 +74029,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -71122,6 +74120,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -71209,6 +74211,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -71296,6 +74302,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -71383,6 +74393,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -71470,6 +74484,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -71556,6 +74574,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -71724,6 +74746,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -71893,6 +74919,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -71978,6 +75008,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -72062,6 +75096,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -72147,6 +75185,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -72236,6 +75278,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -72513,6 +75559,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -72594,6 +75644,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -72678,6 +75732,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -72762,6 +75820,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -72847,6 +75909,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -72932,6 +75998,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -73017,6 +76087,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -73102,6 +76176,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -73369,6 +76447,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -73454,6 +76536,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -73539,6 +76625,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -73624,6 +76714,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -73709,6 +76803,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -73794,6 +76892,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -73879,6 +76981,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -73964,6 +77070,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -74049,6 +77159,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -74135,6 +77249,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -74221,6 +77339,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -74306,6 +77428,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -74391,6 +77517,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -74475,6 +77605,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -74722,6 +77856,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -75163,6 +78301,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -75511,6 +78653,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -75599,6 +78745,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -75693,6 +78843,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -75869,6 +79023,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -76042,6 +79200,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -76126,6 +79288,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -76546,6 +79712,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -76630,6 +79800,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -76717,6 +79891,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -76804,6 +79982,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -76891,6 +80073,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -76978,6 +80164,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -77071,6 +80261,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -77158,6 +80352,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -77442,6 +80640,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -77532,6 +80734,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -77619,6 +80825,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -77706,6 +80916,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -77794,6 +81008,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -77881,6 +81099,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -77969,6 +81191,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -78056,6 +81282,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -78143,6 +81373,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -78230,6 +81464,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -78317,6 +81555,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -78404,6 +81646,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -78571,6 +81817,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 1
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -78661,6 +81911,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -78700,6 +81954,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -78745,6 +82003,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -78784,6 +82046,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -78829,6 +82095,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -78868,6 +82138,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -78913,6 +82187,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -78952,6 +82230,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -78991,6 +82273,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -79030,6 +82316,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -79090,6 +82380,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -79134,6 +82428,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -79178,6 +82476,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -79216,6 +82518,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -79254,6 +82560,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -79298,6 +82608,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -79342,6 +82656,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -79380,6 +82698,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -79424,6 +82746,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -79468,6 +82794,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -79506,6 +82836,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -79550,6 +82884,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -79594,6 +82932,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -79632,6 +82974,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -79670,6 +83016,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
@@ -79708,6 +83058,10 @@
         value: 100
       }
       experiments {
+        key: "luci.recipes.use_python3"
+        value: 5
+      }
+      experiments {
         key: "luci.use_realms"
         value: 100
       }
diff --git a/infra/config/lib/bootstrap.star b/infra/config/lib/bootstrap.star
index 0bfcc13..2c7dafb 100644
--- a/infra/config/lib/bootstrap.star
+++ b/infra/config/lib/bootstrap.star
@@ -29,6 +29,8 @@
 # to ensure that the modified properties get written out to the property files
 load("./builder_config.star", _ = "builder_config")  # @unused
 
+PROPERTIES_OPTIONAL = "PROPERTIES_OPTIONAL"
+
 _NON_BOOTSTRAPPED_PROPERTIES = [
     # The led_recipes_tester recipe examines the recipe property in the input
     # properties of the build definition retrieved using led to determine which
@@ -55,11 +57,16 @@
     "sheriff_rotations",
 ]
 
-def _bootstrappable_recipe_key(name):
-    return graph.key("@chromium", "", "bootstrappable_recipe", name)
+def _recipe_bootstrappability_key(name):
+    return graph.key("@chromium", "", "recipe_bootstrappability", name)
 
-def register_bootstrappable_recipe(name):
-    graph.add_node(_bootstrappable_recipe_key(name))
+def register_recipe_bootstrappability(name, bootstrappability):
+    if bootstrappability not in (False, True, PROPERTIES_OPTIONAL):
+        fail("bootstrap must be one of False, True or PROPERTIES_OPTIONAL")
+    if bootstrappability:
+        graph.add_node(_recipe_bootstrappability_key(name), props = {
+            "bootstrappability": bootstrappability,
+        })
 
 def _bootstrap_key(bucket_name, builder_name):
     return graph.key("@chromium", "", "bootstrap", "{}/{}".format(bucket_name, builder_name))
@@ -120,15 +127,26 @@
             if not bootstrap_node:
                 continue
             executable = bootstrap_node.props.executable
-            bootstrappable_recipe_node = graph.node(_bootstrappable_recipe_key(executable))
-            if not bootstrappable_recipe_node:
+            recipe_bootstrappability_node = graph.node(_recipe_bootstrappability_key(executable))
+            if not recipe_bootstrappability_node:
                 continue
 
-            bootstrap = bootstrap_node.props.bootstrap
+            builder_properties = json.decode(builder.properties)
+            bootstrapper_args = []
 
-            properties_file = "builders/{}/{}/properties.textpb".format(bucket_name, builder_name)
-            non_bootstrapped_properties = {
-                "$bootstrap/properties": {
+            if recipe_bootstrappability_node.props.bootstrappability == PROPERTIES_OPTIONAL:
+                non_bootstrapped_properties = builder_properties
+                bootstrapper_args = ["-properties-optional"]
+
+            else:
+                non_bootstrapped_properties = {}
+
+                for p in _NON_BOOTSTRAPPED_PROPERTIES:
+                    if p in builder_properties:
+                        non_bootstrapped_properties[p] = builder_properties[p]
+
+                properties_file = "builders/{}/{}/properties.textpb".format(bucket_name, builder_name)
+                non_bootstrapped_properties["$bootstrap/properties"] = {
                     "top_level_project": {
                         "repo": {
                             "host": "chromium.googlesource.com",
@@ -137,23 +155,21 @@
                         "ref": settings.ref,
                     },
                     "properties_file": "infra/config/generated/{}".format(properties_file),
-                },
-                "$bootstrap/exe": {
-                    "exe": builder.exe,
-                },
-                "led_builder_is_bootstrapped": True,
-            }
-            builder_properties = json.decode(builder.properties)
-            for p in _NON_BOOTSTRAPPED_PROPERTIES:
-                if p in builder_properties:
-                    non_bootstrapped_properties[p] = builder_properties[p]
-            ctx.output[properties_file] = json.indent(json.encode(builder_properties), indent = "  ")
+                }
+                ctx.output[properties_file] = json.indent(json.encode(builder_properties), indent = "  ")
 
-            if bootstrap:
+            if bootstrap_node.props.bootstrap:
+                non_bootstrapped_properties.update({
+                    "$bootstrap/exe": {
+                        "exe": builder.exe,
+                    },
+                    "led_builder_is_bootstrapped": True,
+                })
+
                 builder.properties = json.encode(non_bootstrapped_properties)
 
                 builder.exe.cipd_package = "infra/chromium/bootstrapper/${platform}"
                 builder.exe.cipd_version = "latest"
-                builder.exe.cmd = ["bootstrapper"]
+                builder.exe.cmd = ["bootstrapper"] + bootstrapper_args
 
 lucicfg.generator(_bootstrap_properties)
diff --git a/infra/config/lib/builders.star b/infra/config/lib/builders.star
index eb18be27..1cf24fb 100644
--- a/infra/config/lib/builders.star
+++ b/infra/config/lib/builders.star
@@ -31,6 +31,7 @@
 load("./bootstrap.star", "register_bootstrap")
 load("./builder_config.star", "builder_config", "register_builder_config")
 load("./listify.star", "listify")
+load("./recipe_experiments.star", "register_recipe_experiments_ref")
 
 ################################################################################
 # Constants for use with the builder function                                  #
@@ -778,6 +779,8 @@
     if builder == None:
         return None
 
+    register_recipe_experiments_ref(bucket, name, executable)
+
     register_builder_config(bucket, name, builder_group, builder_spec, mirrors)
 
     register_bootstrap(bucket, name, bootstrap, executable)
diff --git a/infra/config/lib/recipe_experiments.star b/infra/config/lib/recipe_experiments.star
new file mode 100644
index 0000000..b0a2e0e
--- /dev/null
+++ b/infra/config/lib/recipe_experiments.star
@@ -0,0 +1,57 @@
+# Copyright 2021 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Library for enabling experiments on a per-recipe basis.
+
+For each experiment registered for a recipe, if a builder that uses the recipe
+does not have the experiment set, it will be set with the percentage specified
+for the recipe.
+
+See //recipes.star for information on setting experiments for a recipe.
+"""
+
+load("@stdlib//internal/graph.star", "graph")
+load("@stdlib//internal/luci/common.star", "kinds")
+
+RECIPE_EXPERIMENTS_KIND = "recipe_experiments"
+
+def _recipe_experiments_key(recipe):
+    return graph.key("@chromium", "", RECIPE_EXPERIMENTS_KIND, recipe)
+
+def register_recipe_experiments(recipe, experiments):
+    graph.add_node(_recipe_experiments_key(recipe), props = {
+        "experiments": experiments,
+    })
+
+RECIPE_EXPERIMENTS_REF_KIND = "recipe_experiments_ref"
+
+def _recipe_experiments_ref_key(bucket, builder):
+    return graph.key("@chromium", "", kinds.BUCKET, bucket, RECIPE_EXPERIMENTS_REF_KIND, builder)
+
+def register_recipe_experiments_ref(bucket, builder, recipe):
+    key = _recipe_experiments_ref_key(bucket, builder)
+    graph.add_node(key)
+    graph.add_edge(key, _recipe_experiments_key(recipe))
+
+def _set_recipe_experiments(ctx):
+    cfg = None
+    for f in ctx.output:
+        if f.startswith("luci/cr-buildbucket"):
+            cfg = ctx.output[f]
+            break
+    if cfg == None:
+        fail("There is no buildbucket configuration file to reformat properties")
+
+    for bucket in cfg.buckets:
+        bucket_name = bucket.name
+        for builder in bucket.swarming.builders:
+            builder_name = builder.name
+            recipe_experiments_nodes = graph.children(_recipe_experiments_ref_key(bucket_name, builder_name), RECIPE_EXPERIMENTS_KIND)
+            if len(recipe_experiments_nodes) != 1:
+                fail("Each builder should refer to 1 recipe")
+            recipe_experiments_node = recipe_experiments_nodes[0]
+            for experiment, percentage in recipe_experiments_node.props.experiments.items():
+                builder.experiments.setdefault(experiment, percentage)
+
+lucicfg.generator(_set_recipe_experiments)
diff --git a/infra/config/recipes.star b/infra/config/recipes.star
index 2f8222b..ff9b82ee 100644
--- a/infra/config/recipes.star
+++ b/infra/config/recipes.star
@@ -2,12 +2,20 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-load("//lib/bootstrap.star", "register_bootstrappable_recipe")
+load("//lib/bootstrap.star", "PROPERTIES_OPTIONAL", "register_recipe_bootstrappability")
+load("//lib/recipe_experiments.star", "register_recipe_experiments")
 
 _RECIPE_NAME_PREFIX = "recipe:"
 
 def _recipe_for_package(cipd_package):
-    def recipe(*, name, cipd_version = None, recipe = None, use_python3 = False, bootstrappable = False):
+    def recipe(
+            *,
+            name,
+            cipd_version = None,
+            recipe = None,
+            use_python3 = False,
+            bootstrappable = False,
+            experiments = None):
         """Declare a recipe for the given package.
 
         A wrapper around luci.recipe with a fixed cipd_package and some
@@ -21,6 +29,7 @@
               information.
             cipd_version: See luci.recipe.
             recipe: See luci.recipe.
+            use_python3: See luci.recipe.
             bootstrappable: Whether or not the recipe supports the chromium
               bootstrapper. A recipe supports the bootstrapper if the following
               conditions are met:
@@ -32,6 +41,14 @@
                 skips analysis and performs a full build if
                 chromium_bootstrap.skip_analysis_reasons is non-empty. This will
                 be true if calling chromium_tests.determine_compilation_targets.
+              In addition to a True or False value, PROPERTIES_OPTIONAL can be
+              specified. This value will cause the builder's executable to be
+              changed to the bootstrapper in properties optional mode, which
+              will by default not bootstrap any properties. On a per-run basis
+              the $bootstrap/properties property can be set to bootstrap properties.
+            experiments: Experiments to apply to a builder using the recipe. If
+              the builder specifies an experiment, the experiment value from the
+              recipe will be ignored.
         """
 
         # Force the caller to put the recipe prefix rather than adding it
@@ -50,8 +67,9 @@
             use_python3 = use_python3,
         )
 
-        if bootstrappable:
-            register_bootstrappable_recipe(name)
+        register_recipe_bootstrappability(name, bootstrappable)
+
+        register_recipe_experiments(name, experiments or {})
 
         return ret
 
@@ -108,6 +126,9 @@
 build_recipe(
     name = "recipe:chromium",
     bootstrappable = True,
+    experiments = {
+        "luci.recipes.use_python3": 5,
+    },
 )
 
 build_recipe(
@@ -155,6 +176,9 @@
 build_recipe(
     name = "recipe:chromium_trybot",
     bootstrappable = True,
+    experiments = {
+        "luci.recipes.use_python3": 1,
+    },
 )
 
 build_recipe(
@@ -179,6 +203,7 @@
 
 build_recipe(
     name = "recipe:findit/chromium/single_revision",
+    bootstrappable = PROPERTIES_OPTIONAL,
 )
 
 build_recipe(
@@ -203,6 +228,7 @@
 
 build_recipe(
     name = "recipe:swarming/staging",
+    use_python3 = True,
 )
 
 build_recipe(
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star
index 1fe0a4c..9a890cf3 100644
--- a/infra/config/subprojects/chromium/try.star
+++ b/infra/config/subprojects/chromium/try.star
@@ -817,6 +817,7 @@
 try_.chromium_chromiumos_builder(
     name = "chromeos-arm-generic-rel",
     branch_selector = branches.CROS_LTS_MILESTONE,
+    bootstrap = True,
     builderless = not settings.is_main,
     main_list_view = "try",
     os = os.LINUX_BIONIC_REMOVE,
diff --git a/infra/config/subprojects/findit/findit.star b/infra/config/subprojects/findit/findit.star
index db68032a..885532cd 100644
--- a/infra/config/subprojects/findit/findit.star
+++ b/infra/config/subprojects/findit/findit.star
@@ -54,6 +54,7 @@
 # longer overridable with Buildbucket V2
 builder(
     name = "findit-rerun",
+    bootstrap = True,
     executable = "recipe:findit/chromium/single_revision",
     goma_backend = goma.backend.RBE_PROD,
     reclient_instance = rbe_instance.DEFAULT,
diff --git a/ios/chrome/app/resources/ios_chrome_repack.gni b/ios/chrome/app/resources/ios_chrome_repack.gni
index 08474db..94bab42 100644
--- a/ios/chrome/app/resources/ios_chrome_repack.gni
+++ b/ios/chrome/app/resources/ios_chrome_repack.gni
@@ -35,6 +35,7 @@
       "${root_gen_dir}/ios/chrome/ios_strings_",
       "${root_gen_dir}/third_party/libaddressinput/address_input_strings_",
       "${root_gen_dir}/ui/strings/app_locale_settings_",
+      "${root_gen_dir}/ui/strings/ax_strings_",
       "${root_gen_dir}/ui/strings/ui_strings_",
     ]
 
@@ -55,6 +56,7 @@
       "//ios/chrome/app/strings:ios_strings",
       "//third_party/libaddressinput:strings",
       "//ui/strings:app_locale_settings",
+      "//ui/strings:ax_strings",
       "//ui/strings:ui_strings",
     ]
 
diff --git a/ios/chrome/browser/passwords/ios_chrome_password_manager_client.mm b/ios/chrome/browser/passwords/ios_chrome_password_manager_client.mm
index 8b1bcc8..443b7956 100644
--- a/ios/chrome/browser/passwords/ios_chrome_password_manager_client.mm
+++ b/ios/chrome/browser/passwords/ios_chrome_password_manager_client.mm
@@ -228,7 +228,9 @@
     password_manager::CredentialLeakType leak_type,
     const GURL& origin,
     const std::u16string& username) {
-  [bridge_ showPasswordBreachForLeakType:leak_type URL:origin];
+  [bridge_ showPasswordBreachForLeakType:leak_type
+                                     URL:origin
+                                username:username];
 }
 
 bool IOSChromePasswordManagerClient::IsSavingAndFillingEnabled(
diff --git a/ios/chrome/browser/passwords/password_controller.mm b/ios/chrome/browser/passwords/password_controller.mm
index bb3a266..c242d13 100644
--- a/ios/chrome/browser/passwords/password_controller.mm
+++ b/ios/chrome/browser/passwords/password_controller.mm
@@ -343,7 +343,8 @@
 }
 
 - (void)showPasswordBreachForLeakType:(CredentialLeakType)leakType
-                                  URL:(const GURL&)URL {
+                                  URL:(const GURL&)URL
+                             username:(const std::u16string&)username {
   [self.passwordBreachDispatcher showPasswordBreachForLeakType:leakType];
 }
 
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
index f3706e04..4e0f73a 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_egtest.mm
@@ -160,8 +160,7 @@
 #pragma mark - Tests
 
 // Tests that all items are accessible on the home page.
-// TODO(crbug.com/1237925): Re-enable once misisng accessibility label is fixed.
-- (void)DISABLED_testAccessibility {
+- (void)testAccessibility {
   [ChromeEarlGrey verifyAccessibilityForCurrentScreen];
 }
 
@@ -487,9 +486,7 @@
 // Tests that when navigating back to the NTP while having the omnibox focused
 // and moved up, the scroll position restored is the position before the omnibox
 // is selected.
-// Disable the test due to ios official build failure.
-// TODO(crbug.com/1243222): enable the test with fix.
-- (void)DISABLED_testPositionRestoredWithOmniboxFocused {
+- (void)testPositionRestoredWithOmniboxFocused {
   [self addMostVisitedTile];
 
   // Add suggestions to be able to scroll on iPad.
@@ -776,8 +773,7 @@
 
 // Test to ensure that feed can be enabled/disabled and that feed header changes
 // accordingly.
-// TODO(crbug.com/1194106): Flaky on ios-simulator-noncq.
-- (void)DISABLED_testToggleFeedEnabled {
+- (void)testToggleFeedEnabled {
   [self
       testNTPInitialPositionAndContent:[NewTabPageAppInterface collectionView]];
 
@@ -799,7 +795,7 @@
   [self
       testNTPInitialPositionAndContent:[NewTabPageAppInterface collectionView]];
   [[EarlGrey selectElementWithMatcher:chrome_test_util::DiscoverHeaderLabel()]
-      assertWithMatcher:grey_nil()];
+      assertWithMatcher:grey_notVisible()];
   [self checkIfNTPIsScrollable];
 
   // Re-enable feed.
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
index b7bda78..5d75a81 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-6845f3ba34f3349d39f634198b07ef1f2cc5ab6e
\ No newline at end of file
+5da94496a6036476405ee112b4f21a3a8d4555d3
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
index 24ac3a2..204655d 100644
--- a/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-46003525ddd5885a5edb93f17832793550139a7f
\ No newline at end of file
+c0e7d6582b005f1c730439bf070359f66542ab31
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
index ab626cb..398a177 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-4c836b13a4ae6285a0c564fa5f0c354c0af0fc99
\ No newline at end of file
+66566dbfa7fe4bf2f10e70ef43cdf953a7b21b39
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
index 963939a..f65c43f 100644
--- a/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/chrome_sso_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-46a3b7e60cea428e3b446afa3486c6006002965f
\ No newline at end of file
+3abdaac67d0fce6b572a1ee15ee5cfbea9f5b219
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
index 3ddf0d5..dbd40612 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-f835d148756c2e82356be1e4b89dbf61dedd45ae
\ No newline at end of file
+07d6c0312262f7e652325da872cf56afd2fdacfc
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
index 31963a2a..c4ebc88 100644
--- a/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_dogfood_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-7347fa4be6b2f28f4f8fb2ecef8a5ff17248e019
\ No newline at end of file
+da303077ec667dcf8d9b7bd4e593385a2a8ea598
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
index 46196cd..2d80f60 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-b05bf0b4f359596814d084ffda7b4a20dd5b981a
\ No newline at end of file
+a84f6df2e29c716e8dde6065e28c9fdd34ddbecb
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
index 9d4367f..4a274940 100644
--- a/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/remoting_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-99b321eefc5b64034b67d9190c5588864b97a0cc
\ No newline at end of file
+bb3157a65d7dc145c7a663bfd10139eeafe68288
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
index 117f028..a29a1b0 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.ios.zip.sha1
@@ -1 +1 @@
-3af17c1f1401a6e7c9064f84647df651ffadffed
\ No newline at end of file
+b719b20698427db4a2350b0ccf208a5657879101
\ No newline at end of file
diff --git a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1 b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
index e60b654..aa3109c 100644
--- a/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
+++ b/ios/google_internal/frameworks/web_view_shell_internal_dynamic_framework.iossimulator.zip.sha1
@@ -1 +1 @@
-5575e140563c845b577b52f19cb2a8e03b0f0d28
\ No newline at end of file
+07eea62c1cdc6f8f568862fdf976ac632cbb8c94
\ No newline at end of file
diff --git a/ios/web_view/BUILD.gn b/ios/web_view/BUILD.gn
index 8f2363a..0f68d63 100644
--- a/ios/web_view/BUILD.gn
+++ b/ios/web_view/BUILD.gn
@@ -56,7 +56,6 @@
   "public/cwv_flags.h",
   "public/cwv_html_element.h",
   "public/cwv_identity.h",
-  "public/cwv_legacy_tls_warning_handler.h",
   "public/cwv_metrics_provider.h",
   "public/cwv_navigation_action.h",
   "public/cwv_navigation_delegate.h",
@@ -131,8 +130,6 @@
     "internal/cwv_flags_internal.h",
     "internal/cwv_html_element.mm",
     "internal/cwv_html_element_internal.h",
-    "internal/cwv_legacy_tls_warning_handler.mm",
-    "internal/cwv_legacy_tls_warning_handler_internal.h",
     "internal/cwv_navigation_action.mm",
     "internal/cwv_navigation_action_internal.h",
     "internal/cwv_navigation_type.mm",
@@ -322,7 +319,6 @@
     "//ios/components/credential_provider_extension:password_spec_fetcher",
     "//ios/components/credential_provider_extension:password_util",
     "//ios/components/io_thread",
-    "//ios/components/security_interstitials/legacy_tls",
     "//ios/components/webui:provider",
     "//ios/components/webui:url_constants",
     "//ios/components/webui/sync_internals",
@@ -436,7 +432,6 @@
     "internal/cwv_favicon_unittest.mm",
     "internal/cwv_flags_unittest.mm",
     "internal/cwv_html_element_unittest.mm",
-    "internal/cwv_legacy_tls_warning_handler_unittest.mm",
     "internal/cwv_preferences_unittest.mm",
     "internal/cwv_preview_element_info_unittest.mm",
     "internal/cwv_ssl_error_handler_unittest.mm",
@@ -517,6 +512,7 @@
     "${root_gen_dir}/components/strings/components_strings_",
     "${root_gen_dir}/components/strings/components_locale_settings_",
     "${root_gen_dir}/ui/strings/app_locale_settings_",
+    "${root_gen_dir}/ui/strings/ax_strings_",
     "${root_gen_dir}/ui/strings/ui_strings_",
   ]
 
@@ -524,6 +520,7 @@
     "//components/strings:components_locale_settings",
     "//components/strings:components_strings",
     "//ui/strings:app_locale_settings",
+    "//ui/strings:ax_strings",
     "//ui/strings:ui_strings",
   ]
   input_locales = ios_packed_locales
diff --git a/ios/web_view/internal/DEPS b/ios/web_view/internal/DEPS
index 3017e95..fdb1f20 100644
--- a/ios/web_view/internal/DEPS
+++ b/ios/web_view/internal/DEPS
@@ -45,7 +45,6 @@
   "+google_apis",
   "+ios/components/credential_provider_extension",
   "+ios/components/io_thread",
-  "+ios/components/security_interstitials",
   "+ios/components/webui",
   "+ios/net",
   "+ios/web/common",
diff --git a/ios/web_view/internal/autofill/cwv_autofill_controller.mm b/ios/web_view/internal/autofill/cwv_autofill_controller.mm
index e22e72c..65ec9b4 100644
--- a/ios/web_view/internal/autofill/cwv_autofill_controller.mm
+++ b/ios/web_view/internal/autofill/cwv_autofill_controller.mm
@@ -619,24 +619,33 @@
 }
 
 - (void)showPasswordBreachForLeakType:(CredentialLeakType)leakType
-                                  URL:(const GURL&)URL {
+                                  URL:(const GURL&)URL
+                             username:(const std::u16string&)username {
+  CWVPasswordLeakType cwvLeakType = 0;
+  if (password_manager::IsPasswordSaved(leakType)) {
+    cwvLeakType |= CWVPasswordLeakTypeSaved;
+  }
+  if (password_manager::IsPasswordUsedOnOtherSites(leakType)) {
+    cwvLeakType |= CWVPasswordLeakTypeUsedOnOtherSites;
+  }
+  if (password_manager::IsSyncingPasswordsNormally(leakType)) {
+    cwvLeakType |= CWVPasswordLeakTypeSyncingNormally;
+  }
   if ([self.delegate
           respondsToSelector:@selector(autofillController:
                                  notifyUserOfPasswordLeakOnURL:leakType:)]) {
-    CWVPasswordLeakType cwvLeakType = 0;
-    if (password_manager::IsPasswordSaved(leakType)) {
-      cwvLeakType |= CWVPasswordLeakTypeSaved;
-    }
-    if (password_manager::IsPasswordUsedOnOtherSites(leakType)) {
-      cwvLeakType |= CWVPasswordLeakTypeUsedOnOtherSites;
-    }
-    if (password_manager::IsSyncingPasswordsNormally(leakType)) {
-      cwvLeakType |= CWVPasswordLeakTypeSyncingNormally;
-    }
     [self.delegate autofillController:self
         notifyUserOfPasswordLeakOnURL:net::NSURLWithGURL(URL)
                              leakType:cwvLeakType];
   }
+  if ([self.delegate respondsToSelector:@selector
+                     (autofillController:
+                         notifyUserOfPasswordLeakOnURL:leakType:username:)]) {
+    [self.delegate autofillController:self
+        notifyUserOfPasswordLeakOnURL:net::NSURLWithGURL(URL)
+                             leakType:cwvLeakType
+                             username:base::SysUTF16ToNSString(username)];
+  }
 }
 
 - (void)showPasswordProtectionWarning:(NSString*)warningText
diff --git a/ios/web_view/internal/autofill/cwv_autofill_controller_unittest.mm b/ios/web_view/internal/autofill/cwv_autofill_controller_unittest.mm
index d0497ff..c14188d 100644
--- a/ios/web_view/internal/autofill/cwv_autofill_controller_unittest.mm
+++ b/ios/web_view/internal/autofill/cwv_autofill_controller_unittest.mm
@@ -406,6 +406,10 @@
   OCMExpect([delegate autofillController:autofill_controller_
            notifyUserOfPasswordLeakOnURL:net::NSURLWithGURL(leak_url)
                                 leakType:expected_leak_type]);
+  OCMExpect([delegate autofillController:autofill_controller_
+           notifyUserOfPasswordLeakOnURL:net::NSURLWithGURL(leak_url)
+                                leakType:expected_leak_type
+                                username:@"fake-username"]);
 
   password_manager_client_->NotifyUserCredentialsWereLeaked(
       leak_type, leak_url, base::SysNSStringToUTF16(@"fake-username"));
diff --git a/ios/web_view/internal/cwv_legacy_tls_warning_handler.mm b/ios/web_view/internal/cwv_legacy_tls_warning_handler.mm
deleted file mode 100644
index a767ca2..0000000
--- a/ios/web_view/internal/cwv_legacy_tls_warning_handler.mm
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/web_view/internal/cwv_legacy_tls_warning_handler_internal.h"
-
-#include "ios/components/security_interstitials/legacy_tls/legacy_tls_tab_allow_list.h"
-#import "ios/web/public/navigation/navigation_manager.h"
-#import "net/base/mac/url_conversions.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@implementation CWVLegacyTLSWarningHandler {
-  web::WebState* _webState;
-  void (^_warningPageHTMLCallback)(NSString*);
-}
-
-- (instancetype)initWithWebState:(web::WebState*)webState
-                             URL:(NSURL*)URL
-                           error:(NSError*)error
-         warningPageHTMLCallback:(void (^)(NSString*))warningPageHTMLCallback {
-  self = [super init];
-  if (self) {
-    _webState = webState;
-    _URL = URL;
-    _error = error;
-    _warningPageHTMLCallback = warningPageHTMLCallback;
-    LegacyTLSTabAllowList::CreateForWebState(webState);
-  }
-  return self;
-}
-
-#pragma mark - Public Methods
-
-- (void)displayWarningPageWithHTML:(NSString*)HTML {
-  if (!_warningPageHTMLCallback) {
-    return;
-  }
-
-  _warningPageHTMLCallback(HTML);
-  _warningPageHTMLCallback = nil;
-}
-
-- (void)overrideWarningAndReloadPage {
-  LegacyTLSTabAllowList::FromWebState(_webState)->AllowDomain(
-      net::GURLWithNSURL(_URL).host());
-  _webState->GetNavigationManager()->Reload(web::ReloadType::NORMAL,
-                                            /*check_for_repost=*/true);
-}
-
-@end
diff --git a/ios/web_view/internal/cwv_legacy_tls_warning_handler_internal.h b/ios/web_view/internal/cwv_legacy_tls_warning_handler_internal.h
deleted file mode 100644
index ce0aacb8..0000000
--- a/ios/web_view/internal/cwv_legacy_tls_warning_handler_internal.h
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_WEB_VIEW_INTERNAL_CWV_LEGACY_TLS_WARNING_HANDLER_INTERNAL_H_
-#define IOS_WEB_VIEW_INTERNAL_CWV_LEGACY_TLS_WARNING_HANDLER_INTERNAL_H_
-
-#include "ios/web_view/public/cwv_legacy_tls_warning_handler.h"
-
-#import "ios/web/public/web_state.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@interface CWVLegacyTLSWarningHandler ()
-
-// Designated initializer.
-// |URL| The URL that is using legacy TLS.
-// |error| The NSError object containing details of the warning.
-// |warningPageHTMLCallback| Callback to be invoked to display an error page.
-- (instancetype)initWithWebState:(web::WebState*)webState
-                             URL:(NSURL*)URL
-                           error:(NSError*)error
-         warningPageHTMLCallback:(void (^)(NSString*))warningPageHTMLCallback
-    NS_DESIGNATED_INITIALIZER;
-
-@end
-
-NS_ASSUME_NONNULL_END
-
-#endif  // IOS_WEB_VIEW_INTERNAL_CWV_LEGACY_TLS_WARNING_HANDLER_INTERNAL_H_
diff --git a/ios/web_view/internal/cwv_legacy_tls_warning_handler_unittest.mm b/ios/web_view/internal/cwv_legacy_tls_warning_handler_unittest.mm
deleted file mode 100644
index af87bb8..0000000
--- a/ios/web_view/internal/cwv_legacy_tls_warning_handler_unittest.mm
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/web_view/internal/cwv_legacy_tls_warning_handler_internal.h"
-
-#include <memory>
-
-#import "ios/components/security_interstitials/legacy_tls/legacy_tls_tab_allow_list.h"
-#import "ios/web/public/test/fakes/fake_navigation_manager.h"
-#import "ios/web/public/test/fakes/fake_web_state.h"
-#import "net/base/mac/url_conversions.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "testing/gtest_mac.h"
-#include "testing/platform_test.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace ios_web_view {
-
-using CWVLegacyTLSWarningHandlerTest = PlatformTest;
-
-TEST_F(CWVLegacyTLSWarningHandlerTest, Initialization) {
-  web::FakeWebState web_state;
-  NSURL* URL = [NSURL URLWithString:@"https://www.chromium.org"];
-  NSDictionary* user_info =
-      @{NSLocalizedDescriptionKey : @"This is an error description."};
-  NSError* error = [NSError errorWithDomain:@"TestDomain"
-                                       code:-1
-                                   userInfo:user_info];
-  CWVLegacyTLSWarningHandler* legacy_tls_warning_handler =
-      [[CWVLegacyTLSWarningHandler alloc] initWithWebState:&web_state
-                                                       URL:URL
-                                                     error:error
-                                   warningPageHTMLCallback:^(NSString* HTML){
-                                       // No op.
-                                   }];
-  EXPECT_NSEQ(URL, legacy_tls_warning_handler.URL);
-  EXPECT_NSEQ(error, legacy_tls_warning_handler.error);
-}
-
-TEST_F(CWVLegacyTLSWarningHandlerTest, DisplayHTML) {
-  web::FakeWebState web_state;
-  NSURL* URL = [NSURL URLWithString:@"https://www.chromium.org"];
-  NSError* error = [NSError errorWithDomain:@"TestDomain" code:-1 userInfo:nil];
-  __block NSString* displayed_html = nil;
-  CWVLegacyTLSWarningHandler* legacy_tls_warning_handler =
-      [[CWVLegacyTLSWarningHandler alloc] initWithWebState:&web_state
-                                                       URL:URL
-                                                     error:error
-                                   warningPageHTMLCallback:^(NSString* HTML) {
-                                     displayed_html = HTML;
-                                   }];
-
-  [legacy_tls_warning_handler
-      displayWarningPageWithHTML:@"This is a test warning page."];
-  EXPECT_NSEQ(@"This is a test warning page.", displayed_html);
-}
-
-TEST_F(CWVLegacyTLSWarningHandlerTest, OverrideAndReload) {
-  web::FakeWebState web_state;
-  auto navigation_manager = std::make_unique<web::FakeNavigationManager>();
-  web::FakeNavigationManager* navigation_manager_ptr = navigation_manager.get();
-  web_state.SetNavigationManager(std::move(navigation_manager));
-  NSURL* URL = [NSURL URLWithString:@"https://www.chromium.org"];
-  NSError* error = [NSError errorWithDomain:@"TestDomain" code:-1 userInfo:nil];
-  CWVLegacyTLSWarningHandler* legacy_tls_warning_handler =
-      [[CWVLegacyTLSWarningHandler alloc] initWithWebState:&web_state
-                                                       URL:URL
-                                                     error:error
-                                   warningPageHTMLCallback:^(NSString* HTML){
-                                       // No op.
-                                   }];
-
-  LegacyTLSTabAllowList* allow_list =
-      LegacyTLSTabAllowList::FromWebState(&web_state);
-  std::string host = net::GURLWithNSURL(URL).host();
-  EXPECT_FALSE(allow_list->IsDomainAllowed(host));
-  [legacy_tls_warning_handler overrideWarningAndReloadPage];
-  EXPECT_TRUE(allow_list->IsDomainAllowed(host));
-  EXPECT_TRUE(navigation_manager_ptr->ReloadWasCalled());
-}
-
-}  // namespace ios_web_view
diff --git a/ios/web_view/internal/cwv_ssl_error_handler.mm b/ios/web_view/internal/cwv_ssl_error_handler.mm
index ce0502f..25cdd81 100644
--- a/ios/web_view/internal/cwv_ssl_error_handler.mm
+++ b/ios/web_view/internal/cwv_ssl_error_handler.mm
@@ -18,6 +18,7 @@
   web::WebState* _webState;
   net::SSLInfo _SSLInfo;
   void (^_errorPageHTMLCallback)(NSString*);
+  BOOL _overridden;
 }
 
 - (instancetype)initWithWebState:(web::WebState*)webState
@@ -32,6 +33,7 @@
     _error = error;
     _SSLInfo = SSLInfo;
     _errorPageHTMLCallback = errorPageHTMLCallback;
+    _overridden = NO;
   }
   return self;
 }
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 3be3d9d..e4f04067 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
@@ -229,7 +229,9 @@
     password_manager::CredentialLeakType leak_type,
     const GURL& origin,
     const std::u16string& username) {
-  [bridge_ showPasswordBreachForLeakType:leak_type URL:origin];
+  [bridge_ showPasswordBreachForLeakType:leak_type
+                                     URL:origin
+                                username:username];
 }
 
 bool WebViewPasswordManagerClient::IsSavingAndFillingEnabled(
diff --git a/ios/web_view/internal/web_view_web_client.mm b/ios/web_view/internal/web_view_web_client.mm
index e3f0b12..ead98b8 100644
--- a/ios/web_view/internal/web_view_web_client.mm
+++ b/ios/web_view/internal/web_view_web_client.mm
@@ -8,7 +8,6 @@
 
 #include "base/bind.h"
 #include "base/check.h"
-#import "base/ios/ns_error_util.h"
 #include "base/mac/bundle_locations.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/task/post_task.h"
@@ -18,14 +17,11 @@
 #import "components/password_manager/ios/password_manager_java_script_feature.h"
 #include "components/ssl_errors/error_info.h"
 #include "components/strings/grit/components_strings.h"
-#include "ios/components/security_interstitials/legacy_tls/legacy_tls_tab_allow_list.h"
 #include "ios/components/webui/web_ui_url_constants.h"
-#import "ios/net/protocol_handler_util.h"
 #include "ios/web/common/user_agent.h"
 #include "ios/web/public/security/ssl_status.h"
 #include "ios/web/public/thread/web_task_traits.h"
 #include "ios/web/public/thread/web_thread.h"
-#import "ios/web_view/internal/cwv_legacy_tls_warning_handler_internal.h"
 #import "ios/web_view/internal/cwv_ssl_error_handler_internal.h"
 #import "ios/web_view/internal/cwv_ssl_status_internal.h"
 #import "ios/web_view/internal/cwv_ssl_util.h"
@@ -36,7 +32,6 @@
 #import "ios/web_view/public/cwv_navigation_delegate.h"
 #import "ios/web_view/public/cwv_web_view.h"
 #import "net/base/mac/url_conversions.h"
-#include "net/base/net_errors.h"
 #include "net/cert/cert_status_flags.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/resource/resource_bundle.h"
@@ -133,10 +128,9 @@
 
 bool WebViewWebClient::IsLegacyTLSAllowedForHost(web::WebState* web_state,
                                                  const std::string& hostname) {
-  auto* allowlist = LegacyTLSTabAllowList::FromWebState(web_state);
-  if (!allowlist)
-    return false;
-  return allowlist->IsDomainAllowed(hostname);
+  // TODO(crbug.com/1191799): Legacy TLS should be supported via an interstitial
+  // UI that allows the user to override if desired.
+  return true;
 }
 
 void WebViewWebClient::PrepareErrorPage(
@@ -150,47 +144,26 @@
     base::OnceCallback<void(NSString*)> callback) {
   DCHECK(error);
 
+  // TODO(crbug.com/1191799): Add support for handling legacy TLS.
   CWVWebView* web_view = [CWVWebView webViewForWebState:web_state];
-  __block base::OnceCallback<void(NSString*)> html_callback =
-      std::move(callback);
-  NSError* final_underlying_error =
-      base::ios::GetFinalUnderlyingErrorFromError(error);
-  if ([final_underlying_error.domain isEqual:net::kNSErrorDomain] &&
-      final_underlying_error.code == net::ERR_SSL_OBSOLETE_VERSION) {
-    if ([web_view.navigationDelegate
-            respondsToSelector:@selector(webView:
-                                   handleLegacyTLSWarningWithHandler:)]) {
-      CWVLegacyTLSWarningHandler* handler = [[CWVLegacyTLSWarningHandler alloc]
-                 initWithWebState:web_state
-                              URL:net::NSURLWithGURL(url)
-                            error:error
-          warningPageHTMLCallback:^(NSString* HTML) {
-            std::move(html_callback).Run(HTML);
-          }];
-      [web_view.navigationDelegate webView:web_view
-          handleLegacyTLSWarningWithHandler:handler];
-      return;
-    }
-  } else if (info.has_value()) {
-    if ([web_view.navigationDelegate
-            respondsToSelector:@selector(webView:handleSSLErrorWithHandler:)]) {
-      CWVSSLErrorHandler* handler =
-          [[CWVSSLErrorHandler alloc] initWithWebState:web_state
-                                                   URL:net::NSURLWithGURL(url)
-                                                 error:error
-                                               SSLInfo:info.value()
-                                 errorPageHTMLCallback:^(NSString* HTML) {
-                                   std::move(html_callback).Run(HTML);
-                                 }];
-      [web_view.navigationDelegate webView:web_view
-                 handleSSLErrorWithHandler:handler];
-      return;
-    }
+  if (info.has_value() &&
+      [web_view.navigationDelegate
+          respondsToSelector:@selector(webView:handleSSLErrorWithHandler:)]) {
+    __block base::OnceCallback<void(NSString*)> error_html_callback =
+        std::move(callback);
+    CWVSSLErrorHandler* handler =
+        [[CWVSSLErrorHandler alloc] initWithWebState:web_state
+                                                 URL:net::NSURLWithGURL(url)
+                                               error:error
+                                             SSLInfo:info.value()
+                               errorPageHTMLCallback:^(NSString* HTML) {
+                                 std::move(error_html_callback).Run(HTML);
+                               }];
+    [web_view.navigationDelegate webView:web_view
+               handleSSLErrorWithHandler:handler];
+  } else {
+    std::move(callback).Run(error.localizedDescription);
   }
-
-  // If the error is still not handled by the client, just display the raw error
-  // description.
-  std::move(html_callback).Run(error.localizedDescription);
 }
 
 bool WebViewWebClient::EnableLongPressUIContextMenu() const {
diff --git a/ios/web_view/public/cwv_autofill_controller_delegate.h b/ios/web_view/public/cwv_autofill_controller_delegate.h
index b2b517b..0c79c68 100644
--- a/ios/web_view/public/cwv_autofill_controller_delegate.h
+++ b/ios/web_view/public/cwv_autofill_controller_delegate.h
@@ -151,10 +151,19 @@
 
 // Called if a submitted username and password combination is determined to be
 // leaked for |URL|. |leakType| provides additional context of the leak.
+// Deprecated: Use |autofillController:notifyUserOfPasswordLeakOnURL:username:|.
 - (void)autofillController:(CWVAutofillController*)autofillController
     notifyUserOfPasswordLeakOnURL:(NSURL*)URL
                          leakType:(CWVPasswordLeakType)leakType;
 
+// Called if a submitted username and password combination is determined to be
+// leaked for |URL|. |leakType| provides additional context of the leak.
+// |username| The username whose password is leaked.
+- (void)autofillController:(CWVAutofillController*)autofillController
+    notifyUserOfPasswordLeakOnURL:(NSURL*)URL
+                         leakType:(CWVPasswordLeakType)leakType
+                         username:(NSString*)username;
+
 // Called when the user taps on the "Suggest password..." suggestion when trying
 // to sign up for a new account on a site. |generatedPassword| is a randomly
 // generated password that, if accepted in |decisionHandler|, will be injected
diff --git a/ios/web_view/public/cwv_defines.h b/ios/web_view/public/cwv_defines.h
index cad1d3d3..6553516 100644
--- a/ios/web_view/public/cwv_defines.h
+++ b/ios/web_view/public/cwv_defines.h
@@ -43,9 +43,6 @@
 // Supports APIs used to implement the trusted vault for chrome sync.
 #define IOS_WEB_VIEW_SUPPORTS_TRUSTED_VAULT_APIS 1
 
-// Allows handling of legacy TLS warnings with CWVLegacyTLSWarningHandler.
-#define IOS_WEB_VIEW_SUPPORTS_CWV_LEGACY_TLS_WARNING_HANDLER 1
-
 // Supports -[CWVAutofillDataManager updatePassword:newUsername:newPassword:].
 #define IOS_WEB_VIEW_SUPPORTS_UPDATING_PASSWORDS 1
 
diff --git a/ios/web_view/public/cwv_legacy_tls_warning_handler.h b/ios/web_view/public/cwv_legacy_tls_warning_handler.h
deleted file mode 100644
index cdc1907..0000000
--- a/ios/web_view/public/cwv_legacy_tls_warning_handler.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2021 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_WEB_VIEW_PUBLIC_CWV_LEGACY_TLS_WARNING_HANDLER_H_
-#define IOS_WEB_VIEW_PUBLIC_CWV_LEGACY_TLS_WARNING_HANDLER_H_
-
-#import <Foundation/Foundation.h>
-
-#import "cwv_export.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-// Used to handle legacy TLS warnings. For example TLS 1.0 and 1.1.
-CWV_EXPORT
-@interface CWVLegacyTLSWarningHandler : NSObject
-
-// The URL of the failing page.
-@property(nonatomic, readonly) NSURL* URL;
-
-// The NSError associated with this warning.
-@property(nonatomic, readonly) NSError* error;
-
-- (instancetype)init NS_UNAVAILABLE;
-
-// Call to display a warning page with |HTML|.
-// This will no op after the first call.
-- (void)displayWarningPageWithHTML:(NSString*)HTML;
-
-// Call to override the warning and reload the page.
-// This will no op after the first call.
-- (void)overrideWarningAndReloadPage;
-
-@end
-
-NS_ASSUME_NONNULL_END
-
-#endif  // IOS_WEB_VIEW_PUBLIC_CWV_LEGACY_TLS_WARNING_HANDLER_H_
diff --git a/ios/web_view/public/cwv_navigation_delegate.h b/ios/web_view/public/cwv_navigation_delegate.h
index c7f66920..997beb8 100644
--- a/ios/web_view/public/cwv_navigation_delegate.h
+++ b/ios/web_view/public/cwv_navigation_delegate.h
@@ -13,7 +13,6 @@
 NS_ASSUME_NONNULL_BEGIN
 
 @class CWVDownloadTask;
-@class CWVLegacyTLSWarningHandler;
 @class CWVSSLErrorHandler;
 @class CWVWebView;
 
@@ -59,12 +58,6 @@
 - (void)webView:(CWVWebView*)webView
     handleSSLErrorWithHandler:(CWVSSLErrorHandler*)handler;
 
-// Notifies the delegate that page load was cancelled due to legacy TLS.
-// |handler| can be used to display a warning page and allow the user to
-// override the warning and proceed to the page.
-- (void)webView:(CWVWebView*)webView
-    handleLegacyTLSWarningWithHandler:(CWVLegacyTLSWarningHandler*)handler;
-
 // Called when the web view requests to start downloading a file.
 //
 // The delegate can either:
diff --git a/ios/web_view/shell/BUILD.gn b/ios/web_view/shell/BUILD.gn
index 88aca65..90e8d10 100644
--- a/ios/web_view/shell/BUILD.gn
+++ b/ios/web_view/shell/BUILD.gn
@@ -2,9 +2,15 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import("//build/config/ios/ios_sdk.gni")
 import("//build/config/ios/rules.gni")
 
 declare_args() {
+  # The bundle identifier. Overriding this will affect the provisioning profile
+  # used, and hence will affect the app's capabilities.
+  ios_web_view_shell_bundle_identifier =
+      "$ios_app_bundle_id_prefix.ios-web-view-shell"
+
   # Authorization service implementation used in ios_web_view_shell. Uses a fake
   # implementation by default. Override this with a real implementation to make
   # Sync feature work in the shell. The real implementation must provide
@@ -19,6 +25,11 @@
   ios_web_view_shell_risk_data_loader =
       "//ios/web_view/shell:shell_risk_data_loader_fake_impl"
 
+  # Trusted vault requires 1p access to the authorization services.
+  # Override this with a real implementation to enable trusted vault.
+  ios_web_view_shell_trusted_vault_provider =
+      "//ios/web_view/shell:shell_trusted_vault_provider_fake_impl"
+
   # Path to an entitlements file used in ios_web_view_shell. Can be overridden
   # to provide an alternative.
   ios_web_view_shell_entitlements_path = "//build/config/ios/entitlements.plist"
@@ -38,6 +49,7 @@
     "//ios/web_view/shell/resources",
   ]
   entitlements_path = ios_web_view_shell_entitlements_path
+  bundle_identifier = ios_web_view_shell_bundle_identifier
 
   if (ios_web_view_shell_links_with_material_components_framework) {
     deps += [
@@ -92,6 +104,28 @@
   configs += [ "//build/config/compiler:enable_arc" ]
 }
 
+source_set("shell_trusted_vault_provider_interface") {
+  sources = [ "shell_trusted_vault_provider.h" ]
+
+  deps = [
+    ":shell_auth_service_interface",
+    "//ios/web_view:web_view+link",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+}
+
+source_set("shell_trusted_vault_provider_fake_impl") {
+  sources = [ "shell_trusted_vault_provider_fake.m" ]
+
+  deps = [
+    ":shell_trusted_vault_provider_interface",
+    "//ios/web_view:web_view+link",
+  ]
+
+  configs += [ "//build/config/compiler:enable_arc" ]
+}
+
 source_set("shell") {
   sources = [
     "shell_app_delegate.h",
@@ -101,8 +135,6 @@
     "shell_exe_main.m",
     "shell_translation_delegate.h",
     "shell_translation_delegate.m",
-    "shell_trusted_vault_provider.h",
-    "shell_trusted_vault_provider.m",
     "shell_view_controller.h",
     "shell_view_controller.m",
   ]
@@ -110,10 +142,12 @@
   deps = [
     ":shell_auth_service_interface",
     ":shell_risk_data_loader_interface",
+    ":shell_trusted_vault_provider_interface",
     "//ios/third_party/webkit",
     "//ios/web_view:web_view+link",
     ios_web_view_shell_auth_service,
     ios_web_view_shell_risk_data_loader,
+    ios_web_view_shell_trusted_vault_provider,
   ]
 
   libs = [ "resolv" ]
diff --git a/ios/web_view/shell/shell_autofill_delegate.m b/ios/web_view/shell/shell_autofill_delegate.m
index 1a33119..40d4936 100644
--- a/ios/web_view/shell/shell_autofill_delegate.m
+++ b/ios/web_view/shell/shell_autofill_delegate.m
@@ -305,8 +305,9 @@
 
 - (void)autofillController:(CWVAutofillController*)autofillController
     notifyUserOfPasswordLeakOnURL:(NSURL*)URL
-                         leakType:(CWVPasswordLeakType)leakType {
-  NSLog(@"Password on %@ is leaked!", URL);
+                         leakType:(CWVPasswordLeakType)leakType
+                         username:(NSString*)username {
+  NSLog(@"Password on %@ is leaked for username %@!", URL, username);
 }
 
 - (void)autofillController:(CWVAutofillController*)autofillController
diff --git a/ios/web_view/shell/shell_trusted_vault_provider.h b/ios/web_view/shell/shell_trusted_vault_provider.h
index aea8406d..62674d7 100644
--- a/ios/web_view/shell/shell_trusted_vault_provider.h
+++ b/ios/web_view/shell/shell_trusted_vault_provider.h
@@ -7,10 +7,17 @@
 
 #import <ChromeWebView/ChromeWebView.h>
 
+#import "ios/web_view/shell/shell_auth_service.h"
+
 NS_ASSUME_NONNULL_BEGIN
 
 // Provides trusted vault functions to ChromeWebView.
 @interface ShellTrustedVaultProvider : NSObject <CWVTrustedVaultProvider>
+
+- (instancetype)initWithAuthService:(ShellAuthService*)authService
+    NS_DESIGNATED_INITIALIZER;
+- (instancetype)init NS_UNAVAILABLE;
+
 @end
 
 NS_ASSUME_NONNULL_END
diff --git a/ios/web_view/shell/shell_trusted_vault_provider.m b/ios/web_view/shell/shell_trusted_vault_provider_fake.m
similarity index 94%
rename from ios/web_view/shell/shell_trusted_vault_provider.m
rename to ios/web_view/shell/shell_trusted_vault_provider_fake.m
index ce07bf2..95aa407 100644
--- a/ios/web_view/shell/shell_trusted_vault_provider.m
+++ b/ios/web_view/shell/shell_trusted_vault_provider_fake.m
@@ -10,6 +10,10 @@
 
 @implementation ShellTrustedVaultProvider
 
+- (instancetype)initWithAuthService:(ShellAuthService*)authService {
+  return [super init];
+}
+
 #pragma mark - CWVTrustedVaultProvider
 
 - (void)addTrustedVaultObserver:(CWVTrustedVaultObserver*)observer {
diff --git a/ios/web_view/shell/shell_view_controller.m b/ios/web_view/shell/shell_view_controller.m
index d30fad75..8cc781a 100644
--- a/ios/web_view/shell/shell_view_controller.m
+++ b/ios/web_view/shell/shell_view_controller.m
@@ -270,7 +270,8 @@
   _authService = [[ShellAuthService alloc] init];
   CWVSyncController.dataSource = _authService;
 
-  _trustedVaultProvider = [[ShellTrustedVaultProvider alloc] init];
+  _trustedVaultProvider =
+      [[ShellTrustedVaultProvider alloc] initWithAuthService:_authService];
   CWVSyncController.trustedVaultProvider = _trustedVaultProvider;
 
   CWVWebViewConfiguration* configuration =
@@ -1166,27 +1167,6 @@
   [self presentViewController:alertController animated:YES completion:nil];
 }
 
-- (void)webView:(CWVWebView*)webView
-    handleLegacyTLSWarningWithHandler:(CWVLegacyTLSWarningHandler*)handler {
-  NSLog(@"%@", NSStringFromSelector(_cmd));
-  [handler displayWarningPageWithHTML:handler.error.localizedDescription];
-
-  UIAlertController* alertController =
-      [self actionSheetWithTitle:@"Legacy TLS encountered"
-                         message:@"Would you like to continue anyways?"];
-  [alertController
-      addAction:[UIAlertAction actionWithTitle:@"Yes"
-                                         style:UIAlertActionStyleDefault
-                                       handler:^(UIAlertAction* action) {
-                                         [handler overrideWarningAndReloadPage];
-                                       }]];
-  [alertController
-      addAction:[UIAlertAction actionWithTitle:@"Cancel"
-                                         style:UIAlertActionStyleCancel
-                                       handler:nil]];
-  [self presentViewController:alertController animated:YES completion:nil];
-}
-
 - (void)webViewWebContentProcessDidTerminate:(CWVWebView*)webView {
   NSLog(@"%@", NSStringFromSelector(_cmd));
 }
diff --git a/media/audio/wav_audio_handler.cc b/media/audio/wav_audio_handler.cc
index ee2395e..d2fa51b1 100644
--- a/media/audio/wav_audio_handler.cc
+++ b/media/audio/wav_audio_handler.cc
@@ -127,7 +127,7 @@
   DCHECK(params_out);
 
   // The header should look like: |R|I|F|F|1|2|3|4|W|A|V|E|
-  base::BigEndianReader reader(wav_data.data(), wav_data.size());
+  auto reader = base::BigEndianReader::FromStringPiece(wav_data);
 
   // Read the chunk ID and compare to "RIFF".
   base::StringPiece chunk_id;
diff --git a/media/base/mac/videotoolbox_helpers.cc b/media/base/mac/videotoolbox_helpers.cc
index 25c7b86..88b0f22 100644
--- a/media/base/mac/videotoolbox_helpers.cc
+++ b/media/base/mac/videotoolbox_helpers.cc
@@ -130,7 +130,7 @@
   while (bytes_left > 0) {
     DCHECK_GT(bytes_left, sizeof(NalSizeType));
     NalSizeType nal_size;
-    base::ReadBigEndian(avcc_buffer, &nal_size);
+    base::ReadBigEndian(reinterpret_cast<uint8_t*>(avcc_buffer), &nal_size);
     bytes_left -= sizeof(NalSizeType);
     avcc_buffer += sizeof(NalSizeType);
 
diff --git a/media/cast/net/pacing/paced_sender.cc b/media/cast/net/pacing/paced_sender.cc
index 09a815c..e09c1a1 100644
--- a/media/cast/net/pacing/paced_sender.cc
+++ b/media/cast/net/pacing/paced_sender.cc
@@ -436,8 +436,7 @@
   // TODO(miu): This parsing logic belongs in RtpParser.
   event.timestamp = clock_->NowTicks();
   event.type = type;
-  base::BigEndianReader reader(reinterpret_cast<const char*>(&packet[0]),
-                               packet.size());
+  base::BigEndianReader reader(packet);
   bool success = reader.Skip(4);
   uint32_t truncated_rtp_timestamp;
   success &= reader.ReadU32(&truncated_rtp_timestamp);
diff --git a/media/cast/net/pacing/paced_sender_unittest.cc b/media/cast/net/pacing/paced_sender_unittest.cc
index f30ab68..fbfa0e7 100644
--- a/media/cast/net/pacing/paced_sender_unittest.cc
+++ b/media/cast/net/pacing/paced_sender_unittest.cc
@@ -54,8 +54,7 @@
 
     // Parse for the packet ID and confirm it is the next one we expect.
     EXPECT_LE(kSize1, packet->data.size());
-    base::BigEndianReader reader(reinterpret_cast<char*>(&packet->data[0]),
-                                 packet->data.size());
+    base::BigEndianReader reader(packet->data);
     bool success = reader.Skip(14);
     uint16_t packet_id = 0xffff;
     success &= reader.ReadU16(&packet_id);
diff --git a/media/cast/net/rtcp/receiver_rtcp_session.cc b/media/cast/net/rtcp/receiver_rtcp_session.cc
index 547a1e7..0235651 100644
--- a/media/cast/net/rtcp/receiver_rtcp_session.cc
+++ b/media/cast/net/rtcp/receiver_rtcp_session.cc
@@ -43,7 +43,7 @@
   }
 
   // Parse this packet.
-  base::BigEndianReader reader(reinterpret_cast<const char*>(data), length);
+  base::BigEndianReader reader(data, length);
   if (parser_.Parse(&reader)) {
     if (parser_.has_sender_report()) {
       OnReceivedNtp(parser_.sender_report().ntp_seconds,
diff --git a/media/cast/net/rtcp/rtcp_utility.cc b/media/cast/net/rtcp/rtcp_utility.cc
index 98ead3d..ae2365f4 100644
--- a/media/cast/net/rtcp/rtcp_utility.cc
+++ b/media/cast/net/rtcp/rtcp_utility.cc
@@ -59,10 +59,10 @@
     if (!ParseCommonHeader(reader, &header))
       return false;
 
-    base::StringPiece tmp;
-    if (!reader->ReadPiece(&tmp, header.length_in_octets - 4))
+    base::span<const uint8_t> tmp;
+    if (!reader->ReadSpan(&tmp, header.length_in_octets - 4))
       return false;
-    base::BigEndianReader chunk(tmp.data(), tmp.size());
+    base::BigEndianReader chunk(tmp);
 
     switch (header.PT) {
       case kPacketTypeSenderReport:
@@ -557,8 +557,7 @@
   if (length < kMinLengthOfRtcp)
     return 0;
   uint32_t ssrc_of_sender;
-  base::BigEndianReader big_endian_reader(
-      reinterpret_cast<const char*>(rtcp_buffer), length);
+  base::BigEndianReader big_endian_reader(rtcp_buffer, length);
   big_endian_reader.Skip(4);  // Skip header.
   big_endian_reader.ReadU32(&ssrc_of_sender);
   return ssrc_of_sender;
diff --git a/media/cast/net/rtcp/rtcp_utility_unittest.cc b/media/cast/net/rtcp/rtcp_utility_unittest.cc
index ef8cf00..ca1d3edc 100644
--- a/media/cast/net/rtcp/rtcp_utility_unittest.cc
+++ b/media/cast/net/rtcp/rtcp_utility_unittest.cc
@@ -139,7 +139,7 @@
 };
 
 TEST_F(RtcpParserTest, BrokenPacketIsIgnored) {
-  const char bad_packet[] = {0, 0, 0, 0};
+  const uint8_t bad_packet[] = {0, 0, 0, 0};
   RtcpParser parser(kLocalSsrc, kRemoteSsrc);
   base::BigEndianReader reader(bad_packet, sizeof(bad_packet));
   EXPECT_FALSE(parser.Parse(&reader));
diff --git a/media/cast/net/rtcp/sender_rtcp_session.cc b/media/cast/net/rtcp/sender_rtcp_session.cc
index 3d974af..d22f7cb 100644
--- a/media/cast/net/rtcp/sender_rtcp_session.cc
+++ b/media/cast/net/rtcp/sender_rtcp_session.cc
@@ -107,7 +107,7 @@
   }
 
   // Parse this packet.
-  base::BigEndianReader reader(reinterpret_cast<const char*>(data), length);
+  base::BigEndianReader reader(data, length);
   if (parser_.Parse(&reader)) {
     if (parser_.has_picture_loss_indicator())
       rtcp_observer_->OnReceivedPli();
diff --git a/media/cast/net/rtcp/test_rtcp_packet_builder.cc b/media/cast/net/rtcp/test_rtcp_packet_builder.cc
index 217030b..57a02034 100644
--- a/media/cast/net/rtcp/test_rtcp_packet_builder.cc
+++ b/media/cast/net/rtcp/test_rtcp_packet_builder.cc
@@ -242,8 +242,7 @@
 }
 
 base::BigEndianReader* TestRtcpPacketBuilder::Reader() {
-  big_endian_reader_ = base::BigEndianReader(
-      reinterpret_cast<const char *>(Data()), Length());
+  big_endian_reader_ = base::BigEndianReader(Data(), Length());
   return &big_endian_reader_;
 }
 
diff --git a/media/cast/net/rtp/rtp_parser.cc b/media/cast/net/rtp/rtp_parser.cc
index f9ec437..76b9d5d 100644
--- a/media/cast/net/rtp/rtp_parser.cc
+++ b/media/cast/net/rtp/rtp_parser.cc
@@ -16,8 +16,7 @@
 bool RtpParser::ParseSsrc(const uint8_t* packet,
                           size_t length,
                           uint32_t* ssrc) {
-  base::BigEndianReader big_endian_reader(
-      reinterpret_cast<const char*>(packet), length);
+  base::BigEndianReader big_endian_reader(packet, length);
   return big_endian_reader.Skip(8) && big_endian_reader.ReadU32(ssrc);
 }
 
@@ -42,7 +41,7 @@
   if (length < (kRtpHeaderLength + kCastHeaderLength))
     return false;
 
-  base::BigEndianReader reader(reinterpret_cast<const char*>(packet), length);
+  base::BigEndianReader reader(packet, length);
 
   // Parse the RTP header.  See
   // http://en.wikipedia.org/wiki/Real-time_Transport_Protocol for an
@@ -104,10 +103,10 @@
     uint16_t type_and_size;
     if (!reader.ReadU16(&type_and_size))
       return false;
-    base::StringPiece tmp;
-    if (!reader.ReadPiece(&tmp, type_and_size & 0x3ff))
+    base::span<const uint8_t> tmp;
+    if (!reader.ReadSpan(&tmp, type_and_size & 0x3ff))
       return false;
-    base::BigEndianReader chunk(tmp.data(), tmp.size());
+    base::BigEndianReader chunk(tmp);
     switch (type_and_size >> 10) {
       case kCastRtpExtensionAdaptiveLatency:
         if (!chunk.ReadU16(&header->new_playout_delay_ms))
diff --git a/media/formats/mp4/box_reader.cc b/media/formats/mp4/box_reader.cc
index 36a3039..ac93dc5 100644
--- a/media/formats/mp4/box_reader.cc
+++ b/media/formats/mp4/box_reader.cc
@@ -29,7 +29,7 @@
   RCHECK(HasBytes(sizeof(T)));
 
   // MPEG-4 uses big endian byte order
-  base::ReadBigEndian(reinterpret_cast<const char*>(buf_ + pos_), v);
+  base::ReadBigEndian(buf_ + pos_, v);
   pos_ += sizeof(T);
   return true;
 }
diff --git a/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.cc b/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.cc
index 6346e7c..a16ce3f 100644
--- a/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.cc
@@ -1125,7 +1125,8 @@
   DCHECK(output_ptr);
   DCHECK_LE((input_size + sizeof(kDefaultDhtSeg)), output_size);
 
-  base::BigEndianReader reader(static_cast<const char*>(input_ptr), input_size);
+  base::BigEndianReader reader(static_cast<const uint8_t*>(input_ptr),
+                               input_size);
   bool has_marker_dht = false;
   bool has_marker_sos = false;
   uint8_t marker1, marker2;
@@ -1141,7 +1142,7 @@
   size_t current_offset = 2;
 
   while (!has_marker_sos && !has_marker_dht) {
-    const char* start_addr = reader.ptr();
+    const uint8_t* start_addr = reader.ptr();
     READ_U8_OR_RETURN_FALSE(reader, &marker1);
     if (marker1 != JPEG_MARKER_PREFIX) {
       DVLOGF(1) << "marker1 != 0xFF";
diff --git a/media/parsers/jpeg_parser.cc b/media/parsers/jpeg_parser.cc
index fa528ba9..77189d6 100644
--- a/media/parsers/jpeg_parser.cc
+++ b/media/parsers/jpeg_parser.cc
@@ -131,7 +131,7 @@
 }
 
 // |frame_header| is already initialized to 0 in ParseJpegPicture.
-static bool ParseSOF(const char* buffer,
+static bool ParseSOF(const uint8_t* buffer,
                      size_t length,
                      JpegFrameHeader* frame_header) {
   // Spec B.2.2 Frame header syntax
@@ -201,7 +201,7 @@
 }
 
 // |q_table| is already initialized to 0 in ParseJpegPicture.
-static bool ParseDQT(const char* buffer,
+static bool ParseDQT(const uint8_t* buffer,
                      size_t length,
                      JpegQuantizationTable* q_table) {
   // Spec B.2.4.1 Quantization table-specification syntax
@@ -237,7 +237,7 @@
 }
 
 // |dc_table| and |ac_table| are already initialized to 0 in ParseJpegPicture.
-static bool ParseDHT(const char* buffer,
+static bool ParseDHT(const uint8_t* buffer,
                      size_t length,
                      JpegHuffmanTable* dc_table,
                      JpegHuffmanTable* ac_table) {
@@ -284,7 +284,7 @@
   return true;
 }
 
-static bool ParseDRI(const char* buffer,
+static bool ParseDRI(const uint8_t* buffer,
                      size_t length,
                      uint16_t* restart_interval) {
   // Spec B.2.4.4 Restart interval definition syntax
@@ -295,7 +295,7 @@
 }
 
 // |scan| is already initialized to 0 in ParseJpegPicture.
-static bool ParseSOS(const char* buffer,
+static bool ParseSOS(const uint8_t* buffer,
                      size_t length,
                      const JpegFrameHeader& frame_header,
                      JpegScanHeader* scan) {
@@ -358,7 +358,7 @@
 // and |eoi_end_ptr| will point to the end of image (right after the end of the
 // EOI marker) after search succeeds. Returns true on EOI marker found, or false
 // otherwise.
-static bool SearchEOI(const char* buffer,
+static bool SearchEOI(const uint8_t* buffer,
                       size_t length,
                       const char** eoi_begin_ptr,
                       const char** eoi_end_ptr) {
@@ -373,7 +373,7 @@
         memchr(reader.ptr(), JPEG_MARKER_PREFIX, reader.remaining()));
     if (!marker1_ptr)
       return false;
-    reader.Skip(marker1_ptr - reader.ptr() + 1);
+    reader.Skip(marker1_ptr - reinterpret_cast<const char*>(reader.ptr()) + 1);
 
     do {
       READ_U8_OR_RETURN_FALSE(&marker2);
@@ -395,7 +395,7 @@
         break;
       case JPEG_EOI:
         *eoi_begin_ptr = marker1_ptr;
-        *eoi_end_ptr = reader.ptr();
+        *eoi_end_ptr = reinterpret_cast<const char*>(reader.ptr());
         return true;
       default:
         // Skip for other markers.
@@ -422,7 +422,7 @@
 }
 
 // |result| is already initialized to 0 in ParseJpegPicture.
-static bool ParseSOI(const char* buffer,
+static bool ParseSOI(const uint8_t* buffer,
                      size_t length,
                      JpegParseResult* result) {
   // Spec B.2.1 High-level syntax
@@ -522,7 +522,7 @@
   }
 
   // Scan data follows scan header immediately.
-  result->data = reader.ptr();
+  result->data = reinterpret_cast<const char*>(reader.ptr());
   result->data_size = reader.remaining();
   return true;
 }
@@ -532,7 +532,7 @@
                       JpegParseResult* result) {
   DCHECK(buffer);
   DCHECK(result);
-  BigEndianReader reader(reinterpret_cast<const char*>(buffer), length);
+  BigEndianReader reader(buffer, length);
   memset(result, 0, sizeof(JpegParseResult));
 
   uint8_t marker1, marker2;
@@ -548,7 +548,8 @@
 
   // Update the sizes: |result->data_size| should not include the EOI marker or
   // beyond.
-  BigEndianReader eoi_reader(result->data, result->data_size);
+  BigEndianReader eoi_reader(reinterpret_cast<const uint8_t*>(result->data),
+                             result->data_size);
   const char* eoi_begin_ptr = nullptr;
   const char* eoi_end_ptr = nullptr;
   if (!SearchEOI(eoi_reader.ptr(), eoi_reader.remaining(), &eoi_begin_ptr,
diff --git a/media/remoting/proto_utils.cc b/media/remoting/proto_utils.cc
index a482132..0d746eb 100644
--- a/media/remoting/proto_utils.cc
+++ b/media/remoting/proto_utils.cc
@@ -100,7 +100,7 @@
 
 scoped_refptr<DecoderBuffer> ByteArrayToDecoderBuffer(const uint8_t* data,
                                                       uint32_t size) {
-  base::BigEndianReader reader(reinterpret_cast<const char*>(data), size);
+  base::BigEndianReader reader(data, size);
   uint8_t payload_version = 0;
   uint16_t proto_size = 0;
   openscreen::cast::DecoderBuffer segment;
diff --git a/net/dns/dns_query.cc b/net/dns/dns_query.cc
index 746a962a..75faae3 100644
--- a/net/dns/dns_query.cc
+++ b/net/dns/dns_query.cc
@@ -175,7 +175,8 @@
   // buffer. If we have constructed the query from data or the query is already
   // parsed after constructed from a raw buffer, |header_| is not null.
   DCHECK(header_ == nullptr);
-  base::BigEndianReader reader(io_buffer_->data(), valid_bytes);
+  base::BigEndianReader reader(
+      reinterpret_cast<const uint8_t*>(io_buffer_->data()), valid_bytes);
   dns_protocol::Header header;
   if (!ReadHeader(&reader, &header)) {
     return false;
@@ -215,8 +216,9 @@
 
 uint16_t DnsQuery::qtype() const {
   uint16_t type;
-  base::ReadBigEndian<uint16_t>(io_buffer_->data() + kHeaderSize + qname_size_,
-                                &type);
+  base::ReadBigEndian(reinterpret_cast<const uint8_t*>(
+                          io_buffer_->data() + kHeaderSize + qname_size_),
+                      &type);
   return type;
 }
 
diff --git a/net/dns/dns_response.cc b/net/dns/dns_response.cc
index 3f6a47609..762521d06 100644
--- a/net/dns/dns_response.cc
+++ b/net/dns/dns_response.cc
@@ -175,7 +175,7 @@
           return 0;
         }
         uint16_t offset;
-        base::ReadBigEndian<uint16_t>(p, &offset);
+        base::ReadBigEndian(reinterpret_cast<const uint8_t*>(p), &offset);
         offset &= dns_protocol::kOffsetMask;
         p = packet_ + offset;
         if (p >= end) {
@@ -233,15 +233,16 @@
   size_t consumed = ReadName(cur_, &out->name);
   if (!consumed)
     return false;
-  base::BigEndianReader reader(cur_ + consumed,
-                               packet_ + length_ - (cur_ + consumed));
+  base::BigEndianReader reader(
+      reinterpret_cast<const uint8_t*>(cur_ + consumed),
+      packet_ + length_ - (cur_ + consumed));
   uint16_t rdlen;
   if (reader.ReadU16(&out->type) &&
       reader.ReadU16(&out->klass) &&
       reader.ReadU32(&out->ttl) &&
       reader.ReadU16(&rdlen) &&
       reader.ReadPiece(&out->rdata, rdlen)) {
-    cur_ = reader.ptr();
+    cur_ = reinterpret_cast<const char*>(reader.ptr());
     ++num_records_parsed_;
     return true;
   }
@@ -258,7 +259,8 @@
   if (next > packet_ + length_)
     return false;
 
-  base::ReadBigEndian<uint16_t>(cur_ + consumed, &out_qtype);
+  base::ReadBigEndian(reinterpret_cast<const uint8_t*>(cur_ + consumed),
+                      &out_qtype);
 
   cur_ = next;
 
diff --git a/net/dns/dns_transaction.cc b/net/dns/dns_transaction.cc
index 04f70ec..134b631a 100644
--- a/net/dns/dns_transaction.cc
+++ b/net/dns/dns_transaction.cc
@@ -802,7 +802,9 @@
       return OK;
     }
 
-    base::ReadBigEndian<uint16_t>(length_buffer_->data(), &response_length_);
+    base::ReadBigEndian(
+        reinterpret_cast<const uint8_t*>(length_buffer_->data()),
+        &response_length_);
     // Check if advertised response is too short. (Optimization only.)
     if (response_length_ < query_->io_buffer()->size())
       return ERR_DNS_MALFORMED_RESPONSE;
diff --git a/net/dns/dns_util.cc b/net/dns/dns_util.cc
index f1009b2..552ef39 100644
--- a/net/dns/dns_util.cc
+++ b/net/dns/dns_util.cc
@@ -149,7 +149,7 @@
 
 absl::optional<std::string> DnsDomainToString(base::StringPiece dns_name,
                                               bool require_complete) {
-  base::BigEndianReader reader(dns_name.data(), dns_name.length());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   return DnsDomainToString(reader, require_complete);
 }
 
diff --git a/net/dns/dns_util_unittest.cc b/net/dns/dns_util_unittest.cc
index 5303a11..eb5b30d 100644
--- a/net/dns/dns_util_unittest.cc
+++ b/net/dns/dns_util_unittest.cc
@@ -92,22 +92,22 @@
 TEST_F(DNSUtilTest, DnsDomainToStringShouldHandleSimpleNames) {
   std::string dns_name = "\003foo";
   EXPECT_THAT(DnsDomainToString(dns_name), testing::Optional(Eq("foo")));
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_THAT(DnsDomainToString(reader), testing::Optional(Eq("foo")));
 
   dns_name += "\003bar";
   EXPECT_THAT(DnsDomainToString(dns_name), testing::Optional(Eq("foo.bar")));
-  base::BigEndianReader reader1(dns_name.c_str(), dns_name.size());
+  auto reader1 = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_THAT(DnsDomainToString(reader1), testing::Optional(Eq("foo.bar")));
 
   dns_name += "\002uk";
   EXPECT_THAT(DnsDomainToString(dns_name), testing::Optional(Eq("foo.bar.uk")));
-  base::BigEndianReader reader2(dns_name.c_str(), dns_name.size());
+  auto reader2 = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_THAT(DnsDomainToString(reader2), testing::Optional(Eq("foo.bar.uk")));
 
   dns_name += '\0';
   EXPECT_THAT(DnsDomainToString(dns_name), testing::Optional(Eq("foo.bar.uk")));
-  base::BigEndianReader reader3(dns_name.c_str(), dns_name.size());
+  auto reader3 = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_THAT(DnsDomainToString(reader3), testing::Optional(Eq("foo.bar.uk")));
 }
 
@@ -115,13 +115,13 @@
   std::string dns_name;
 
   EXPECT_THAT(DnsDomainToString(dns_name), testing::Optional(Eq("")));
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_THAT(DnsDomainToString(reader), testing::Optional(Eq("")));
 
   dns_name += '\0';
 
   EXPECT_THAT(DnsDomainToString(dns_name), testing::Optional(Eq("")));
-  base::BigEndianReader reader1(dns_name.c_str(), dns_name.size());
+  auto reader1 = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_THAT(DnsDomainToString(reader1), testing::Optional(Eq("")));
 }
 
@@ -130,13 +130,13 @@
 
   EXPECT_THAT(DnsDomainToString(dns_name, false /* require_complete */),
               testing::Optional(Eq("")));
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_THAT(DnsDomainToString(reader, false /* require_complete */),
               testing::Optional(Eq("")));
 
   EXPECT_EQ(DnsDomainToString(dns_name, true /* require_complete */),
             absl::nullopt);
-  base::BigEndianReader reader1(dns_name.c_str(), dns_name.size());
+  auto reader1 = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_EQ(DnsDomainToString(reader1, true /* require_complete */),
             absl::nullopt);
 }
@@ -149,13 +149,13 @@
 
   EXPECT_THAT(DnsDomainToString(dns_name, false /* require_complete */),
               testing::Optional(Eq("foo.test")));
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_THAT(DnsDomainToString(reader, false /* require_complete */),
               testing::Optional(Eq("foo.test")));
 
   EXPECT_THAT(DnsDomainToString(dns_name, true /* require_complete */),
               testing::Optional(Eq("foo.test")));
-  base::BigEndianReader reader1(dns_name.c_str(), dns_name.size());
+  auto reader1 = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_THAT(DnsDomainToString(reader1, true /* require_complete */),
               testing::Optional(Eq("foo.test")));
 }
@@ -167,13 +167,13 @@
 
   EXPECT_THAT(DnsDomainToString(dns_name, false /* require_complete */),
               testing::Optional(Eq("boo.test")));
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_THAT(DnsDomainToString(reader, false /* require_complete */),
               testing::Optional(Eq("boo.test")));
 
   EXPECT_EQ(DnsDomainToString(dns_name, true /* require_complete */),
             absl::nullopt);
-  base::BigEndianReader reader2(dns_name.c_str(), dns_name.size());
+  auto reader2 = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_EQ(DnsDomainToString(reader2, true /* require_complete */),
             absl::nullopt);
 }
@@ -183,13 +183,13 @@
 
   EXPECT_THAT(DnsDomainToString(dns_name, false /* require_complete */),
               testing::Optional(Eq("")));
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_THAT(DnsDomainToString(reader, false /* require_complete */),
               testing::Optional(Eq("")));
 
   EXPECT_EQ(DnsDomainToString(dns_name, true /* require_complete */),
             absl::nullopt);
-  base::BigEndianReader reader1(dns_name.c_str(), dns_name.size());
+  auto reader1 = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_EQ(DnsDomainToString(reader1, true /* require_complete */),
             absl::nullopt);
 
@@ -197,7 +197,7 @@
 
   EXPECT_THAT(DnsDomainToString(dns_name, true /* require_complete */),
               testing::Optional(Eq("")));
-  base::BigEndianReader reader2(dns_name.c_str(), dns_name.size());
+  auto reader2 = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_THAT(DnsDomainToString(reader2, true /* require_complete */),
               testing::Optional(Eq("")));
 }
@@ -206,14 +206,14 @@
   std::string dns_name = CreateNamePointer(152);
 
   EXPECT_EQ(DnsDomainToString(dns_name), absl::nullopt);
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_EQ(DnsDomainToString(reader), absl::nullopt);
 
   dns_name = "\005hello";
   dns_name += CreateNamePointer(152);
 
   EXPECT_EQ(DnsDomainToString(dns_name), absl::nullopt);
-  base::BigEndianReader reader1(dns_name.c_str(), dns_name.size());
+  auto reader1 = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_EQ(DnsDomainToString(reader1), absl::nullopt);
 }
 
@@ -225,7 +225,7 @@
 
   EXPECT_THAT(DnsDomainToString(dns_name),
               testing::Optional(Eq("cool.name.test")));
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_THAT(DnsDomainToString(reader),
               testing::Optional(Eq("cool.name.test")));
 
@@ -234,7 +234,7 @@
   dns_name += "goodbye";
 
   EXPECT_THAT(DnsDomainToString(dns_name), testing::Optional(Eq("hi")));
-  base::BigEndianReader reader1(dns_name.c_str(), dns_name.size());
+  auto reader1 = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_THAT(DnsDomainToString(reader1), testing::Optional(Eq("hi")));
 }
 
@@ -243,13 +243,13 @@
   std::string dns_name = "\07cheese";
 
   EXPECT_EQ(DnsDomainToString(dns_name), absl::nullopt);
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_EQ(DnsDomainToString(reader), absl::nullopt);
 
   dns_name = "\006cheesy\05test";
 
   EXPECT_EQ(DnsDomainToString(dns_name), absl::nullopt);
-  base::BigEndianReader reader1(dns_name.c_str(), dns_name.size());
+  auto reader1 = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_EQ(DnsDomainToString(reader1), absl::nullopt);
 }
 
@@ -260,7 +260,7 @@
   }
 
   EXPECT_NE(DnsDomainToString(dns_name), absl::nullopt);
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_NE(DnsDomainToString(reader), absl::nullopt);
 }
 
@@ -272,7 +272,7 @@
   }
 
   EXPECT_NE(DnsDomainToString(dns_name), absl::nullopt);
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_NE(DnsDomainToString(reader), absl::nullopt);
 }
 
@@ -283,7 +283,7 @@
   }
 
   EXPECT_EQ(DnsDomainToString(dns_name), absl::nullopt);
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_EQ(DnsDomainToString(reader), absl::nullopt);
 }
 
@@ -295,7 +295,7 @@
   }
 
   EXPECT_EQ(DnsDomainToString(dns_name), absl::nullopt);
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_EQ(DnsDomainToString(reader), absl::nullopt);
 }
 
@@ -314,7 +314,7 @@
   }
 
   EXPECT_EQ(DnsDomainToString(dns_name), absl::nullopt);
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_EQ(DnsDomainToString(reader), absl::nullopt);
 }
 #endif  // if CHAR_MIN < 0
@@ -333,7 +333,7 @@
   ASSERT_EQ(dns_name.size(), static_cast<size_t>(dns_protocol::kMaxNameLength));
 
   EXPECT_NE(DnsDomainToString(dns_name), absl::nullopt);
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_NE(DnsDomainToString(reader), absl::nullopt);
 }
 
@@ -352,7 +352,7 @@
             static_cast<size_t>(dns_protocol::kMaxNameLength + 1));
 
   EXPECT_EQ(DnsDomainToString(dns_name), absl::nullopt);
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_EQ(DnsDomainToString(reader), absl::nullopt);
 }
 
@@ -372,7 +372,7 @@
             static_cast<size_t>(dns_protocol::kMaxNameLength + 1));
 
   EXPECT_NE(DnsDomainToString(dns_name), absl::nullopt);
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_NE(DnsDomainToString(reader), absl::nullopt);
 }
 
@@ -392,7 +392,7 @@
             static_cast<size_t>(dns_protocol::kMaxNameLength + 2));
 
   EXPECT_EQ(DnsDomainToString(dns_name), absl::nullopt);
-  base::BigEndianReader reader(dns_name.c_str(), dns_name.size());
+  auto reader = base::BigEndianReader::FromStringPiece(dns_name);
   EXPECT_EQ(DnsDomainToString(reader), absl::nullopt);
 }
 
diff --git a/net/dns/https_record_rdata.cc b/net/dns/https_record_rdata.cc
index 627b2c4..109259d 100644
--- a/net/dns/https_record_rdata.cc
+++ b/net/dns/https_record_rdata.cc
@@ -55,7 +55,7 @@
                         std::set<uint16_t>* out_parsed) {
   DCHECK(out_parsed);
 
-  base::BigEndianReader reader(param_value.data(), param_value.size());
+  auto reader = base::BigEndianReader::FromStringPiece(param_value);
 
   std::set<uint16_t> mandatory_keys;
   // Do/while to require at least one key.
@@ -82,7 +82,7 @@
                   std::vector<std::string>* out_parsed) {
   DCHECK(out_parsed);
 
-  base::BigEndianReader reader(param_value.data(), param_value.size());
+  auto reader = base::BigEndianReader::FromStringPiece(param_value);
 
   std::vector<std::string> alpn_ids;
   // Do/while to require at least one ID.
@@ -106,7 +106,7 @@
                       std::vector<IPAddress>* out_addresses) {
   DCHECK(out_addresses);
 
-  base::BigEndianReader reader(param_value.data(), param_value.size());
+  auto reader = base::BigEndianReader::FromStringPiece(param_value);
 
   std::vector<IPAddress> addresses;
   uint8_t addr_bytes[ADDRESS_SIZE];
@@ -129,7 +129,7 @@
   if (!HasValidSize(data, kType))
     return std::make_unique<MalformedHttpsRecordRdata>();
 
-  base::BigEndianReader reader(data.data(), data.size());
+  auto reader = base::BigEndianReader::FromStringPiece(data);
   uint16_t priority;
   CHECK(reader.ReadU16(&priority));
 
@@ -207,7 +207,7 @@
 // static
 std::unique_ptr<AliasFormHttpsRecordRdata> AliasFormHttpsRecordRdata::Parse(
     base::StringPiece data) {
-  base::BigEndianReader reader(data.data(), data.size());
+  auto reader = base::BigEndianReader::FromStringPiece(data);
 
   uint16_t priority;
   if (!reader.ReadU16(&priority))
@@ -315,7 +315,7 @@
 // static
 std::unique_ptr<ServiceFormHttpsRecordRdata> ServiceFormHttpsRecordRdata::Parse(
     base::StringPiece data) {
-  base::BigEndianReader reader(data.data(), data.size());
+  auto reader = base::BigEndianReader::FromStringPiece(data);
 
   uint16_t priority;
   if (!reader.ReadU16(&priority))
@@ -388,7 +388,8 @@
     if (param_value.size() != 2)
       return nullptr;
     uint16_t port_val;
-    base::ReadBigEndian(param_value.data(), &port_val);
+    base::ReadBigEndian(reinterpret_cast<const uint8_t*>(param_value.data()),
+                        &port_val);
     port = port_val;
     if (reader.remaining() > 0 &&
         !ReadNextServiceParam(param_key, reader, &param_key, &param_value)) {
diff --git a/net/dns/record_rdata.cc b/net/dns/record_rdata.cc
index c257148..26b46cb 100644
--- a/net/dns/record_rdata.cc
+++ b/net/dns/record_rdata.cc
@@ -68,7 +68,7 @@
 
   std::unique_ptr<SrvRecordRdata> rdata(new SrvRecordRdata);
 
-  base::BigEndianReader reader(data.data(), data.size());
+  auto reader = base::BigEndianReader::FromStringPiece(data);
   // 2 bytes for priority, 2 bytes for weight, 2 bytes for port.
   reader.ReadU16(&rdata->priority_);
   reader.ReadU16(&rdata->weight_);
@@ -318,7 +318,7 @@
   std::unique_ptr<OptRecordRdata> rdata(new OptRecordRdata);
   rdata->buf_.assign(data.begin(), data.end());
 
-  base::BigEndianReader reader(data.data(), data.size());
+  auto reader = base::BigEndianReader::FromStringPiece(data);
   while (reader.remaining() > 0) {
     uint16_t opt_code, opt_data_size;
     base::StringPiece opt_data;
@@ -412,7 +412,7 @@
 // static
 std::unique_ptr<IntegrityRecordRdata> IntegrityRecordRdata::Create(
     const base::StringPiece& data) {
-  base::BigEndianReader reader(data.data(), data.size());
+  auto reader = base::BigEndianReader::FromStringPiece(data);
   // Parse a U16-prefixed |Nonce| followed by a |Digest|.
   base::StringPiece parsed_nonce, parsed_digest;
 
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc
index 5f3deef..a4dd4b90 100644
--- a/net/socket/ssl_client_socket_unittest.cc
+++ b/net/socket/ssl_client_socket_unittest.cc
@@ -88,7 +88,6 @@
 #include "net/test/embedded_test_server/http_response.h"
 #include "net/test/gtest_util.h"
 #include "net/test/key_util.h"
-#include "net/test/spawned_test_server/spawned_test_server.h"
 #include "net/test/test_data_directory.h"
 #include "net/test/test_with_task_environment.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
@@ -758,12 +757,10 @@
   }
 
  protected:
-  // The address of the test server, after calling StartEmbeddedTestServer() or
-  // StartTestServer().
+  // The address of the test server, after calling StartEmbeddedTestServer().
   const AddressList& addr() const { return addr_; }
 
-  // The hostname of the test server, after calling StartEmbeddedTestServer() or
-  // StartTestServer().
+  // The hostname of the test server, after calling StartEmbeddedTestServer().
   const HostPortPair& host_port_pair() const { return host_port_pair_; }
 
   // The EmbeddedTestServer object, after calling StartEmbeddedTestServer().
@@ -771,16 +768,10 @@
     return embedded_test_server_.get();
   }
 
-  // The SpawnedTestServer object, after calling StartTestServer().
-  const SpawnedTestServer* spawned_test_server() const {
-    return spawned_test_server_.get();
-  }
-
   // Starts the embedded test server with the specified parameters. Returns true
   // on success.
   bool StartEmbeddedTestServer(EmbeddedTestServer::ServerCertificate cert,
                                const SSLServerConfig& server_config) {
-    spawned_test_server_ = nullptr;
     embedded_test_server_ =
         std::make_unique<EmbeddedTestServer>(EmbeddedTestServer::TYPE_HTTPS);
     embedded_test_server_->SetSSLConfig(cert, server_config);
@@ -792,7 +783,6 @@
   bool StartEmbeddedTestServer(
       const EmbeddedTestServer::ServerCertificateConfig& cert_config,
       const SSLServerConfig& server_config) {
-    spawned_test_server_ = nullptr;
     embedded_test_server_ =
         std::make_unique<EmbeddedTestServer>(EmbeddedTestServer::TYPE_HTTPS);
     embedded_test_server_->SetSSLConfig(cert_config, server_config);
@@ -823,25 +813,6 @@
         base::BindRepeating(&HandleSSLInfoRequest, base::Unretained(this)));
   }
 
-  // Starts the spawned test server with SSL configuration |ssl_options|.
-  // Returns true on success. Prefer StartEmbeddedTestServer().
-  bool StartTestServer(const SpawnedTestServer::SSLOptions& ssl_options) {
-    embedded_test_server_ = nullptr;
-    spawned_test_server_ = std::make_unique<SpawnedTestServer>(
-        SpawnedTestServer::TYPE_HTTPS, ssl_options, base::FilePath());
-    if (!spawned_test_server_->Start()) {
-      LOG(ERROR) << "Could not start SpawnedTestServer";
-      return false;
-    }
-
-    if (!spawned_test_server_->GetAddressList(&addr_)) {
-      LOG(ERROR) << "Could not get SpawnedTestServer address list";
-      return false;
-    }
-    host_port_pair_ = spawned_test_server_->host_port_pair();
-    return true;
-  }
-
   std::unique_ptr<SSLClientSocket> CreateSSLClientSocket(
       std::unique_ptr<StreamSocket> transport_socket,
       const HostPortPair& host_and_port,
@@ -852,11 +823,11 @@
 
   // Create an SSLClientSocket object and use it to connect to a test server,
   // then wait for connection results. This must be called after a successful
-  // StartEmbeddedTestServer() or StartTestServer() call.
+  // StartEmbeddedTestServer() call.
   //
   // |ssl_config| The SSL configuration to use.
   // |host_port_pair| The hostname and port to use at the SSL layer. (The
-  //     socket connection will still be made to |spawned_test_server_|.)
+  //     socket connection will still be made to |embedded_test_server_|.)
   // |result| will retrieve the ::Connect() result value.
   //
   // Returns true on success, false otherwise. Success means that the SSL
@@ -870,7 +841,7 @@
         addr_, nullptr, nullptr, NetLog::Get(), NetLogSource()));
     int rv = callback_.GetResult(transport->Connect(callback_.callback()));
     if (rv != OK) {
-      LOG(ERROR) << "Could not connect to SpawnedTestServer";
+      LOG(ERROR) << "Could not connect to test server";
       return false;
     }
 
@@ -936,7 +907,6 @@
     return std::make_unique<test_server::BasicHttpResponse>();
   }
 
-  std::unique_ptr<SpawnedTestServer> spawned_test_server_;
   std::unique_ptr<EmbeddedTestServer> embedded_test_server_;
   base::Lock server_ssl_info_lock_;
   absl::optional<SSLInfo> server_ssl_info_ GUARDED_BY(server_ssl_info_lock_);
@@ -2642,7 +2612,7 @@
   EXPECT_THAT(rv, IsError(ERR_CERT_INVALID));
   EXPECT_FALSE(sock_->IsConnected());
 
-  // When given option CERT_CHAIN_WRONG_ROOT, SpawnedTestServer will present
+  // When given option CERT_CHAIN_WRONG_ROOT, EmbeddedTestServer will present
   // certs from redundant-server-chain.pem.
   CertificateList server_certs =
       CreateCertificateListFromFile(GetTestCertsDirectory(),
@@ -3522,9 +3492,8 @@
       client_config, &callback, &raw_transport, &sock));
 
   // Wait for the server Finished to arrive, release it, and allow
-  // SSLClientSocket to process it. This should install a session.
-  // SpawnedTestServer, however, writes data in small chunks, so, even though it
-  // is only sending 51 bytes, it may take a few iterations to complete.
+  // SSLClientSocket to process it. This should install a session. It make take
+  // a few iterations to complete if the server writes in small chunks
   while (ssl_client_session_cache_->size() == 0) {
     raw_transport->WaitForReadResult();
     raw_transport->UnblockReadResult();
@@ -5479,70 +5448,6 @@
   EXPECT_TRUE(ran_callback);
 }
 
-class TLS13DowngradeTest
-    : public SSLClientSocketTest,
-      public ::testing::WithParamInterface<
-          std::tuple<SpawnedTestServer::SSLOptions::TLSMaxVersion,
-                     /* simulate_tls13_downgrade */ bool,
-                     /* known_root */ bool>> {
- public:
-  TLS13DowngradeTest() {}
-  ~TLS13DowngradeTest() {}
-
-  SpawnedTestServer::SSLOptions::TLSMaxVersion tls_max_version() const {
-    return std::get<0>(GetParam());
-  }
-
-  bool simulate_tls13_downgrade() const { return std::get<1>(GetParam()); }
-  bool known_root() const { return std::get<2>(GetParam()); }
-};
-
-INSTANTIATE_TEST_SUITE_P(
-    All,
-    TLS13DowngradeTest,
-    Combine(Values(SpawnedTestServer::SSLOptions::TLS_MAX_VERSION_TLS1_0,
-                   SpawnedTestServer::SSLOptions::TLS_MAX_VERSION_TLS1_1,
-                   SpawnedTestServer::SSLOptions::TLS_MAX_VERSION_TLS1_2),
-            Bool(),
-            Bool()));
-
-TEST_P(TLS13DowngradeTest, DowngradeEnforced) {
-  SpawnedTestServer::SSLOptions ssl_options;
-  ssl_options.simulate_tls13_downgrade = simulate_tls13_downgrade();
-  ssl_options.tls_max_version = tls_max_version();
-  ASSERT_TRUE(StartTestServer(ssl_options));
-  scoped_refptr<X509Certificate> server_cert =
-      spawned_test_server()->GetCertificate();
-
-  SSLContextConfig config;
-  config.version_max = SSL_PROTOCOL_VERSION_TLS1_3;
-  // If the test is using legacy TLS versions, explicitly disable warnings
-  // (e.g., to cover cases like post-interstitial or when legacy TLS is
-  // explicitly allowed via configuration).
-  if (tls_max_version() <
-      SpawnedTestServer::SSLOptions::TLS_MAX_VERSION_TLS1_2) {
-    config.version_min_warn = SSL_PROTOCOL_VERSION_TLS1;
-  }
-  ssl_config_service_->UpdateSSLConfigAndNotify(config);
-
-  CertVerifyResult verify_result;
-  verify_result.is_issued_by_known_root = known_root();
-  verify_result.verified_cert = server_cert;
-  cert_verifier_->ClearRules();
-  cert_verifier_->AddResultForCert(server_cert.get(), verify_result, OK);
-
-  ssl_client_session_cache_->Flush();
-  int rv;
-  ASSERT_TRUE(CreateAndConnectSSLClientSocket(SSLConfig(), &rv));
-  if (simulate_tls13_downgrade()) {
-    EXPECT_THAT(rv, IsError(ERR_TLS13_DOWNGRADE_DETECTED));
-    EXPECT_FALSE(sock_->IsConnected());
-  } else {
-    EXPECT_THAT(rv, IsOk());
-    EXPECT_TRUE(sock_->IsConnected());
-  }
-}
-
 struct SSLHandshakeDetailsParams {
   bool alpn;
   bool early_data;
diff --git a/net/test/spawned_test_server/base_test_server.cc b/net/test/spawned_test_server/base_test_server.cc
index c9383bc..04fcd16 100644
--- a/net/test/spawned_test_server/base_test_server.cc
+++ b/net/test/spawned_test_server/base_test_server.cc
@@ -437,17 +437,6 @@
 
   if (type_ == TYPE_HTTPS) {
     arguments->SetKey("https", base::Value());
-
-    if (ssl_options_.tls_max_version != SSLOptions::TLS_MAX_VERSION_DEFAULT) {
-      arguments->SetIntKey("tls-max-version", ssl_options_.tls_max_version);
-    }
-
-    if (ssl_options_.simulate_tls13_downgrade) {
-      arguments->SetKey("simulate-tls13-downgrade", base::Value());
-    }
-    if (ssl_options_.simulate_tls12_downgrade) {
-      arguments->SetKey("simulate-tls12-downgrade", base::Value());
-    }
   }
 
   return GenerateAdditionalArguments(arguments);
diff --git a/net/test/spawned_test_server/base_test_server.h b/net/test/spawned_test_server/base_test_server.h
index f61db24..f9db4c9 100644
--- a/net/test/spawned_test_server/base_test_server.h
+++ b/net/test/spawned_test_server/base_test_server.h
@@ -82,13 +82,6 @@
       CERT_TEST_NAMES,
     };
 
-    enum TLSMaxVersion {
-      TLS_MAX_VERSION_DEFAULT = 0,
-      TLS_MAX_VERSION_TLS1_0 = 1,
-      TLS_MAX_VERSION_TLS1_1 = 2,
-      TLS_MAX_VERSION_TLS1_2 = 3,
-    };
-
     // Initialize a new SSLOptions using CERT_OK as the certificate.
     SSLOptions();
 
@@ -115,17 +108,6 @@
     // from each certificate will be added to the certificate_authorities
     // field of the CertificateRequest.
     std::vector<base::FilePath> client_authorities;
-
-    // The maximum TLS version to support.
-    TLSMaxVersion tls_max_version = TLS_MAX_VERSION_DEFAULT;
-
-    // If true, sends the TLS 1.3 to TLS 1.2 downgrade signal in the ServerHello
-    // random.
-    bool simulate_tls13_downgrade = false;
-
-    // If true, sends the TLS 1.2 to TLS 1.1 downgrade signal in the ServerHello
-    // random.
-    bool simulate_tls12_downgrade = false;
   };
 
   // Initialize a TestServer.
diff --git a/net/tools/huffman_trie/trie/trie_bit_buffer.cc b/net/tools/huffman_trie/trie/trie_bit_buffer.cc
index 88e38b4..33b2aad 100644
--- a/net/tools/huffman_trie/trie/trie_bit_buffer.cc
+++ b/net/tools/huffman_trie/trie/trie_bit_buffer.cc
@@ -6,6 +6,7 @@
 
 #include <ostream>
 
+#include "base/bits.h"
 #include "base/check.h"
 #include "net/tools/huffman_trie/bit_writer.h"
 
@@ -39,7 +40,7 @@
     int32_t delta = position - *last_position;
     DCHECK(delta > 0) << "delta position is not positive.";
 
-    uint8_t number_of_bits = BitLength(delta);
+    uint8_t number_of_bits = base::bits::Log2Floor(delta) + 1;
     DCHECK(number_of_bits <= 7 + 15) << "positive position delta too large.";
 
     if (number_of_bits <= 7) {
@@ -64,15 +65,6 @@
   *last_position = position;
 }
 
-uint8_t TrieBitBuffer::BitLength(uint32_t input) const {
-  uint8_t number_of_bits = 0;
-  while (input != 0) {
-    number_of_bits++;
-    input >>= 1;
-  }
-  return number_of_bits;
-}
-
 void TrieBitBuffer::WriteChar(uint8_t byte,
                               const HuffmanRepresentationTable& table,
                               HuffmanBuilder* huffman_builder) {
@@ -136,8 +128,8 @@
       uint32_t target = element.position;
       DCHECK(target < current) << "Reference is not backwards";
       uint32_t delta = current - target;
-      uint8_t delta_number_of_bits = BitLength(delta);
-      DCHECK(delta_number_of_bits < 32) << "Delta to large";
+      uint8_t delta_number_of_bits = base::bits::Log2Floor(delta) + 1;
+      DCHECK(delta_number_of_bits < 32) << "Delta too large";
       writer->WriteBits(delta_number_of_bits, 5);
       writer->WriteBits(delta, delta_number_of_bits);
     }
diff --git a/net/tools/huffman_trie/trie/trie_bit_buffer.h b/net/tools/huffman_trie/trie/trie_bit_buffer.h
index ced8f94..51ad4b7 100644
--- a/net/tools/huffman_trie/trie/trie_bit_buffer.h
+++ b/net/tools/huffman_trie/trie/trie_bit_buffer.h
@@ -70,9 +70,6 @@
     uint32_t position;
   };
 
-  // Returns the minimum number of bits needed to represent |input|.
-  uint8_t BitLength(uint32_t input) const;
-
   // Append a new element to |elements_|.
   void AppendBitsElement(uint8_t bits, uint8_t number_of_bits);
   void AppendPositionElement(uint32_t position);
diff --git a/net/tools/testserver/testserver.py b/net/tools/testserver/testserver.py
index d523f33..594c0f3d 100755
--- a/net/tools/testserver/testserver.py
+++ b/net/tools/testserver/testserver.py
@@ -101,8 +101,7 @@
   client verification."""
 
   def __init__(self, server_address, request_hander_class, pem_cert_and_key,
-               ssl_client_auth, ssl_client_cas, simulate_tls13_downgrade,
-               simulate_tls12_downgrade, tls_max_version):
+               ssl_client_auth, ssl_client_cas):
     self.cert_chain = tlslite.api.X509CertChain()
     self.cert_chain.parsePemList(pem_cert_and_key)
     # Force using only python implementation - otherwise behavior is different
@@ -125,12 +124,6 @@
     self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
     # Enable SSLv3 for testing purposes.
     self.ssl_handshake_settings.minVersion = (3, 0)
-    if simulate_tls13_downgrade:
-      self.ssl_handshake_settings.simulateTLS13Downgrade = True
-    if simulate_tls12_downgrade:
-      self.ssl_handshake_settings.simulateTLS12Downgrade = True
-    if tls_max_version != 0:
-      self.ssl_handshake_settings.maxVersion = (3, tls_max_version)
 
     self.session_cache = tlslite.api.SessionCache()
     testserver_base.StoppableHTTPServer.__init__(self,
@@ -394,11 +387,9 @@
                 'specified trusted client CA file not found: ' + ca_cert +
                 ' exiting...')
 
-        server = HTTPSServer(
-            (host, port), TestPageHandler, pem_cert_and_key,
-            self.options.ssl_client_auth, self.options.ssl_client_ca,
-            self.options.simulate_tls13_downgrade,
-            self.options.simulate_tls12_downgrade, self.options.tls_max_version)
+        server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
+                             self.options.ssl_client_auth,
+                             self.options.ssl_client_ca)
         print('HTTPS server started on https://%s:%d...' %
               (host, server.server_port))
       else:
@@ -502,14 +493,6 @@
     self.option_parser.add_option('--ws-basic-auth', action='store_true',
                                   dest='ws_basic_auth',
                                   help='Enable basic-auth for WebSocket')
-    self.option_parser.add_option('--simulate-tls13-downgrade',
-                                  action='store_true')
-    self.option_parser.add_option('--simulate-tls12-downgrade',
-                                  action='store_true')
-    self.option_parser.add_option('--tls-max-version', default='0', type='int',
-                                  help='If non-zero, the maximum TLS version '
-                                  'to support. 1 means TLS 1.0, 2 means '
-                                  'TLS 1.1, and 3 means TLS 1.2.')
     self.option_parser.add_option('--redirect-connect-to-localhost',
                                   dest='redirect_connect_to_localhost',
                                   default=False, action='store_true',
diff --git a/net/websockets/websocket_channel.cc b/net/websockets/websocket_channel.cc
index a035a82..f07d51b 100644
--- a/net/websockets/websocket_channel.cc
+++ b/net/websockets/websocket_channel.cc
@@ -960,7 +960,7 @@
 
   const char* data = payload.data();
   uint16_t unchecked_code = 0;
-  base::ReadBigEndian(data, &unchecked_code);
+  base::ReadBigEndian(reinterpret_cast<const uint8_t*>(data), &unchecked_code);
   static_assert(sizeof(unchecked_code) == kWebSocketCloseCodeLength,
                 "they should both be two bytes");
 
diff --git a/net/websockets/websocket_frame_parser.cc b/net/websockets/websocket_frame_parser.cc
index bbbd0db..fe5f619 100644
--- a/net/websockets/websocket_frame_parser.cc
+++ b/net/websockets/websocket_frame_parser.cc
@@ -129,7 +129,8 @@
     if (data.size() < current + 2)
       return 0;
     uint16_t payload_length_16;
-    base::ReadBigEndian(&data[current], &payload_length_16);
+    base::ReadBigEndian(reinterpret_cast<const uint8_t*>(&data[current]),
+                        &payload_length_16);
     current += 2;
     payload_length = payload_length_16;
     if (payload_length <= kMaxPayloadLengthWithoutExtendedLengthField) {
@@ -139,7 +140,8 @@
   } else if (payload_length == kPayloadLengthWithEightByteExtendedLengthField) {
     if (data.size() < current + 8)
       return 0;
-    base::ReadBigEndian(&data[current], &payload_length);
+    base::ReadBigEndian(reinterpret_cast<const uint8_t*>(&data[current]),
+                        &payload_length);
     current += 8;
     if (payload_length <= UINT16_MAX ||
         payload_length > static_cast<uint64_t>(INT64_MAX)) {
diff --git a/services/device/wake_lock/power_save_blocker/BUILD.gn b/services/device/wake_lock/power_save_blocker/BUILD.gn
index 09b0797..1637134b 100644
--- a/services/device/wake_lock/power_save_blocker/BUILD.gn
+++ b/services/device/wake_lock/power_save_blocker/BUILD.gn
@@ -59,10 +59,10 @@
       ]
     } else {
       sources += [ "power_save_blocker_linux.cc" ]
-      deps += [ "//ui/display" ]
     }
     deps += [
       "//dbus",
+      "//ui/display",
       "//ui/gfx",
     ]
   } else if (is_mac) {
diff --git a/services/device/wake_lock/power_save_blocker/power_save_blocker_lacros.cc b/services/device/wake_lock/power_save_blocker/power_save_blocker_lacros.cc
index 99cf56c..444853e 100644
--- a/services/device/wake_lock/power_save_blocker/power_save_blocker_lacros.cc
+++ b/services/device/wake_lock/power_save_blocker/power_save_blocker_lacros.cc
@@ -10,14 +10,18 @@
 #include "chromeos/crosapi/mojom/power.mojom.h"
 #include "chromeos/lacros/lacros_service.h"
 #include "mojo/public/cpp/bindings/receiver.h"
+#include "ui/display/screen.h"
 
 namespace device {
 
 /******** PowerSaveBlocker::Delegate ********/
 
-// Lacros-chrome PowerSaveBlocker uses ash-chrome ProwerSaveBlocker via crosapi.
+// Lacros-chrome PowerSaveBlocker uses ash-chrome ProwerSaveBlocker via either
+// crosapi (the default) or Wayland (if the idle inhibitor feature is enabled).
 // RAII style is maintained by keeping a crosapi::mojom::PowerWakeLock Mojo
 // connection, whose disconnection triggers resource release in ash-chrome.
+// TODO(b/193670013): Cleanup logic after Wayland idle inhibitor replaces
+// crosapi power service.
 
 class PowerSaveBlocker::Delegate
     : public base::RefCountedThreadSafe<PowerSaveBlocker::Delegate> {
@@ -35,6 +39,11 @@
 
   void ApplyBlock() {
     DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
+
+    auto* const screen = display::Screen::GetScreen();
+    if (screen && screen->SetScreenSaverSuspended(true))
+      return;
+
     auto* lacros_service = chromeos::LacrosService::Get();
     if (lacros_service->IsAvailable<crosapi::mojom::Power>()) {
       lacros_service->GetRemote<crosapi::mojom::Power>()->AddPowerSaveBlocker(
@@ -44,6 +53,11 @@
 
   void RemoveBlock() {
     DCHECK(ui_task_runner_->RunsTasksInCurrentSequence());
+
+    auto* const screen = display::Screen::GetScreen();
+    if (screen && screen->SetScreenSaverSuspended(false))
+      return;
+
     // Disconnect to make ash-chrome release its PowerSaveBlocker.
     receiver_.reset();
   }
diff --git a/services/viz/privileged/mojom/compositing/BUILD.gn b/services/viz/privileged/mojom/compositing/BUILD.gn
index 65d5f75..ee3bcd1 100644
--- a/services/viz/privileged/mojom/compositing/BUILD.gn
+++ b/services/viz/privileged/mojom/compositing/BUILD.gn
@@ -29,7 +29,12 @@
     "//ui/latency/mojom",
   ]
 
-  traits_public_deps = [ "//ui/base:features" ]
+  parser_deps = [ "//components/viz/common" ]
+
+  traits_public_deps = [
+    "//components/viz/common",
+    "//ui/base:features",
+  ]
 
   enabled_features = []
   if (use_ozone) {
diff --git a/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom b/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom
index 24945b1..38deefc 100644
--- a/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom
+++ b/services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom
@@ -30,12 +30,17 @@
 };
 
 // Provided with FrameSinkVideoConsumer::ChangeTarget() to indicate what
-// sub target specifier should be used, if any.
-union SubTarget {
+// target should be selected for capture.
+union VideoCaptureSubTarget {
   SubtreeCaptureId subtree_capture_id;
   mojo_base.mojom.Token region_capture_crop_id;
 };
 
+struct VideoCaptureTarget {
+  FrameSinkId frame_sink_id;
+  VideoCaptureSubTarget? sub_target;
+};
+
 // Interface for a consumer that receives frames and notifications related to
 // capture of the source content. An instance that implements this interface is
 // provided to FrameSinkVideoCapturer.Start().
@@ -127,8 +132,9 @@
   SetAutoThrottlingEnabled(bool enabled);
 
   // Targets a different compositor frame sink. This may be called anytime,
-  // before or after Start(). If |frame_sink_id| is null, capture will suspend
-  // until a new frame sink target is set.
+  // before or after Start(). If |target| is null, capture will suspend
+  // until a new frame sink target is set. If |target| is provided, the
+  // frame sink identifier associated with it must be valid.
   // If the |sub_target| is a valid subtree capture id, the capturer will
   // capture a render pass associated with a layer subtree under the target
   // frame sink, which is identifiable by that
@@ -136,7 +142,7 @@
   // crop id, only the region associated with that crop id will be captured.
   // Otherwise, the capturer captures the root render pass of the target frame
   // sink.
-  ChangeTarget(FrameSinkId? frame_sink_id, SubTarget? sub_target);
+  ChangeTarget(VideoCaptureTarget? target);
 
   // Starts emitting video frames to the given |consumer|.
   Start(pending_remote<FrameSinkVideoConsumer> consumer);
@@ -150,7 +156,7 @@
   // resolve occasional "picture loss" issues consumer-side.
   RequestRefreshFrame();
 
-  // Creates an overlay to be renderered within each captured video frame. The
+  // Creates an overlay to be rendered within each captured video frame. The
   // |stacking_index| is an arbitrary value that determines whether to render
   // this overlay before/after other overlays. Greater values mean "after" and
   // "on top of" those with lesser values. Specifying the same index as an
diff --git a/services/viz/public/cpp/compositing/video_capture_target_mojom_traits.h b/services/viz/public/cpp/compositing/video_capture_target_mojom_traits.h
new file mode 100644
index 0000000..85e466de9
--- /dev/null
+++ b/services/viz/public/cpp/compositing/video_capture_target_mojom_traits.h
@@ -0,0 +1,104 @@
+// Copyright 2021 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_VIDEO_CAPTURE_TARGET_MOJOM_TRAITS_H_
+#define SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_VIDEO_CAPTURE_TARGET_MOJOM_TRAITS_H_
+
+#include <utility>
+
+#include "build/build_config.h"
+#include "components/viz/common/surfaces/video_capture_target.h"
+#include "mojo/public/cpp/base/token_mojom_traits.h"
+#include "services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom-shared.h"
+#include "services/viz/public/cpp/compositing/frame_sink_id_mojom_traits.h"
+#include "services/viz/public/cpp/compositing/subtree_capture_id_mojom_traits.h"
+
+namespace mojo {
+
+template <>
+struct UnionTraits<viz::mojom::VideoCaptureSubTargetDataView,
+                   viz::VideoCaptureSubTarget> {
+  static bool IsNull(const viz::VideoCaptureSubTarget& data) {
+    return absl::holds_alternative<absl::monostate>(data);
+  }
+
+  static void SetToNull(viz::VideoCaptureSubTarget* data) {
+    *data = viz::VideoCaptureSubTarget();
+  }
+
+  static const viz::RegionCaptureCropId& region_capture_crop_id(
+      const viz::VideoCaptureSubTarget& data) {
+    return absl::get<viz::RegionCaptureCropId>(data);
+  }
+
+  static viz::SubtreeCaptureId subtree_capture_id(
+      const viz::VideoCaptureSubTarget& data) {
+    return absl::get<viz::SubtreeCaptureId>(data);
+  }
+
+  using Tag = viz::mojom::VideoCaptureSubTargetDataView::Tag;
+  static Tag GetTag(const viz::VideoCaptureSubTarget& data) {
+    if (absl::holds_alternative<viz::RegionCaptureCropId>(data)) {
+      return Tag::REGION_CAPTURE_CROP_ID;
+    }
+    DCHECK(absl::holds_alternative<viz::SubtreeCaptureId>(data));
+    return Tag::SUBTREE_CAPTURE_ID;
+  }
+
+  static bool Read(viz::mojom::VideoCaptureSubTargetDataView data,
+                   viz::VideoCaptureSubTarget* out) {
+    switch (data.tag()) {
+      case Tag::REGION_CAPTURE_CROP_ID: {
+        viz::RegionCaptureCropId crop_id;
+        if (!data.ReadRegionCaptureCropId(&crop_id) || crop_id.is_zero())
+          return false;
+
+        *out = crop_id;
+        return true;
+      }
+      case Tag::SUBTREE_CAPTURE_ID: {
+        viz::SubtreeCaptureId capture_id;
+        if (!data.ReadSubtreeCaptureId(&capture_id) || !capture_id.is_valid())
+          return false;
+
+        *out = capture_id;
+        return true;
+      }
+    }
+
+    NOTREACHED();
+    return false;
+  }
+};
+
+template <>
+struct StructTraits<viz::mojom::VideoCaptureTargetDataView,
+                    viz::VideoCaptureTarget> {
+  static viz::FrameSinkId frame_sink_id(const viz::VideoCaptureTarget& input) {
+    return input.frame_sink_id;
+  }
+
+  static const viz::VideoCaptureSubTarget& sub_target(
+      const viz::VideoCaptureTarget& input) {
+    return input.sub_target;
+  }
+
+  static bool Read(viz::mojom::VideoCaptureTargetDataView data,
+                   viz::VideoCaptureTarget* out) {
+    viz::VideoCaptureTarget target;
+    if (!data.ReadSubTarget(&target.sub_target) ||
+        !data.ReadFrameSinkId(&target.frame_sink_id))
+      return false;
+
+    if (!target.frame_sink_id.is_valid())
+      return false;
+
+    *out = std::move(target);
+    return true;
+  }
+};
+
+}  // namespace mojo
+
+#endif  // SERVICES_VIZ_PUBLIC_CPP_COMPOSITING_VIDEO_CAPTURE_TARGET_MOJOM_TRAITS_H_
diff --git a/services/viz/public/mojom/BUILD.gn b/services/viz/public/mojom/BUILD.gn
index 2f766bed..7411af8 100644
--- a/services/viz/public/mojom/BUILD.gn
+++ b/services/viz/public/mojom/BUILD.gn
@@ -183,6 +183,24 @@
       traits_headers = [ "//services/viz/public/cpp/compositing/vertical_scroll_direction_mojom_traits.h" ]
       traits_public_deps = [ "//components/viz/common" ]
     },
+    {
+      types = [
+        {
+          mojom = "viz.mojom.VideoCaptureTarget"
+          cpp = "::viz::VideoCaptureTarget"
+        },
+        {
+          mojom = "viz.mojom.VideoCaptureSubTarget"
+          cpp = "::viz::VideoCaptureSubTarget"
+          nullable_is_same_type = true
+        },
+      ]
+      traits_headers = [ "//services/viz/public/cpp/compositing/video_capture_target_mojom_traits.h" ]
+      traits_public_deps = [
+        "//components/viz/common",
+        "//ui/gfx/geometry/mojom",
+      ]
+    },
   ]
   cpp_typemaps = shared_cpp_typemaps
   cpp_typemaps += [
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index e728e2a..9963bf2 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -95548,7 +95548,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.14.6"
             }
           ],
diff --git a/testing/buildbot/chromium.mac.json b/testing/buildbot/chromium.mac.json
index 983bc69..6775e55 100644
--- a/testing/buildbot/chromium.mac.json
+++ b/testing/buildbot/chromium.mac.json
@@ -1714,7 +1714,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -1733,7 +1732,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -1755,7 +1753,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -1775,7 +1772,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -1794,7 +1790,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -1813,7 +1808,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -1832,7 +1826,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -1851,7 +1844,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -1870,7 +1862,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -1890,7 +1881,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -1909,7 +1899,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -1928,7 +1917,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -1947,7 +1935,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -1970,7 +1957,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -1989,7 +1975,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2008,7 +1993,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2027,7 +2011,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2046,7 +2029,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2065,7 +2047,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2084,7 +2065,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2103,7 +2083,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2123,7 +2102,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2142,7 +2120,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2161,7 +2138,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2180,7 +2156,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2199,7 +2174,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2218,7 +2192,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2237,7 +2210,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2256,7 +2228,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2275,7 +2246,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2294,7 +2264,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2313,7 +2282,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2332,7 +2300,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2351,7 +2318,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2370,7 +2336,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2389,7 +2354,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2408,7 +2372,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2427,7 +2390,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2446,7 +2408,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2465,7 +2426,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2484,7 +2444,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2503,7 +2462,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2523,7 +2481,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2542,7 +2499,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2561,7 +2517,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2580,7 +2535,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2599,7 +2553,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2618,7 +2571,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2637,7 +2589,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2656,7 +2607,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2675,7 +2625,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2694,7 +2643,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2713,7 +2661,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2732,7 +2679,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2751,7 +2697,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2770,7 +2715,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2789,7 +2733,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2808,7 +2751,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2827,7 +2769,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2846,7 +2787,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2865,7 +2805,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2884,7 +2823,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2903,7 +2841,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2922,7 +2859,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2941,7 +2877,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2960,7 +2895,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2979,7 +2913,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -2998,7 +2931,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3017,7 +2949,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3036,7 +2967,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3055,7 +2985,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3074,7 +3003,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3098,7 +3026,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3117,7 +3044,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3136,7 +3062,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3155,7 +3080,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3174,7 +3098,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3194,7 +3117,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3213,7 +3135,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3239,7 +3160,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3268,7 +3188,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3295,7 +3214,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3315,7 +3233,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3340,7 +3257,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3364,7 +3280,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3388,7 +3303,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3411,7 +3325,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3431,7 +3344,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3454,7 +3366,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3477,7 +3388,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3505,7 +3415,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3534,7 +3443,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -3561,7 +3469,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
diff --git a/testing/buildbot/chromium.webrtc.fyi.json b/testing/buildbot/chromium.webrtc.fyi.json
index 9cb0b56..4394f0b7 100644
--- a/testing/buildbot/chromium.webrtc.fyi.json
+++ b/testing/buildbot/chromium.webrtc.fyi.json
@@ -311,7 +311,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -335,7 +334,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -357,7 +355,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -383,7 +380,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -405,7 +401,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -424,7 +419,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
@@ -446,7 +440,6 @@
           "dimension_sets": [
             {
               "cpu": "x86-64",
-              "gpu": "8086:0a2e",
               "os": "Mac-10.12.6"
             }
           ],
diff --git a/testing/buildbot/filters/fuchsia.unit_tests.filter b/testing/buildbot/filters/fuchsia.unit_tests.filter
index 0f36d12..1dabe68 100644
--- a/testing/buildbot/filters/fuchsia.unit_tests.filter
+++ b/testing/buildbot/filters/fuchsia.unit_tests.filter
@@ -25,7 +25,6 @@
 -LastDownloadFinderTest.Add*
 -LastDownloadFinderTest.Simple*
 -LayoutProviderTest.Legacy*
--MediaEngagementPreloaded*
 -NativeDesktop*
 -Notifications*
 -PermissionMessageCombinationsUnittest.USBSerial*
diff --git a/testing/buildbot/filters/pixel_browser_tests.filter b/testing/buildbot/filters/pixel_browser_tests.filter
index 275ae9b6..eead3bea 100644
--- a/testing/buildbot/filters/pixel_browser_tests.filter
+++ b/testing/buildbot/filters/pixel_browser_tests.filter
@@ -7,16 +7,22 @@
 BookmarkBubbleViewBrowserTest.*

 BookmarkEditorViewBrowserTest.*

 ChromeLabsUiTest.*

+ConfirmBubbleTest.*

+ContentAnalysysDialogUiTest.*

 ContentSettingBubbleDialogTest.*

 CookieControlsBubbleViewTest.*

+CryptoModulePasswordDialogTest.*

+DeepScanningFailureModalDialogTest.*

 ExtensionInstallDialogViewInteractiveBrowserTest.*

 ExtensionUninstallDialogViewInteractiveBrowserTest.*

 FeaturePromoDialogTest.*

 FileSystemAccessUsageBubbleViewTest.*

+FirstRunDialogTest.*

 GlobalErrorBubbleTest.*

 HatsBubbleTest.*

 HungRendererDialogViewBrowserTest.*

 ImportLockDialogViewBrowserTest.*

+InlineLoginHelperBrowserTest.InvokeUi_*

 NewTabPageTest.*

 OneTimePermissionPromptBubbleViewBrowserTest.*

 OutdatedUpgradeBubbleTest.*

@@ -26,14 +32,20 @@
 PermissionRequestChipDialogBrowserTest.*

 All/PermissionPromptBubbleViewBrowserTest.*

 ProfileSigninConfirmationDialogTest.*

+PromptForScanningModalDialogTest.*

 RelaunchRecommendedBubbleViewDialogTest.*

 RelaunchRequiredDialogViewDialogTest.*

 ReopenTabPromoControllerDialogBrowserTest.*

+SafetyTipPageInfoBubbleViewDialogTest.*

 ScreenCaptureNotificationUiBrowserTest.*

+SecurePaymentConfirmationDialogViewTest.InvokeUi_*

+All/SendTabToSelfBubbleTest.*

 SessionCrashedBubbleViewTest.*

 TabGroupEditorBubbleViewDialogBrowserTest.*

 TabHoverCardBubbleViewBrowserTest.*

 UpdateRecommendedDialogTest.*

+WebAppConfirmViewBrowserTest.*

+WorkProfileSigninConfirmationDialogTest.*

 ZoomBubbleDialogTest.*

 

 # This test uses random network port and shows it on ui.

diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index 3b311cf..51abf88e1 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -652,6 +652,14 @@
       },
     },
   },
+  'mac_10.12': {
+    'swarming': {
+      'dimensions': {
+        'cpu': 'x86-64',
+        'os': 'Mac-10.12.6',
+      },
+    },
+  },
   'mac_10.13': {
     'swarming': {
       'dimensions': {
@@ -727,24 +735,6 @@
       },
     },
   },
-  'mac_mini_10.12': {
-    'swarming': {
-      'dimensions': {
-        'cpu': 'x86-64',
-        'gpu': '8086:0a2e',
-        'os': 'Mac-10.12.6',
-      },
-    },
-  },
-  'mac_mini_10.14': {
-    'swarming': {
-      'dimensions': {
-        'cpu': 'x86-64',
-        'gpu': '8086:0a2e',
-        'os': 'Mac-10.14.6',
-      },
-    },
-  },
   'mac_mini_intel_gpu_experimental': {
     'swarming': {
       'dimensions': {
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 274b6da..8b7c0bc 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -3398,7 +3398,7 @@
       },
       'mac10.14-blink-rel-dummy': {
         'mixins': [
-          'mac_mini_10.14',
+          'mac_10.14',
         ],
         'swarming': {
           'hard_timeout': 900,
@@ -4996,7 +4996,7 @@
       },
       'Mac10.12 Tests': {
         'mixins': [
-            'mac_mini_10.12',
+            'mac_10.12',
         ],
         'test_suites': {
           'gtest_tests': 'chromium_mac_gtests_no_nacl',
@@ -5974,7 +5974,7 @@
       },
       'WebRTC Chromium FYI Mac Tester': {
         'mixins': [
-          'mac_mini_10.12',
+          'mac_10.12',
         ],
         'test_suites': {
           'gtest_tests': 'webrtc_chromium_gtests',
diff --git a/testing/scripts/run_performance_tests.py b/testing/scripts/run_performance_tests.py
index 00a51f9..b687be8 100755
--- a/testing/scripts/run_performance_tests.py
+++ b/testing/scripts/run_performance_tests.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env vpython3
 # Copyright 2017 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/testing/scripts/run_variations_smoke_tests.py b/testing/scripts/run_variations_smoke_tests.py
index b960c71..27f2d74 100755
--- a/testing/scripts/run_variations_smoke_tests.py
+++ b/testing/scripts/run_variations_smoke_tests.py
@@ -128,9 +128,12 @@
   return False
 
 
-def _run_tests():
+def _run_tests(*args):
   """Runs the smoke tests.
 
+  Args:
+    args: Arguments to be passed to the chrome binary.
+
   Returns:
     0 if tests passed, otherwise 1.
   """
@@ -144,6 +147,8 @@
   chrome_options.binary_location = path_chrome
   chrome_options.add_argument('user-data-dir=' + user_data_dir)
   chrome_options.add_argument('log-file=' + log_file)
+  for arg in args:
+    chrome_options.add_argument(arg)
 
   # By default, ChromeDriver passes in --disable-backgroud-networking, however,
   # fetching variations seeds requires network connection, so override it.
@@ -232,8 +237,8 @@
   logging.basicConfig(level=logging.INFO)
   parser = argparse.ArgumentParser()
   parser.add_argument('--isolated-script-test-output', type=str)
-  args, _ = parser.parse_known_args()
-  rc = _run_tests()
+  args, rest = parser.parse_known_args()
+  rc = _run_tests(rest)
   if args.isolated_script_test_output:
     with open(args.isolated_script_test_output, 'w') as f:
       common.record_local_script_results('run_variations_smoke_tests', f, [],
diff --git a/testing/test_env.py b/testing/test_env.py
index 95f5741..8a641838 100755
--- a/testing/test_env.py
+++ b/testing/test_env.py
@@ -12,7 +12,7 @@
 import subprocess
 import sys
 import time
-
+import six
 
 # This is hardcoded to be src/ relative to this script.
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -204,12 +204,12 @@
                      stderr=subprocess.STDOUT)
     forward_signals([process])
     while process.poll() is None:
-      sys.stdout.write(reader.read())
+      sys.stdout.write(six.ensure_str(reader.read()))
       # This sleep is needed for signal propagation. See the
       # wait_with_signals() docstring.
       time.sleep(0.1)
     # Read the remaining.
-    sys.stdout.write(reader.read())
+    sys.stdout.write(six.ensure_str(reader.read()))
     print('Command %r returned exit code %d' % (argv, process.returncode))
     return process.returncode
 
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 17700db..a28d517 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -8097,6 +8097,29 @@
             ]
         }
     ],
+    "UpdateClientHintsGREASERollout": [
+        {
+            "platforms": [
+                "android",
+                "chromeos",
+                "chromeos_lacros",
+                "linux",
+                "mac",
+                "windows"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "params": {
+                        "updated_algorithm": "true"
+                    },
+                    "enable_features": [
+                        "GreaseUACH"
+                    ]
+                }
+            ]
+        }
+    ],
     "UpdateHistoryEntryPointsInIncognito": [
         {
             "platforms": [
diff --git a/third_party/blink/common/origin_trials/trial_token.cc b/third_party/blink/common/origin_trials/trial_token.cc
index 5c4884d..bbac595 100644
--- a/third_party/blink/common/origin_trials/trial_token.cc
+++ b/third_party/blink/common/origin_trials/trial_token.cc
@@ -146,7 +146,9 @@
 
   // Extract the length of the signed data (Big-endian).
   uint32_t payload_length;
-  base::ReadBigEndian(&(token_contents[kPayloadLengthOffset]), &payload_length);
+  base::ReadBigEndian(
+      reinterpret_cast<const uint8_t*>(&(token_contents[kPayloadLengthOffset])),
+      &payload_length);
 
   // Validate that the stated length matches the actual payload length.
   if (payload_length != token_contents.length() - kPayloadOffset) {
diff --git a/third_party/blink/public/mojom/web_feature/web_feature.mojom b/third_party/blink/public/mojom/web_feature/web_feature.mojom
index a55a8d66a..1db13e8 100644
--- a/third_party/blink/public/mojom/web_feature/web_feature.mojom
+++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -832,10 +832,10 @@
   kV8ForInInitializer = 1238,
   kV8Animation_Id_AttributeGetter = 1239,
   kV8Animation_Id_AttributeSetter = 1240,
-  kApplicationCacheManifestSelectInsecureOrigin = 1245,
-  kApplicationCacheManifestSelectSecureOrigin = 1246,
-  kApplicationCacheAPIInsecureOrigin = 1247,
-  kApplicationCacheAPISecureOrigin = 1248,
+  kOBSOLETE_ApplicationCacheManifestSelectInsecureOrigin = 1245,
+  kOBSOLETE_ApplicationCacheManifestSelectSecureOrigin = 1246,
+  kOBSOLETE_ApplicationCacheAPIInsecureOrigin = 1247,
+  kOBSOLETE_ApplicationCacheAPISecureOrigin = 1248,
   // The above items are available in M50 branch
 
   kCSSAtRuleApply = 1249,  // Removed
@@ -2119,7 +2119,7 @@
   kHTMLTemplateElement = 2769,
   kNoSysexWebMIDIWithoutPermission = 2770,
   kNoSysexWebMIDIOnInsecureOrigin = 2771,
-  kApplicationCacheInstalledButNoManifest = 2772,
+  kOBSOLETE_ApplicationCacheInstalledButNoManifest = 2772,
   kCustomCursorIntersectsViewport = 2776,
   kOBSOLETE_ClientHintsLang = 2777,
   kLinkRelPreloadImageSrcset = 2778,
diff --git a/third_party/blink/public/platform/input/input_handler_proxy.h b/third_party/blink/public/platform/input/input_handler_proxy.h
index 0b473e7..cc4d4ecd 100644
--- a/third_party/blink/public/platform/input/input_handler_proxy.h
+++ b/third_party/blink/public/platform/input/input_handler_proxy.h
@@ -208,12 +208,6 @@
     return currently_active_gesture_device_.value();
   }
 
- protected:
-  void RecordMainThreadScrollingReasons(blink::WebGestureDevice device,
-                                        uint32_t reasons);
-  void RecordScrollingThreadStatus(blink::WebGestureDevice device,
-                                   uint32_t reasons);
-
  private:
   friend class test::TestInputHandlerProxy;
   friend class test::InputHandlerProxyTest;
@@ -287,6 +281,11 @@
     event_attribution_enabled_ = enabled;
   }
 
+  void RecordMainThreadScrollingReasons(blink::WebGestureDevice device,
+                                        uint32_t reasons_from_scroll_begin,
+                                        bool was_main_thread_hit_tested,
+                                        bool needs_main_thread_repaint);
+
   InputHandlerProxyClient* client_;
 
   // The input handler object is owned by the compositor delegate. The input
diff --git a/third_party/blink/renderer/core/css/style_engine_test.cc b/third_party/blink/renderer/core/css/style_engine_test.cc
index bf9ca08..5700e20 100644
--- a/third_party/blink/renderer/core/css/style_engine_test.cc
+++ b/third_party/blink/renderer/core/css/style_engine_test.cc
@@ -44,6 +44,7 @@
 #include "third_party/blink/renderer/core/html/html_span_element.h"
 #include "third_party/blink/renderer/core/html/html_style_element.h"
 #include "third_party/blink/renderer/core/layout/layout_counter.h"
+#include "third_party/blink/renderer/core/layout/layout_custom_scrollbar_part.h"
 #include "third_party/blink/renderer/core/layout/layout_list_marker.h"
 #include "third_party/blink/renderer/core/layout/layout_text_fragment.h"
 #include "third_party/blink/renderer/core/layout/layout_theme.h"
@@ -4790,4 +4791,71 @@
   UpdateAllLifecyclePhases();
 }
 
+// Regression test for https://crbug.com/1270190
+TEST_F(StyleEngineTest, ScrollbarStyleNoExcessiveCaching) {
+  GetDocument().documentElement()->setInnerHTML(R"HTML(
+    <style>
+    .a {
+      width: 50px;
+      height: 50px;
+      background-color: magenta;
+      overflow-y: scroll;
+      margin: 5px;
+      float: left;
+    }
+
+    .b {
+      height: 100px;
+    }
+
+    ::-webkit-scrollbar {
+      width: 10px;
+    }
+
+    ::-webkit-scrollbar-thumb {
+      background: green;
+    }
+
+    ::-webkit-scrollbar-thumb:hover {
+      background: red;
+    }
+    </style>
+    <div class="a" id="container">
+      <div class="b">
+      </div>
+    </div>
+  )HTML");
+  UpdateAllLifecyclePhases();
+
+  // We currently don't cache ::-webkit-scrollbar-* pseudo element styles, so
+  // the cache is always empty. If we decide to cache them, we should make sure
+  // that the cache size remains bounded.
+
+  Element* container = GetDocument().getElementById("container");
+  EXPECT_FALSE(container->GetComputedStyle()->GetPseudoElementStyleCache());
+
+  PaintLayerScrollableArea* area =
+      container->GetLayoutBox()->GetScrollableArea();
+  Scrollbar* scrollbar = area->VerticalScrollbar();
+  CustomScrollbar* custom_scrollbar = To<CustomScrollbar>(scrollbar);
+
+  scrollbar->SetHoveredPart(kThumbPart);
+  UpdateAllLifecyclePhases();
+  EXPECT_FALSE(container->GetComputedStyle()->GetPseudoElementStyleCache());
+  EXPECT_EQ("#ff0000", custom_scrollbar->GetPart(kThumbPart)
+                           ->Style()
+                           ->BackgroundColor()
+                           .GetColor()
+                           .Serialized());
+
+  scrollbar->SetHoveredPart(kNoPart);
+  UpdateAllLifecyclePhases();
+  EXPECT_FALSE(container->GetComputedStyle()->GetPseudoElementStyleCache());
+  EXPECT_EQ("#008000", custom_scrollbar->GetPart(kThumbPart)
+                           ->Style()
+                           ->BackgroundColor()
+                           .GetColor()
+                           .Serialized());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index 4b06ac7b..2c78b89 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -4587,6 +4587,11 @@
   EnsureElementRareData().SetHasUndoStack(value);
 }
 
+void Element::SetScrollbarPseudoElementStylesDependOnFontMetrics(bool value) {
+  EnsureElementRareData().SetScrollbarPseudoElementStylesDependOnFontMetrics(
+      value);
+}
+
 bool Element::UpdateForceLegacyLayout(const ComputedStyle& new_style,
                                       const ComputedStyle* old_style) {
   // ::first-letter may cause structure discrepancies between DOM and layout
@@ -5581,18 +5586,21 @@
   if (style->CachedPseudoElementStylesDependOnFontMetrics())
     return true;
 
-  // Note that |HasAnyPseudoElementStyles()| counts public pseudo elements only.
-  // ::-webkit-scrollbar-*  are internal, and hence are not counted. So we must
-  // perform this check after checking the cached pseudo element styles to avoid
-  // false negatives.
-  if (!style->HasAnyPseudoElementStyles())
-    return false;
-
   // If we don't generate a PseudoElement, its style must have been cached on
   // the originating element's ComputedStyle. Hence, it remains to check styles
   // on the generated PseudoElements.
   if (!HasRareData())
     return false;
+
+  if (GetElementRareData()->ScrollbarPseudoElementStylesDependOnFontMetrics())
+    return true;
+
+  // Note that |HasAnyPseudoElementStyles()| counts public pseudo elements only.
+  // ::-webkit-scrollbar-*  are internal, and hence are not counted. So we must
+  // perform this check after checking scrollbar pseudo element styles.
+  if (!style->HasAnyPseudoElementStyles())
+    return false;
+
   for (PseudoElement* pseudo_element :
        GetElementRareData()->GetPseudoElements()) {
     if (pseudo_element->GetComputedStyle()->DependsOnFontMetrics())
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index 487030f..e1f5391e 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -1009,6 +1009,9 @@
   bool HasUndoStack() const;
   void SetHasUndoStack(bool);
 
+  // For font-related style invalidation.
+  void SetScrollbarPseudoElementStylesDependOnFontMetrics(bool);
+
  protected:
   const ElementData* GetElementData() const { return element_data_.Get(); }
   UniqueElementData& EnsureUniqueElementData();
diff --git a/third_party/blink/renderer/core/dom/element_rare_data.cc b/third_party/blink/renderer/core/dom/element_rare_data.cc
index f9396f8a..2dfc6418 100644
--- a/third_party/blink/renderer/core/dom/element_rare_data.cc
+++ b/third_party/blink/renderer/core/dom/element_rare_data.cc
@@ -51,7 +51,12 @@
 
 ElementRareData::ElementRareData(NodeRenderingData* node_layout_data)
     : NodeRareData(ClassType::kElementRareData, node_layout_data),
-      class_list_(nullptr) {}
+      class_list_(nullptr),
+      did_attach_internals_(false),
+      should_force_legacy_layout_for_child_(false),
+      style_should_force_legacy_layout_(false),
+      has_undo_stack_(false),
+      scrollbar_pseudo_element_styles_depend_on_font_metrics_(false) {}
 
 ElementRareData::~ElementRareData() {
   DCHECK(!pseudo_element_data_);
diff --git a/third_party/blink/renderer/core/dom/element_rare_data.h b/third_party/blink/renderer/core/dom/element_rare_data.h
index fb71bc01..d45c0d0 100644
--- a/third_party/blink/renderer/core/dom/element_rare_data.h
+++ b/third_party/blink/renderer/core/dom/element_rare_data.h
@@ -177,6 +177,12 @@
   }
   bool HasUndoStack() const { return has_undo_stack_; }
   void SetHasUndoStack(bool value) { has_undo_stack_ = value; }
+  bool ScrollbarPseudoElementStylesDependOnFontMetrics() const {
+    return scrollbar_pseudo_element_styles_depend_on_font_metrics_;
+  }
+  void SetScrollbarPseudoElementStylesDependOnFontMetrics(bool value) {
+    scrollbar_pseudo_element_styles_depend_on_font_metrics_ = value;
+  }
 
   AccessibleNode* GetAccessibleNode() const { return accessible_node_.Get(); }
   AccessibleNode* EnsureAccessibleNode(Element* owner_element) {
@@ -280,12 +286,11 @@
   Member<ContainerQueryData> container_query_data_;
   std::unique_ptr<RegionCaptureCropId> region_capture_crop_id_;
 
-  // NOTE: Booleans should be contiguous since the compiler will optimize them
-  // into a single memory address.
-  bool did_attach_internals_ = false;
-  bool should_force_legacy_layout_for_child_ = false;
-  bool style_should_force_legacy_layout_ = false;
-  bool has_undo_stack_ = false;
+  unsigned did_attach_internals_ : 1;
+  unsigned should_force_legacy_layout_for_child_ : 1;
+  unsigned style_should_force_legacy_layout_ : 1;
+  unsigned has_undo_stack_ : 1;
+  unsigned scrollbar_pseudo_element_styles_depend_on_font_metrics_ : 1;
 };
 
 inline LayoutSize DefaultMinimumSizeForResizing() {
diff --git a/third_party/blink/renderer/core/editing/selection_controller.cc b/third_party/blink/renderer/core/editing/selection_controller.cc
index fd471da1..d8c221e9 100644
--- a/third_party/blink/renderer/core/editing/selection_controller.cc
+++ b/third_party/blink/renderer/core/editing/selection_controller.cc
@@ -1031,8 +1031,7 @@
   if (!Selection().IsAvailable())
     return;
   if (selection_state_ != SelectionState::kExtendedSelection) {
-    HitTestRequest request(HitTestRequest::kReadOnly | HitTestRequest::kActive |
-                           HitTestRequest::kRetargetForInert);
+    HitTestRequest request(HitTestRequest::kReadOnly | HitTestRequest::kActive);
     HitTestLocation location(mouse_down_pos);
     HitTestResult result(request, location);
     frame_->GetDocument()->GetLayoutView()->HitTest(location, result);
@@ -1055,8 +1054,7 @@
     return;
 
   HitTestRequest request(HitTestRequest::kReadOnly | HitTestRequest::kActive |
-                         HitTestRequest::kMove |
-                         HitTestRequest::kRetargetForInert);
+                         HitTestRequest::kMove);
   HitTestLocation location(last_known_mouse_position_in_root_frame);
   HitTestResult result(request, location);
   layout_view->HitTest(location, result);
diff --git a/third_party/blink/renderer/core/editing/visible_units.cc b/third_party/blink/renderer/core/editing/visible_units.cc
index 6364618..8bdd5c4 100644
--- a/third_party/blink/renderer/core/editing/visible_units.cc
+++ b/third_party/blink/renderer/core/editing/visible_units.cc
@@ -441,8 +441,7 @@
     LocalFrame* frame) {
   HitTestRequest request = HitTestRequest::kMove | HitTestRequest::kReadOnly |
                            HitTestRequest::kActive |
-                           HitTestRequest::kIgnoreClipping |
-                           HitTestRequest::kRetargetForInert;
+                           HitTestRequest::kIgnoreClipping;
   HitTestLocation location(contents_point);
   HitTestResult result(request, location);
   frame->GetDocument()->GetLayoutView()->HitTest(location, result);
diff --git a/third_party/blink/renderer/core/execution_context/navigator_base.cc b/third_party/blink/renderer/core/execution_context/navigator_base.cc
index c5cc655..f6e9b96a 100644
--- a/third_party/blink/renderer/core/execution_context/navigator_base.cc
+++ b/third_party/blink/renderer/core/execution_context/navigator_base.cc
@@ -39,8 +39,7 @@
   // If the User-Agent string is frozen, platform should be a value
   // matching the frozen string per https://github.com/WICG/ua-client-hints.
   // See content::frozen_user_agent_strings.
-  if (base::FeatureList::IsEnabled(features::kReduceUserAgent) ||
-      RuntimeEnabledFeatures::UserAgentReductionEnabled(execution_context)) {
+  if (RuntimeEnabledFeatures::UserAgentReductionEnabled(execution_context)) {
 #if defined(OS_ANDROID)
     return "Linux armv81";
 #elif defined(OS_MAC)
diff --git a/third_party/blink/renderer/core/frame/deprecation.cc b/third_party/blink/renderer/core/frame/deprecation.cc
index 986fb6c..2a99595 100644
--- a/third_party/blink/renderer/core/frame/deprecation.cc
+++ b/third_party/blink/renderer/core/frame/deprecation.cc
@@ -389,25 +389,6 @@
           "sourceBuffers, where newDuration < oldDuration.",
           "6107495151960064");
 
-    case WebFeature::kApplicationCacheAPIInsecureOrigin:
-    case WebFeature::kApplicationCacheManifestSelectInsecureOrigin:
-      return DeprecationInfo::WithDetails(
-          "ApplicationCacheAPIInsecureOrigin", kM70,
-          "Application Cache was previously restricted to secure origins only "
-          "from M70 on but now secure origin use is deprecated and will be "
-          "removed in M82.  Please shift your use case over to Service "
-          "Workers.");
-
-    case WebFeature::kApplicationCacheAPISecureOrigin:
-      return DeprecationInfo::WithFeatureAndChromeStatusID(
-          "ApplicationCacheAPISecureOrigin", kM85, "Application Cache API use",
-          "6192449487634432");
-
-    case WebFeature::kApplicationCacheManifestSelectSecureOrigin:
-      return DeprecationInfo::WithFeatureAndChromeStatusID(
-          "ApplicationCacheAPISecureOrigin", kM85,
-          "Application Cache API manifest selection", "6192449487634432");
-
     case WebFeature::kNotificationInsecureOrigin:
     case WebFeature::kNotificationAPIInsecureOriginIframe:
     case WebFeature::kNotificationPermissionRequestedInsecureOrigin:
diff --git a/third_party/blink/renderer/core/input/event_handler.cc b/third_party/blink/renderer/core/input/event_handler.cc
index f930bd01..5d03d8c 100644
--- a/third_party/blink/renderer/core/input/event_handler.cc
+++ b/third_party/blink/renderer/core/input/event_handler.cc
@@ -785,8 +785,7 @@
   if (!frame_->View())
     return WebInputEventResult::kNotHandled;
 
-  HitTestRequest request(HitTestRequest::kActive |
-                         HitTestRequest::kRetargetForInert);
+  HitTestRequest request(HitTestRequest::kActive);
   // Save the document point we generate in case the window coordinate is
   // invalidated by what happens when we dispatch the event.
   PhysicalOffset document_point = frame_->View()->ConvertFromRootFrame(
@@ -887,8 +886,7 @@
   GetSelectionController().InitializeSelectionState();
 
   HitTestResult hit_test_result = event_handling_util::HitTestResultInFrame(
-      frame_, HitTestLocation(document_point),
-      HitTestRequest::kReadOnly | HitTestRequest::kRetargetForInert);
+      frame_, HitTestLocation(document_point), HitTestRequest::kReadOnly);
   InputDeviceCapabilities* source_capabilities =
       frame_->DomWindow()->GetInputDeviceCapabilities()->FiresTouchEvents(
           mouse_event.FromTouch());
@@ -916,8 +914,7 @@
   if (event_result == WebInputEventResult::kNotHandled) {
     if (ShouldRefetchEventTarget(mev)) {
       HitTestRequest read_only_request(HitTestRequest::kReadOnly |
-                                       HitTestRequest::kActive |
-                                       HitTestRequest::kRetargetForInert);
+                                       HitTestRequest::kActive);
       mev = frame_->GetDocument()->PerformMouseEventHitTest(
           read_only_request, document_point, mouse_event);
     }
@@ -1032,8 +1029,7 @@
     return WebInputEventResult::kHandledSystem;
   }
 
-  HitTestRequest::HitTestRequestType hit_type =
-      HitTestRequest::kMove | HitTestRequest::kRetargetForInert;
+  HitTestRequest::HitTestRequestType hit_type = HitTestRequest::kMove;
   if (mouse_event_manager_->MousePressed()) {
     hit_type |= HitTestRequest::kActive;
   }
@@ -1203,8 +1199,7 @@
 
   // Mouse events simulated from touch should not hit-test again.
   DCHECK(!mouse_event.FromTouch());
-  HitTestRequest::HitTestRequestType hit_type =
-      HitTestRequest::kRelease | HitTestRequest::kRetargetForInert;
+  HitTestRequest::HitTestRequestType hit_type = HitTestRequest::kRelease;
   HitTestRequest request(hit_type);
   MouseEventWithHitTestResults mev = GetMouseEventTarget(request, mouse_event);
   LocalFrame* subframe = event_handling_util::GetTargetSubframe(
@@ -1258,8 +1253,7 @@
   if (!frame_->View())
     return event_result;
 
-  HitTestRequest request(HitTestRequest::kReadOnly |
-                         HitTestRequest::kRetargetForInert);
+  HitTestRequest request(HitTestRequest::kReadOnly);
   MouseEventWithHitTestResults mev =
       event_handling_util::PerformMouseEventHitTest(frame_, request, event);
 
@@ -1945,7 +1939,6 @@
   // disabled). Note that we don't yet apply hover/active state here because
   // we need to resolve touch adjustment first so that we apply hover/active
   // it to the final adjusted node.
-  hit_type |= HitTestRequest::kRetargetForInert;
   hit_type |= HitTestRequest::kReadOnly;
   WebGestureEvent adjusted_event = gesture_event;
   LayoutSize hit_rect_size;
@@ -2062,8 +2055,7 @@
 
   PhysicalOffset position_in_contents(
       v->ConvertFromRootFrame(FlooredIntPoint(event.PositionInRootFrame())));
-  HitTestRequest request(HitTestRequest::kActive |
-                         HitTestRequest::kRetargetForInert);
+  HitTestRequest request(HitTestRequest::kActive);
   MouseEventWithHitTestResults mev =
       frame_->GetDocument()->PerformMouseEventHitTest(
           request, position_in_contents, event);
@@ -2185,8 +2177,7 @@
           .origin();
 
   // Use the focused node as the target for hover and active.
-  HitTestRequest request(HitTestRequest::kActive |
-                         HitTestRequest::kRetargetForInert);
+  HitTestRequest request(HitTestRequest::kActive);
   HitTestLocation location(location_in_root_frame);
   HitTestResult result(request, location);
   result.SetInnerNode(focused_element ? static_cast<Node*>(focused_element)
@@ -2272,8 +2263,7 @@
 
   if (auto* layout_object = frame_->ContentLayoutObject()) {
     if (LocalFrameView* view = frame_->View()) {
-      HitTestRequest request(HitTestRequest::kMove |
-                             HitTestRequest::kRetargetForInert);
+      HitTestRequest request(HitTestRequest::kMove);
       HitTestLocation location(view->ViewportToFrame(
           mouse_event_manager_->LastKnownMousePositionInViewport()));
       HitTestResult result(request, location);
@@ -2291,8 +2281,7 @@
     // FIXME: Enable condition when http://crbug.com/226842 lands
     // m_lastDeferredTapElement.get() == m_frame->document()->activeElement()
     HitTestRequest request(HitTestRequest::kTouchEvent |
-                           HitTestRequest::kRelease |
-                           HitTestRequest::kRetargetForInert);
+                           HitTestRequest::kRelease);
     frame_->GetDocument()->UpdateHoverActiveState(
         request.Active(), !request.Move(), last_deferred_tap_element_.Get());
   }
@@ -2325,8 +2314,7 @@
     ui::mojom::blink::DragOperation operation) {
   // Asides from routing the event to the correct frame, the hit test is also an
   // opportunity for Layer to update the :hover and :active pseudoclasses.
-  HitTestRequest request(HitTestRequest::kRelease |
-                         HitTestRequest::kRetargetForInert);
+  HitTestRequest request(HitTestRequest::kRelease);
   MouseEventWithHitTestResults mev =
       event_handling_util::PerformMouseEventHitTest(frame_, request, event);
 
diff --git a/third_party/blink/renderer/core/input/event_handler.h b/third_party/blink/renderer/core/input/event_handler.h
index 22df865..52daf5a 100644
--- a/third_party/blink/renderer/core/input/event_handler.h
+++ b/third_party/blink/renderer/core/input/event_handler.h
@@ -100,9 +100,8 @@
 
   HitTestResult HitTestResultAtLocation(
       const HitTestLocation&,
-      HitTestRequest::HitTestRequestType hit_type =
-          HitTestRequest::kReadOnly | HitTestRequest::kActive |
-          HitTestRequest::kRetargetForInert,
+      HitTestRequest::HitTestRequestType hit_type = HitTestRequest::kReadOnly |
+                                                    HitTestRequest::kActive,
       const LayoutObject* stop_node = nullptr,
       bool no_lifecycle_update = false);
 
diff --git a/third_party/blink/renderer/core/input/gesture_manager.cc b/third_party/blink/renderer/core/input/gesture_manager.cc
index 4f853a4..6e5d0ac 100644
--- a/third_party/blink/renderer/core/input/gesture_manager.cc
+++ b/third_party/blink/renderer/core/input/gesture_manager.cc
@@ -85,8 +85,7 @@
 
 HitTestRequest::HitTestRequestType GestureManager::GetHitTypeForGestureType(
     WebInputEvent::Type type) {
-  HitTestRequest::HitTestRequestType hit_type =
-      HitTestRequest::kTouchEvent | HitTestRequest::kRetargetForInert;
+  HitTestRequest::HitTestRequestType hit_type = HitTestRequest::kTouchEvent;
   switch (type) {
     case WebInputEvent::Type::kGestureShowPress:
     case WebInputEvent::Type::kGestureTapUnconfirmed:
diff --git a/third_party/blink/renderer/core/input/mouse_event_manager.cc b/third_party/blink/renderer/core/input/mouse_event_manager.cc
index 2f3e84b..473c52c 100644
--- a/third_party/blink/renderer/core/input/mouse_event_manager.cc
+++ b/third_party/blink/renderer/core/input/mouse_event_manager.cc
@@ -894,8 +894,7 @@
     return false;
 
   if (mouse_down_may_start_drag_) {
-    HitTestRequest request(HitTestRequest::kReadOnly |
-                           HitTestRequest::kRetargetForInert);
+    HitTestRequest request(HitTestRequest::kReadOnly);
     HitTestLocation location(mouse_down_pos_);
     HitTestResult result(request, location);
     frame_->ContentLayoutObject()->HitTest(location, result);
diff --git a/third_party/blink/renderer/core/input/mouse_wheel_event_manager.cc b/third_party/blink/renderer/core/input/mouse_wheel_event_manager.cc
index 3f55af5..9726bc7 100644
--- a/third_party/blink/renderer/core/input/mouse_wheel_event_manager.cc
+++ b/third_party/blink/renderer/core/input/mouse_wheel_event_manager.cc
@@ -157,8 +157,7 @@
   PhysicalOffset v_point(
       view->ConvertFromRootFrame(FlooredIntPoint(event.PositionInRootFrame())));
 
-  HitTestRequest request(HitTestRequest::kReadOnly |
-                         HitTestRequest::kRetargetForInert);
+  HitTestRequest request(HitTestRequest::kReadOnly);
   HitTestLocation location(v_point);
   HitTestResult result(request, location);
   doc->GetLayoutView()->HitTest(location, result);
diff --git a/third_party/blink/renderer/core/input/pointer_event_manager.cc b/third_party/blink/renderer/core/input/pointer_event_manager.cc
index 00ea696..51f74ab 100644
--- a/third_party/blink/renderer/core/input/pointer_event_manager.cc
+++ b/third_party/blink/renderer/core/input/pointer_event_manager.cc
@@ -364,8 +364,7 @@
 
   HitTestRequest::HitTestRequestType hit_type =
       HitTestRequest::kTouchEvent | HitTestRequest::kReadOnly |
-      HitTestRequest::kActive | HitTestRequest::kListBased |
-      HitTestRequest::kRetargetForInert;
+      HitTestRequest::kActive | HitTestRequest::kListBased;
   LocalFrame& root_frame = frame_->LocalFrameRoot();
   // TODO(szager): Shouldn't this be PositionInScreen() ?
   PhysicalOffset hit_test_point = PhysicalOffset::FromFloatPointRound(
@@ -422,9 +421,9 @@
   // before firing the event.
   if (web_pointer_event.GetType() == WebInputEvent::Type::kPointerDown ||
       !pending_pointer_capture_target_.Contains(pointer_id)) {
-    HitTestRequest::HitTestRequestType hit_type =
-        HitTestRequest::kTouchEvent | HitTestRequest::kReadOnly |
-        HitTestRequest::kActive | HitTestRequest::kRetargetForInert;
+    HitTestRequest::HitTestRequestType hit_type = HitTestRequest::kTouchEvent |
+                                                  HitTestRequest::kReadOnly |
+                                                  HitTestRequest::kActive;
     HitTestLocation location(frame_->View()->ConvertFromRootFrame(
         PhysicalOffset::FromFloatPointRound(
             FloatPoint(web_pointer_event.PositionInWidget()))));
@@ -861,8 +860,7 @@
       // test to find the new target.
       if (pointer_capture_target_.find(pointer_event->pointerId()) !=
           pointer_capture_target_.end()) {
-        HitTestRequest::HitTestRequestType hit_type =
-            HitTestRequest::kRelease | HitTestRequest::kRetargetForInert;
+        HitTestRequest::HitTestRequestType hit_type = HitTestRequest::kRelease;
         HitTestRequest request(hit_type);
         MouseEventWithHitTestResults mev =
             event_handling_util::PerformMouseEventHitTest(frame_, request,
diff --git a/third_party/blink/renderer/core/input/scroll_manager.cc b/third_party/blink/renderer/core/input/scroll_manager.cc
index dbfa7cb..b366c166 100644
--- a/third_party/blink/renderer/core/input/scroll_manager.cc
+++ b/third_party/blink/renderer/core/input/scroll_manager.cc
@@ -1051,8 +1051,7 @@
     LocalFrameView* view = frame_->View();
     PhysicalOffset view_point(view->ConvertFromRootFrame(
         FlooredIntPoint(gesture_event.PositionInRootFrame())));
-    HitTestRequest request(HitTestRequest::kReadOnly |
-                           HitTestRequest::kRetargetForInert);
+    HitTestRequest request(HitTestRequest::kReadOnly);
     HitTestLocation location(view_point);
     HitTestResult result(request, location);
     document->GetLayoutView()->HitTest(location, result);
diff --git a/third_party/blink/renderer/core/layout/custom_scrollbar.cc b/third_party/blink/renderer/core/layout/custom_scrollbar.cc
index 60b08fe5..7d3fdcc 100644
--- a/third_party/blink/renderer/core/layout/custom_scrollbar.cc
+++ b/third_party/blink/renderer/core/layout/custom_scrollbar.cc
@@ -146,13 +146,16 @@
   }
   if (!element->GetLayoutObject())
     return nullptr;
-  const ComputedStyle* source_style = element->GetLayoutObject()->Style();
+  const ComputedStyle* source_style = StyleSource()->GetLayoutObject()->Style();
   scoped_refptr<const ComputedStyle> part_style =
       element->UncachedStyleForPseudoElement(
           StyleRequest(pseudo_id, this, part_type, source_style));
   if (!part_style)
     return nullptr;
-  return source_style->AddCachedPseudoElementStyle(std::move(part_style));
+  if (part_style->DependsOnFontMetrics()) {
+    element->SetScrollbarPseudoElementStylesDependOnFontMetrics(true);
+  }
+  return part_style;
 }
 
 void CustomScrollbar::DestroyScrollbarParts() {
diff --git a/third_party/blink/renderer/core/layout/hit_test_request.h b/third_party/blink/renderer/core/layout/hit_test_request.h
index c6e64e7e..95ee04a 100644
--- a/third_party/blink/renderer/core/layout/hit_test_request.h
+++ b/third_party/blink/renderer/core/layout/hit_test_request.h
@@ -45,16 +45,15 @@
     kAllowChildFrameContent = 1 << 8,
     kChildFrameHitTest = 1 << 9,
     kIgnorePointerEventsNone = 1 << 10,
-    kRetargetForInert = 1 << 11,
     // Collect a list of nodes instead of just one.
     // (This is for elementsFromPoint and rect-based tests).
-    kListBased = 1 << 12,
+    kListBased = 1 << 11,
     // When using list-based testing, this flag causes us to continue hit
     // testing after a hit has been found.
-    kPenetratingList = 1 << 13,
-    kAvoidCache = 1 << 14,
-    kIgnoreZeroOpacityObjects = 1 << 15,
-    kHitTestVisualOverflow = 1 << 16,
+    kPenetratingList = 1 << 12,
+    kAvoidCache = 1 << 13,
+    kIgnoreZeroOpacityObjects = 1 << 14,
+    kHitTestVisualOverflow = 1 << 15,
   };
 
   typedef unsigned HitTestRequestType;
@@ -87,7 +86,6 @@
   bool IgnorePointerEventsNone() const {
     return request_type_ & kIgnorePointerEventsNone;
   }
-  bool RetargetForInert() const { return request_type_ & kRetargetForInert; }
   bool ListBased() const { return request_type_ & kListBased; }
   bool PenetratingList() const { return request_type_ & kPenetratingList; }
   bool AvoidCache() const { return request_type_ & kAvoidCache; }
diff --git a/third_party/blink/renderer/core/layout/hit_test_result.cc b/third_party/blink/renderer/core/layout/hit_test_result.cc
index fc2d6be..134c7f4 100644
--- a/third_party/blink/renderer/core/layout/hit_test_result.cc
+++ b/third_party/blink/renderer/core/layout/hit_test_result.cc
@@ -71,7 +71,6 @@
     : hit_test_request_(other.hit_test_request_),
       cacheable_(other.cacheable_),
       inner_node_(other.InnerNode()),
-      inert_node_(other.InertNode()),
       inner_element_(other.InnerElement()),
       inner_possibly_pseudo_node_(other.inner_possibly_pseudo_node_),
       point_in_inner_node_frame_(other.point_in_inner_node_frame_),
@@ -98,7 +97,7 @@
 
 bool HitTestResult::EqualForCacheability(const HitTestResult& other) const {
   return hit_test_request_.EqualForCacheability(other.hit_test_request_) &&
-         inner_node_ == other.InnerNode() && inert_node_ == other.InertNode() &&
+         inner_node_ == other.InnerNode() &&
          inner_element_ == other.InnerElement() &&
          inner_possibly_pseudo_node_ == other.InnerPossiblyPseudoNode() &&
          point_in_inner_node_frame_ == other.point_in_inner_node_frame_ &&
@@ -115,7 +114,6 @@
 
 void HitTestResult::PopulateFromCachedResult(const HitTestResult& other) {
   inner_node_ = other.InnerNode();
-  inert_node_ = other.InertNode();
   inner_element_ = other.InnerElement();
   inner_possibly_pseudo_node_ = other.InnerPossiblyPseudoNode();
   point_in_inner_node_frame_ = other.point_in_inner_node_frame_;
@@ -137,7 +135,6 @@
 void HitTestResult::Trace(Visitor* visitor) const {
   visitor->Trace(hit_test_request_);
   visitor->Trace(inner_node_);
-  visitor->Trace(inert_node_);
   visitor->Trace(inner_element_);
   visitor->Trace(inner_possibly_pseudo_node_);
   visitor->Trace(inner_url_element_);
@@ -303,24 +300,6 @@
     return;
   }
 
-  if (RuntimeEnabledFeatures::InertAttributeEnabled()) {
-    // TODO(crbug.com/692360): Remove inert retargeting, and instead use
-    // 'pointer-events: none'.
-    if (GetHitTestRequest().RetargetForInert()) {
-      if (n->IsInert() && n != n->GetDocument().documentElement()) {
-        if (!inert_node_)
-          inert_node_ = n;
-
-        return;
-      }
-
-      if (inert_node_ && n != inert_node_ &&
-          !n->IsShadowIncludingInclusiveAncestorOf(*inert_node_)) {
-        return;
-      }
-    }
-  }
-
   inner_possibly_pseudo_node_ = n;
   if (auto* pseudo_element = DynamicTo<PseudoElement>(n))
     n = pseudo_element->InnerNodeForHitTesting();
@@ -335,14 +314,6 @@
     inner_element_ = FlatTreeTraversal::ParentElement(*inner_node_);
 }
 
-void HitTestResult::SetInertNode(Node* n) {
-  // Don't overwrite an existing value for inert_node_
-  if (inert_node_)
-    DCHECK(n == inert_node_);
-
-  inert_node_ = n;
-}
-
 void HitTestResult::SetURLElement(Element* n) {
   inner_url_element_ = n;
 }
@@ -520,10 +491,6 @@
 HitTestResult::AddNodeToListBasedTestResultInternal(
     Node* node,
     const HitTestLocation& location) {
-  // If we are in the process of retargeting for `inert`, continue.
-  if (GetHitTestRequest().RetargetForInert() && InertNode() && !InnerNode())
-    return std::make_tuple(false, kContinueHitTesting);
-
   // If not a list-based test, stop testing because the hit has been found.
   if (!GetHitTestRequest().ListBased())
     return std::make_tuple(false, kStopHitTesting);
@@ -601,9 +568,6 @@
     canvas_region_id_ = other.CanvasRegionId();
   }
 
-  if (!inert_node_ && other.InertNode())
-    SetInertNode(other.InertNode());
-
   if (other.list_based_test_result_) {
     NodeSet& set = MutableListBasedTestResult();
     for (NodeSet::const_iterator it = other.list_based_test_result_->begin(),
diff --git a/third_party/blink/renderer/core/layout/hit_test_result.h b/third_party/blink/renderer/core/layout/hit_test_result.h
index a6f5910..d9988a03 100644
--- a/third_party/blink/renderer/core/layout/hit_test_result.h
+++ b/third_party/blink/renderer/core/layout/hit_test_result.h
@@ -85,7 +85,6 @@
   // FIXME: Make these less error-prone for rect-based hit tests (center point
   // or fail).
   Node* InnerNode() const { return inner_node_.Get(); }
-  Node* InertNode() const { return inert_node_.Get(); }
   Node* InnerPossiblyPseudoNode() const {
     return inner_possibly_pseudo_node_.Get();
   }
@@ -141,7 +140,6 @@
   const HitTestRequest& GetHitTestRequest() const { return hit_test_request_; }
 
   void SetInnerNode(Node*);
-  void SetInertNode(Node*);
   HTMLAreaElement* ImageAreaForImage() const;
   void SetURLElement(Element*);
   void SetScrollbar(Scrollbar*);
@@ -213,7 +211,6 @@
   bool cacheable_;
 
   Member<Node> inner_node_;
-  Member<Node> inert_node_;
   // This gets calculated in the first call to InnerElement function.
   Member<Element> inner_element_;
   Member<Node> inner_possibly_pseudo_node_;
diff --git a/third_party/blink/renderer/core/layout/layout_embedded_content.cc b/third_party/blink/renderer/core/layout/layout_embedded_content.cc
index 856008c..b43c489 100644
--- a/third_party/blink/renderer/core/layout/layout_embedded_content.cc
+++ b/third_party/blink/renderer/core/layout/layout_embedded_content.cc
@@ -185,7 +185,6 @@
           result.GetHitTestRequest().GetStopNode());
       HitTestResult child_frame_result(new_hit_test_request,
                                        new_hit_test_location);
-      child_frame_result.SetInertNode(result.InertNode());
 
       // The frame's layout and style must be up to date if we reach here.
       bool is_inside_child_frame = child_layout_view->HitTestNoLifecycleUpdate(
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc
index 478be56..7639f87 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_foreign_object.cc
@@ -161,7 +161,6 @@
   // element, but PaintLayer::HitTestLayer assumes it has not been.
   HitTestLocation local_without_offset(*local_location, -PhysicalLocation());
   HitTestResult layer_result(result.GetHitTestRequest(), local_without_offset);
-  layer_result.SetInertNode(result.InertNode());
   bool retval = Layer()->HitTest(local_without_offset, layer_result,
                                  PhysicalRect(PhysicalRect::InfiniteIntRect()));
 
diff --git a/third_party/blink/renderer/core/page/context_menu_controller.cc b/third_party/blink/renderer/core/page/context_menu_controller.cc
index 08b9e3f..7193fa9 100644
--- a/third_party/blink/renderer/core/page/context_menu_controller.cc
+++ b/third_party/blink/renderer/core/page/context_menu_controller.cc
@@ -423,9 +423,8 @@
   if (context_menu_client_receiver_.is_bound())
     context_menu_client_receiver_.reset();
 
-  HitTestRequest::HitTestRequestType type = HitTestRequest::kReadOnly |
-                                            HitTestRequest::kActive |
-                                            HitTestRequest::kRetargetForInert;
+  HitTestRequest::HitTestRequestType type =
+      HitTestRequest::kReadOnly | HitTestRequest::kActive;
   if (base::FeatureList::IsEnabled(
           features::kEnablePenetratingImageSelection)) {
     type |= HitTestRequest::kPenetratingList | HitTestRequest::kListBased;
diff --git a/third_party/blink/renderer/core/page/drag_controller.cc b/third_party/blink/renderer/core/page/drag_controller.cc
index d638212..366ee68 100644
--- a/third_party/blink/renderer/core/page/drag_controller.cc
+++ b/third_party/blink/renderer/core/page/drag_controller.cc
@@ -372,8 +372,7 @@
 // This can return null if an empty document is loaded.
 static Element* ElementUnderMouse(Document* document_under_mouse,
                                   const PhysicalOffset& point) {
-  HitTestRequest request(HitTestRequest::kReadOnly | HitTestRequest::kActive |
-                         HitTestRequest::kRetargetForInert);
+  HitTestRequest request(HitTestRequest::kReadOnly | HitTestRequest::kActive);
   HitTestLocation location(point);
   HitTestResult result(request, location);
   document_under_mouse->GetLayoutView()->HitTest(location, result);
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index 13f3ff3..0654194 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -2355,7 +2355,6 @@
       // to 'result' if we know we're frontmost.
       STACK_UNINITIALIZED HitTestResult temp_result(
           result.GetHitTestRequest(), recursion_data.original_location);
-      temp_result.SetInertNode(result.InertNode());
       bool inside_fragment_foreground_rect = false;
 
       if (HitTestContentsForFragments(
@@ -2377,9 +2376,6 @@
                  result.GetHitTestRequest().ListBased() &&
                  IsHitCandidateForStopNode(GetLayoutObject(), stop_node)) {
         result.Append(temp_result);
-      } else if (result.GetHitTestRequest().RetargetForInert() &&
-                 IsHitCandidateForStopNode(GetLayoutObject(), stop_node)) {
-        result.SetInertNode(temp_result.InertNode());
       }
     }
   }
@@ -2403,7 +2399,6 @@
   if (recursion_data.intersects_location && IsSelfPaintingLayer()) {
     STACK_UNINITIALIZED HitTestResult temp_result(
         result.GetHitTestRequest(), recursion_data.original_location);
-    temp_result.SetInertNode(result.InertNode());
     bool inside_fragment_background_rect = false;
     if (HitTestContentsForFragments(layer_fragments, temp_result,
                                     recursion_data.location, kHitTestSelf,
@@ -2416,9 +2411,6 @@
       else
         result = temp_result;
       return this;
-    } else if (result.GetHitTestRequest().RetargetForInert() &&
-               IsHitCandidateForStopNode(GetLayoutObject(), stop_node)) {
-      result.SetInertNode(temp_result.InertNode());
     }
     if (inside_fragment_background_rect &&
         result.GetHitTestRequest().ListBased() &&
@@ -2672,7 +2664,6 @@
     PaintLayer* hit_layer = nullptr;
     STACK_UNINITIALIZED HitTestResult temp_result(
         result.GetHitTestRequest(), recursion_data.original_location);
-    temp_result.SetInertNode(result.InertNode());
     hit_layer = child_layer->HitTestLayer(
         transform_container, container_fragment, temp_result, recursion_data,
         /*applied_transform*/ false, container_transform_state,
@@ -2690,8 +2681,6 @@
         result = temp_result;
       if (!depth_sort_descendants)
         break;
-    } else if (result.GetHitTestRequest().RetargetForInert()) {
-      result.SetInertNode(temp_result.InertNode());
     }
   }
 
diff --git a/third_party/blink/renderer/core/style/computed_style.h b/third_party/blink/renderer/core/style/computed_style.h
index 507543c..d170927 100644
--- a/third_party/blink/renderer/core/style/computed_style.h
+++ b/third_party/blink/renderer/core/style/computed_style.h
@@ -286,7 +286,7 @@
   StyleCachedData& EnsureCachedData() const;
 
   bool HasCachedPseudoElementStyles() const;
-  PseudoElementStyleCache* GetPseudoElementStyleCache() const;
+  CORE_EXPORT PseudoElementStyleCache* GetPseudoElementStyleCache() const;
   PseudoElementStyleCache& EnsurePseudoElementStyleCache() const;
 
   Vector<AtomicString>* GetVariableNamesCache() const;
@@ -3088,6 +3088,7 @@
       TextDecorationEqualDoesNotRequireRecomputeInkOverflow);
   FRIEND_TEST_ALL_PREFIXES(ComputedStyleTest,
                            TextDecorationNotEqualRequiresRecomputeInkOverflow);
+  FRIEND_TEST_ALL_PREFIXES(StyleEngineTest, ScrollbarStyleNoExcessiveCaching);
 };
 
 inline bool ComputedStyle::HasAnyPseudoElementStyles() const {
diff --git a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
index 9c41fb7..3fb299c 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_layout_object.cc
@@ -1173,8 +1173,7 @@
   PaintLayer* layer = To<LayoutBox>(layout_object_.Get())->Layer();
   DCHECK(layer);
 
-  HitTestRequest request(HitTestRequest::kReadOnly | HitTestRequest::kActive |
-                         HitTestRequest::kRetargetForInert);
+  HitTestRequest request(HitTestRequest::kReadOnly | HitTestRequest::kActive);
   HitTestLocation location(point);
   HitTestResult hit_test_result = HitTestResult(request, location);
   layer->HitTest(location, hit_test_result,
diff --git a/third_party/blink/renderer/platform/PRESUBMIT.py b/third_party/blink/renderer/platform/PRESUBMIT.py
index c583756..018410a 100644
--- a/third_party/blink/renderer/platform/PRESUBMIT.py
+++ b/third_party/blink/renderer/platform/PRESUBMIT.py
@@ -22,7 +22,6 @@
 LACROS_CHROMEOS_FEATURE_STATUS_PARITY_IGNORE_LIST = [
     'BarcodeDetector',  # crbug.com/1235855
     'DigitalGoods',  # crbug.com/1235859
-    'ForceTallerSelectPopup',  # crbug.com/1235860
     'NetInfoDownlinkMax',  # crbug.com/1235864
     'WebBluetooth',  # crbug.com/1235867
     'WebBluetoothManufacturerDataFilter',  # crbug.com/1235869
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 4953f69..4cae866 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1121,7 +1121,7 @@
     },
     {
       name: "ForceTallerSelectPopup",
-      status: {"ChromeOS": "stable"},
+      status: {"ChromeOS": "stable", "Lacros": "stable"},
     },
     {
       name: "FractionalLineHeight",
diff --git a/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc b/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc
index 2558a2b..c201a85 100644
--- a/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc
+++ b/third_party/blink/renderer/platform/widget/input/input_handler_proxy.cc
@@ -169,7 +169,7 @@
 
 cc::ScrollBeginThreadState RecordScrollingThread(
     bool scrolling_on_compositor_thread,
-    bool blocked_on_main_thread_event_handler,
+    bool blocked_on_main_at_begin,
     WebGestureDevice device) {
   const char* kWheelHistogramName = "Renderer4.ScrollingThread.Wheel";
   const char* kTouchHistogramName = "Renderer4.ScrollingThread.Touch";
@@ -177,7 +177,7 @@
   auto status = cc::ScrollBeginThreadState::kScrollingOnMain;
   if (scrolling_on_compositor_thread) {
     status =
-        blocked_on_main_thread_event_handler
+        blocked_on_main_at_begin
             ? cc::ScrollBeginThreadState::kScrollingOnCompositorBlockedOnMain
             : cc::ScrollBeginThreadState::kScrollingOnCompositor;
   }
@@ -421,7 +421,8 @@
     // DROP the ScrollEnd. We call this to ensure symmetry between
     // RecordScrollBegin and RecordScrollEnd but we should probably be avoiding
     // this if the scroll never starts. https://crbug.com/1082601.
-    RecordMainThreadScrollingReasons(gesture_event->SourceDevice(), 0);
+    RecordMainThreadScrollingReasons(gesture_event->SourceDevice(), 0, false,
+                                     false);
 
     // If the main thread failed to return a scroller for whatever reason,
     // consider the ScrollBegin to be dropped.
@@ -839,22 +840,27 @@
 
 void InputHandlerProxy::RecordMainThreadScrollingReasons(
     WebGestureDevice device,
-    uint32_t reasons) {
+    uint32_t reasons_from_scroll_begin,
+    bool was_main_thread_hit_tested,
+    bool needs_main_thread_repaint) {
   if (device != WebGestureDevice::kTouchpad &&
       device != WebGestureDevice::kScrollbar &&
       device != WebGestureDevice::kTouchscreen) {
     return;
   }
 
-  // NonCompositedScrollReasons should only be set on the main thread.
-  DCHECK(
-      !cc::MainThreadScrollingReason::HasNonCompositedScrollReasons(reasons));
+  // The "NonCompositedScrollReasons" are only reported by the pre-unification
+  // main thread event handling path.
+  DCHECK(!cc::MainThreadScrollingReason::HasNonCompositedScrollReasons(
+      reasons_from_scroll_begin));
 
   // This records whether a scroll is handled on the main or compositor
   // threads. Note: scrolls handled on the compositor but blocked on main due
   // to event handlers are still considered compositor scrolls.
   const bool is_compositor_scroll =
-      reasons == cc::MainThreadScrollingReason::kNotScrollingOnMain;
+      reasons_from_scroll_begin ==
+          cc::MainThreadScrollingReason::kNotScrollingOnMain &&
+      !needs_main_thread_repaint;
 
   absl::optional<EventDisposition> disposition =
       (device == WebGestureDevice::kTouchpad ? mouse_wheel_result_
@@ -866,20 +872,47 @@
   bool blocked_on_main_thread_handler =
       disposition.has_value() && disposition == DID_NOT_HANDLE;
 
+  bool blocked_on_main_at_begin =
+      blocked_on_main_thread_handler || was_main_thread_hit_tested;
+
   auto scroll_start_state = RecordScrollingThread(
-      is_compositor_scroll, blocked_on_main_thread_handler, device);
+      is_compositor_scroll, blocked_on_main_at_begin, device);
   input_handler_->RecordScrollBegin(GestureScrollInputType(device),
                                     scroll_start_state);
 
+  uint32_t reportable_reasons = reasons_from_scroll_begin;
   if (blocked_on_main_thread_handler) {
     // We should also collect main thread scrolling reasons if a scroll event
     // scrolls on impl thread but is blocked by main thread event handlers.
-    reasons |= (device == WebGestureDevice::kTouchpad
-                    ? cc::MainThreadScrollingReason::kWheelEventHandlerRegion
-                    : cc::MainThreadScrollingReason::kTouchEventHandlerRegion);
+    reportable_reasons |=
+        (device == WebGestureDevice::kTouchpad
+             ? cc::MainThreadScrollingReason::kWheelEventHandlerRegion
+             : cc::MainThreadScrollingReason::kTouchEventHandlerRegion);
+  }
+  if (was_main_thread_hit_tested) {
+    reportable_reasons |= cc::MainThreadScrollingReason::kFailedHitTest;
   }
 
-  RecordScrollReasonsMetric(device, reasons);
+  if (needs_main_thread_repaint) {
+    // With scroll unification, most of the values in MainThreadScrollingReason
+    // aren't reflected in reasons_from_scroll_begin, since we are not scrolling
+    // "on main" from ThreadedInputHandler's perspective. But we still want to
+    // log a reason to UMA if the user will not see new pixels until the next
+    // BeginMainFrame. We use kNoScrollingLayer here to cover scenarios that
+    // were reported pre-unification as one of:
+    //
+    //   kHasBackgroundAttachmentFixedObjects
+    //   kThreadedScrollingDisabled
+    //   kNonFastScrollableRegion
+    //   kNotOpaqueForTextAndLCDText
+    //   kCantPaintScrollingBackgroundAndLCDText
+    //
+    // TODO(crbug.com/1082590): Add new plumbing to distinguish between these in
+    // the post-unification world?
+    reportable_reasons |= cc::MainThreadScrollingReason::kNoScrollingLayer;
+  }
+
+  RecordScrollReasonsMetric(device, reportable_reasons);
 }
 
 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleMouseWheel(
@@ -975,7 +1008,9 @@
   }
 
   RecordMainThreadScrollingReasons(gesture_event.SourceDevice(),
-                                   scroll_status.main_thread_scrolling_reasons);
+                                   scroll_status.main_thread_scrolling_reasons,
+                                   scroll_state.is_main_thread_hit_tested(),
+                                   scroll_status.needs_main_thread_repaint);
 
   InputHandlerProxy::EventDisposition result = DID_NOT_HANDLE;
   scroll_sequence_ignored_ = false;
diff --git a/third_party/blink/renderer/platform/widget/input/input_handler_proxy_unittest.cc b/third_party/blink/renderer/platform/widget/input/input_handler_proxy_unittest.cc
index 8315446..6ce8c28 100644
--- a/third_party/blink/renderer/platform/widget/input/input_handler_proxy_unittest.cc
+++ b/third_party/blink/renderer/platform/widget/input/input_handler_proxy_unittest.cc
@@ -287,7 +287,7 @@
       : InputHandlerProxy(input_handler, client) {}
   void RecordMainThreadScrollingReasonsForTest(WebGestureDevice device,
                                                uint32_t reasons) {
-    RecordMainThreadScrollingReasons(device, reasons);
+    RecordMainThreadScrollingReasons(device, reasons, false, false);
   }
 
   MOCK_METHOD0(SetNeedsAnimateInput, void());
@@ -3628,6 +3628,68 @@
                                             gesture_scroll_end_));
 }
 
+TEST_P(InputHandlerProxyMainThreadScrollingReasonTest,
+       ImplHandled_MainThreadHitTest) {
+  if (!base::FeatureList::IsEnabled(features::kScrollUnification))
+    return;
+
+  expected_disposition_ = InputHandlerProxy::DID_HANDLE;
+  VERIFY_AND_RESET_MOCKS();
+
+  gesture_.data.scroll_begin.scrollable_area_element_id = 1;
+  gesture_.data.scroll_begin.main_thread_hit_tested = true;
+
+  EXPECT_CALL(mock_input_handler_, ScrollBegin(_, _))
+      .WillOnce(testing::Return(kImplThreadScrollState));
+  EXPECT_CALL(
+      mock_input_handler_,
+      RecordScrollBegin(
+          _, cc::ScrollBeginThreadState::kScrollingOnCompositorBlockedOnMain))
+      .Times(1);
+
+  gesture_.SetType(WebInputEvent::Type::kGestureScrollBegin);
+  EXPECT_EQ(expected_disposition_,
+            HandleInputEventWithLatencyInfo(input_handler_.get(), gesture_));
+
+  VERIFY_AND_RESET_MOCKS();
+
+  EXPECT_THAT(
+      histogram_tester().GetAllSamples("Renderer4.MainThreadWheelScrollReason"),
+      testing::ElementsAre(base::Bucket(
+          GetBucketSample(cc::MainThreadScrollingReason::kFailedHitTest), 1)));
+}
+
+TEST_P(InputHandlerProxyMainThreadScrollingReasonTest,
+       ImplHandled_MainThreadRepaint) {
+  if (!base::FeatureList::IsEnabled(features::kScrollUnification))
+    return;
+
+  expected_disposition_ = InputHandlerProxy::DID_HANDLE;
+  VERIFY_AND_RESET_MOCKS();
+
+  cc::InputHandler::ScrollStatus scroll_status = kImplThreadScrollState;
+  scroll_status.needs_main_thread_repaint = true;
+
+  EXPECT_CALL(mock_input_handler_, ScrollBegin(_, _))
+      .WillOnce(testing::Return(scroll_status));
+  EXPECT_CALL(
+      mock_input_handler_,
+      RecordScrollBegin(_, cc::ScrollBeginThreadState::kScrollingOnMain))
+      .Times(1);
+
+  gesture_.SetType(WebInputEvent::Type::kGestureScrollBegin);
+  EXPECT_EQ(expected_disposition_,
+            HandleInputEventWithLatencyInfo(input_handler_.get(), gesture_));
+
+  VERIFY_AND_RESET_MOCKS();
+
+  EXPECT_THAT(
+      histogram_tester().GetAllSamples("Renderer4.MainThreadWheelScrollReason"),
+      testing::ElementsAre(base::Bucket(
+          GetBucketSample(cc::MainThreadScrollingReason::kNoScrollingLayer),
+          1)));
+}
+
 TEST_P(InputHandlerProxyMainThreadScrollingReasonTest, WheelScrollHistogram) {
   // Firstly check if input handler can correctly record main thread scrolling
   // reasons.
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 0e08c8596..42eb496 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -6098,7 +6098,6 @@
 # Temporarily disabled to unblock https://crrev.com/c/2979697
 crbug.com/1222114 http/tests/devtools/console/console-dir.js [ Failure Pass ]
 crbug.com/1222114 http/tests/devtools/console/console-format.js [ Failure Pass ]
-crbug.com/1222114 http/tests/devtools/console/console-functions.js [ Failure Pass ]
 
 crbug.com/1247844 external/wpt/css/css-contain/content-visibility/content-visibility-input-image.html [ Timeout ]
 
@@ -6649,7 +6648,6 @@
 crbug.com/1222097 http/tests/canvas/captureStream-on-detached-canvas.html [ Skip ]
 crbug.com/1222097 virtual/plz-dedicated-worker/external/wpt/html/cross-origin-embedder-policy/blob.https.html [ Skip ]
 crbug.com/1222097 virtual/plz-dedicated-worker/external/wpt/html/cross-origin-embedder-policy/worker-inheritance.sub.https.html [ Skip ]
-crbug.com/1222097 virtual/shared_array_buffer_on_desktop/http/tests/devtools/security/interstitial-sidebar.js [ Skip ]
 crbug.com/1222097 virtual/wasm-site-isolated-code-cache/http/tests/devtools/wasm-isolated-code-cache/wasm-cache-test.js [ Skip ]
 crbug.com/1222097 external/wpt/uievents/order-of-events/mouse-events/mouseover-out.html [ Skip ]
 
@@ -6844,7 +6842,6 @@
 crbug.com/1243933 [ Win7 ] virtual/shared_array_buffer_on_desktop/http/tests/devtools/elements/accessibility/edit-aria-attributes.js [ Failure ]
 
 # Sheriff 2021-09-03
-crbug.com/1246238 http/tests/devtools/security/interstitial-sidebar.js [ Failure Pass ]
 crbug.com/1246351 http/tests/misc/scroll-cross-origin-iframes-scrollbar.html [ Failure Pass Timeout ]
 
 # Sheriff 2021-09-06
@@ -7277,12 +7274,14 @@
 crbug.com/1267736 [ Win ] http/tests/devtools/bindings/jssourcemap-bindings-overlapping-sources.js [ Failure Pass ]
 
 # Sheriff 2021-11-09
-crbug.com/1268265 [ Mac10.14 ] virtual/prerender/external/wpt/speculation-rules/prerender/local-storage.html [ Failure ]
 crbug.com/1268518 [ Linux ] external/wpt/web-animations/interfaces/Animation/onfinish.html [ Failure Pass ]
 crbug.com/1268518 [ Mac10.12 ] external/wpt/web-animations/interfaces/Animation/onfinish.html [ Failure Pass ]
 crbug.com/1268519 [ Win10.20h2 ] virtual/scroll-unification/http/tests/misc/resource-timing-sizes-multipart.html [ Failure Pass ]
 crbug.com/1267734 [ Win7 ] virtual/scroll-unification/fast/forms/suggestion-picker/week-suggestion-picker-appearance.html [ Failure Pass ]
 
+# This test is flaky on all platforms
+crbug.com/1191846 virtual/prerender/external/wpt/speculation-rules/prerender/local-storage.html [ Failure Pass ]
+
 # Sheriff 2021-11-10
 crbug.com/1268963 [ Linux ] virtual/plz-dedicated-worker/external/wpt/resource-timing/object-not-found-after-TAO-cross-origin-redirect.html [ Failure Pass ]
 
diff --git a/third_party/blink/web_tests/http/tests/cookies/partitioned-cookies/ancestor-chain.https.html b/third_party/blink/web_tests/http/tests/cookies/partitioned-cookies/ancestor-chain.https.html
new file mode 100644
index 0000000..6330b82c
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/cookies/partitioned-cookies/ancestor-chain.https.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<head>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<meta name="help" href="https://github.com/WICG/CHIPS#chips-cookies-having-independent-partitioned-state">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/testharness-helpers.js"></script>
+<title>Test partitioned cookies do not consider ancestor chain</title>
+</head>
+<body>
+<script>
+
+document.body.onload = function main() {
+  if (window.location.hostname != ORIGINAL_HOST) {
+    window.location.hostname = ORIGINAL_HOST;
+  }
+
+  test(() => {
+    assert_equals(document.cookie, "");
+
+    // Valid Partitioned cookie: __Host- prefix and no SameParty attribute.
+    const sameSiteCookie = "__Host-same=site";
+    // Include Partitioned attribute to make sure we don't introduce a site for
+    // cookies regression.
+    const sameSiteCookieAttributes =
+        "; Secure; Path=/; SameSite=Lax; Partitioned";
+    const sameSiteCookieLine = sameSiteCookie + sameSiteCookieAttributes;
+    document.cookie = sameSiteCookieLine;
+
+    const partitionedCookie = "__Host-foo=bar";
+    const partitionedCookieAttributes =
+        "; Secure; Path=/; SameSite=None; Partitioned";
+    const partitionedCookieLine =
+        partitionedCookie + partitionedCookieAttributes;
+    document.cookie = partitionedCookieLine;
+
+    assert_true(document.cookie.includes(sameSiteCookie));
+    assert_true(document.cookie.includes(partitionedCookie));
+
+    const iframe = document.createElement("iframe");
+    const url = new URL(
+      "/cookies/partitioned-cookies/resources/" +
+          "ancestor-chain-cross-site-embed.html",
+      `https://${TEST_HOST}:${window.location.port}`);
+    iframe.src = url.href;
+    document.body.appendChild(iframe);
+    fetch_tests_from_window(iframe.contentWindow);
+  }, "Setting SameSite and Partitioned cookies");
+};
+
+</script>
+</body>
diff --git a/third_party/blink/web_tests/http/tests/cookies/partitioned-cookies/resources/ancestor-chain-cross-site-embed.html b/third_party/blink/web_tests/http/tests/cookies/partitioned-cookies/resources/ancestor-chain-cross-site-embed.html
new file mode 100644
index 0000000..499ac01
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/cookies/partitioned-cookies/resources/ancestor-chain-cross-site-embed.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<head>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/cookies/resources/testharness-helpers.js"></script>
+<title>Test partitioned cookies ancestor chain: cross site embed</title>
+</head>
+<body>
+<script>
+
+test(() => {
+  const iframe = document.createElement("iframe");
+  const url = new URL(
+    "/cookies/partitioned-cookies/resources/" +
+        "ancestor-chain-same-site-embed.html",
+    `https://${ORIGINAL_HOST}:${window.location.port}`);
+  iframe.src = url.href;
+  document.body.appendChild(iframe);
+  fetch_tests_from_window(iframe.contentWindow);
+}, "Cross-site embed opened");
+
+</script>
+</body>
diff --git a/third_party/blink/web_tests/http/tests/cookies/partitioned-cookies/resources/ancestor-chain-same-site-embed.html b/third_party/blink/web_tests/http/tests/cookies/partitioned-cookies/resources/ancestor-chain-same-site-embed.html
new file mode 100644
index 0000000..950c38e
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/cookies/partitioned-cookies/resources/ancestor-chain-same-site-embed.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<head>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/cookies/resources/testharness-helpers.js"></script>
+<title>Test partitioned cookies ancestor chain: same site embed</title>
+</head>
+<body>
+<script>
+
+test(() => {
+  assert_false(document.cookie.includes("__Host-same=site"));
+  assert_true(document.cookie.includes("__Host-foo=bar"));
+}, "Same-site embed partitioned cookie access");
+
+</script>
+</body>
diff --git a/third_party/blink/web_tests/http/tests/devtools/console/console-functions-expected.txt b/third_party/blink/web_tests/http/tests/devtools/console/console-functions-expected.txt
index fb170c38..49c3d1f 100644
--- a/third_party/blink/web_tests/http/tests/devtools/console/console-functions-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/console/console-functions-expected.txt
@@ -14,6 +14,8 @@
 console-functions.js:28 async ƒ asyncSimple()
     length: 0
     name: "asyncSimple"
+    arguments: (...)
+    caller: (...)
     [[FunctionLocation]]: console-functions.js:14
     [[Prototype]]: AsyncFunction
     [[Scopes]]: Scopes[1]
@@ -22,6 +24,8 @@
     length: 0
     name: "genSimple"
     prototype: Generator {}
+    arguments: (...)
+    caller: (...)
     [[FunctionLocation]]: console-functions.js:15
     [[IsGenerator]]: true
     [[Prototype]]: GeneratorFunction
@@ -81,6 +85,8 @@
     length: 1
     name: "whitespace"
     prototype: Generator {}
+    arguments: (...)
+    caller: (...)
     [[FunctionLocation]]: console-functions.js:21
     [[IsGenerator]]: true
     [[Prototype]]: GeneratorFunction
@@ -89,6 +95,8 @@
 console-functions.js:28 async ƒ whitespace2(  x  ,  y  ,  z  )
     length: 3
     name: "whitespace2"
+    arguments: (...)
+    caller: (...)
     [[FunctionLocation]]: console-functions.js:22
     [[Prototype]]: AsyncFunction
     [[Scopes]]: Scopes[1]
diff --git a/third_party/blink/web_tests/http/tests/devtools/security/interstitial-sidebar-expected.txt b/third_party/blink/web_tests/http/tests/devtools/security/interstitial-sidebar-expected.txt
index a696992..370e5b48 100644
--- a/third_party/blink/web_tests/http/tests/devtools/security/interstitial-sidebar-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/security/interstitial-sidebar-expected.txt
@@ -13,8 +13,6 @@
         </STYLE>
         <STYLE >
         </STYLE>
-        <STYLE >
-        </STYLE>
         <DIV class=tree-outline-disclosure >
             <OL class=tree-outline role=tree tabindex=-1 >
                 <LI role=treeitem class=security-main-view-sidebar-tree-item selected force-white-icons title=Overview tabindex=0 aria-selected=true >
@@ -134,8 +132,6 @@
         </STYLE>
         <STYLE >
         </STYLE>
-        <STYLE >
-        </STYLE>
         <DIV class=tree-outline-disclosure >
             <OL class=tree-outline role=tree tabindex=-1 >
                 <LI role=treeitem class=security-main-view-sidebar-tree-item selected force-white-icons title=Overview tabindex=0 aria-selected=true >
@@ -255,8 +251,6 @@
         </STYLE>
         <STYLE >
         </STYLE>
-        <STYLE >
-        </STYLE>
         <DIV class=tree-outline-disclosure >
             <OL class=tree-outline role=tree tabindex=-1 >
                 <LI role=treeitem class=security-main-view-sidebar-tree-item selected force-white-icons title=Overview tabindex=0 aria-selected=true >
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/fenced-frame/fenced-frame-navigation-request-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/fenced-frame/fenced-frame-navigation-request-expected.txt
new file mode 100644
index 0000000..9e82d78
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/fenced-frame/fenced-frame-navigation-request-expected.txt
@@ -0,0 +1,5 @@
+Tests that network events for a newly created fenced frame can be observed after auto-attach.
+FF navigation request sent for url: http://127.0.0.1:8000/inspector-protocol/fenced-frame/resources/page-with-title.php
+Response received for FF navigation request: 200
+Loading finished for FF.
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/fenced-frame/fenced-frame-navigation-request.js b/third_party/blink/web_tests/http/tests/inspector-protocol/fenced-frame/fenced-frame-navigation-request.js
new file mode 100644
index 0000000..6e48f6bf
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/fenced-frame/fenced-frame-navigation-request.js
@@ -0,0 +1,40 @@
+(async function (testRunner) {
+  const { session, dp } = await testRunner.startURL('../resources/empty.html',
+    'Tests that network events for a newly created fenced frame can be observed after auto-attach.');
+
+  dp.Target.setAutoAttach({ autoAttach: true, waitForDebuggerOnStart: true, flatten: true });
+  session.evaluate(function() {
+    let ff = document.createElement('fencedframe');
+    ff.src = '../fenced-frame/resources/page-with-title.php';
+    document.body.appendChild(ff);
+  });
+
+  let { targetInfo, sessionId } = (await dp.Target.onceAttachedToTarget()).params;
+  let ff_session = session.createChild(sessionId);
+  let ff_dp = ff_session.protocol;
+  ff_dp.Network.enable();
+  ff_dp.Runtime.runIfWaitingForDebugger();
+
+  const requestWillBeSentPromise = ff_dp.Network.onceRequestWillBeSent();
+  const responseReceivedPromise = ff_dp.Network.onceResponseReceived();
+  const loadingFinishedPromise = ff_dp.Network.onceLoadingFinished();
+
+  const requestWillBeSentParams = (await requestWillBeSentPromise).params;
+  let requestId = undefined;
+  if (requestWillBeSentParams.frameId === targetInfo.targetId) {
+    testRunner.log('FF navigation request sent for url: ' + requestWillBeSentParams.documentURL);
+    requestId = requestWillBeSentParams.requestId;
+  }
+
+  const responseReceivedParams = (await responseReceivedPromise).params;
+  if (responseReceivedParams.requestId == requestId) {
+    testRunner.log('Response received for FF navigation request: ' + responseReceivedParams.response.status);
+  }
+
+  const loadingFinishedParams = (await loadingFinishedPromise).params;
+  if (loadingFinishedParams.requestId == requestId) {
+    testRunner.log('Loading finished for FF.');
+  }
+
+  testRunner.completeTest();
+});
diff --git a/third_party/blink/web_tests/http/tests/security/mixedContent/empty-url-plugin-in-frame-expected.txt b/third_party/blink/web_tests/http/tests/security/mixedContent/empty-url-plugin-in-frame-expected.txt
deleted file mode 100644
index f81fac1..0000000
--- a/third_party/blink/web_tests/http/tests/security/mixedContent/empty-url-plugin-in-frame-expected.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-CONSOLE MESSAGE: Blink Test Plugin: initializing
-This test loads a secure iframe that loads a plugin without a URL. We should *not* get a mixed content callback because the plugin cannot be controlled by an active network attacker.
diff --git a/third_party/blink/web_tests/http/tests/security/mixedContent/empty-url-plugin-in-frame.html b/third_party/blink/web_tests/http/tests/security/mixedContent/empty-url-plugin-in-frame.html
deleted file mode 100644
index 512d697..0000000
--- a/third_party/blink/web_tests/http/tests/security/mixedContent/empty-url-plugin-in-frame.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<html>
-<body>
-<script>
-if (window.testRunner) {
-    testRunner.dumpAsText();
-    testRunner.waitUntilDone();
-    window.addEventListener("message", _ => {
-      testRunner.notifyDone();
-    });
-}
-</script>
-<p>This test loads a secure iframe that loads a plugin without a URL.  We should
-*not* get a mixed content callback because the plugin cannot be controlled by
-an active network attacker.</p>
-<iframe src="https://127.0.0.1:8443/security/mixedContent/resources/frame-with-empty-url-plugin.html"></iframe>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/http/tests/security/mixedContent/resources/frame-with-empty-url-plugin.html b/third_party/blink/web_tests/http/tests/security/mixedContent/resources/frame-with-empty-url-plugin.html
deleted file mode 100644
index 30e77c58..0000000
--- a/third_party/blink/web_tests/http/tests/security/mixedContent/resources/frame-with-empty-url-plugin.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<script>
-window.onload = function() {
-  if (window.internals)
-    internals.updateLayoutAndRunPostLayoutTasks();
-  if (window.opener)
-    window.opener.postMessage('done', '*');
-  if (window.parent)
-    window.parent.postMessage('done', '*');
-}
-</script>
-<object name='plugin' type='application/x-blink-test-plugin'></object>
diff --git a/third_party/blink/web_tests/platform/mac-mac10.14/virtual/prerender/external/wpt/speculation-rules/prerender/local-storage-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.14/virtual/prerender/external/wpt/speculation-rules/prerender/local-storage-expected.txt
deleted file mode 100644
index f34d0662..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.14/virtual/prerender/external/wpt/speculation-rules/prerender/local-storage-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL prerendering page should be able to access local storage assert_equals: prerendering page should be able to write to local storage expected (string) "prerender_set" but got (object) null
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/serviceWorker-frameType-inner.html b/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/serviceWorker-frameType-inner.html
new file mode 100644
index 0000000..ae423ca
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/serviceWorker-frameType-inner.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<body>
+<script>
+
+function getFrameType(service_worker, url) {
+  return new Promise((resolve, reject) => {
+    const channel = new MessageChannel();
+    channel.port1.onmessage = e => {
+      resolve(e.data);
+    };
+    service_worker.postMessage({port:channel.port2, url:url},
+                               [channel.port2]);
+  });
+}
+
+(async function() {
+  await navigator.serviceWorker.register('serviceWorker-frameType.js');
+  const registration = await navigator.serviceWorker.ready;
+  const service_worker = registration.active;
+
+  const frame_type_key = KEYS['serviceWorker.frameType'];
+  const frame_type_ack_key = KEYS['serviceWorker.frameType ACK'];
+
+  const frame_type = await getFrameType(service_worker, location.href);
+  writeValueToServer(frame_type_key, frame_type);
+
+  // Wait for ACK, so we know that the outer page has read the last value from
+  // the `serviceWorker.frameType` stash and we can write to it again.
+  await nextValueFromServer(frame_type_ack_key);
+
+  const iframe = document.createElement('iframe');
+  iframe.src = "serviceWorker-frameType-nested.html";
+  document.body.append(iframe);
+})();
+</script>
+</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/serviceWorker-frameType-inner.html.headers b/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/serviceWorker-frameType-inner.html.headers
new file mode 100644
index 0000000..6247f6d
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/serviceWorker-frameType-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/serviceWorker-frameType-nested.html b/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/serviceWorker-frameType-nested.html
new file mode 100644
index 0000000..3962dfa
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/serviceWorker-frameType-nested.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<body>
+<script>
+
+function getFrameType(service_worker, url) {
+  return new Promise((resolve, reject) => {
+    const channel = new MessageChannel();
+    channel.port1.onmessage = e => {
+      resolve(e.data);
+    };
+    service_worker.postMessage({port:channel.port2, url:url},
+                               [channel.port2]);
+  });
+}
+
+(async function() {
+  const service_worker = navigator.serviceWorker.controller;
+  const frame_type = await getFrameType(service_worker, location.href);
+
+  const frame_type_key = KEYS['serviceWorker.frameType'];
+  writeValueToServer(frame_type_key, frame_type);
+})();
+</script>
+</body>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/serviceWorker-frameType.js b/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/serviceWorker-frameType.js
new file mode 100644
index 0000000..91003fc1
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/serviceWorker-frameType.js
@@ -0,0 +1,19 @@
+self.onmessage = function(e) {
+  var port = e.data.port;
+  var url = e.data.url;
+
+  e.waitUntil(self.clients.matchAll({includeUncontrolled: true})
+    .then(function(clients) {
+        var frame_type = "none";
+        for (client of clients) {
+          if (client.url === url) {
+            frame_type = client.frameType;
+            break;
+          }
+        }
+        port.postMessage(frame_type);
+      })
+    .catch(e => {
+        port.postMessage('clients.matchAll() rejected: ' + e);
+      }));
+};
\ No newline at end of file
diff --git a/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/utils.js b/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/utils.js
index 674f221..4da27d1 100644
--- a/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/utils.js
+++ b/third_party/blink/web_tests/wpt_internal/fenced_frame/resources/utils.js
@@ -57,6 +57,9 @@
   "keyboard.getLayoutMap"                       : "00000000-0000-0000-0000-00000000001A",
 
   "permission.notification"                     : "00000000-0000-0000-0000-00000000001B",
+
+  "serviceWorker.frameType"                     : "00000000-0000-0000-0000-00000000001C",
+  "serviceWorker.frameType ACK"                 : "00000000-0000-0000-0000-00000000001D",
   // Add keys above this list, incrementing the key UUID in hexadecimal
 }
 
diff --git a/third_party/blink/web_tests/wpt_internal/fenced_frame/serviceWorker-frameType.https.html b/third_party/blink/web_tests/wpt_internal/fenced_frame/serviceWorker-frameType.https.html
new file mode 100644
index 0000000..f30adcd
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/fenced_frame/serviceWorker-frameType.https.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>Service Worker: Clients.matchAll with includeUncontrolled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<body>
+<script>
+
+promise_test(async t => {
+  const frame_type_key = KEYS['serviceWorker.frameType'];
+  const frame_type_ack_key = KEYS['serviceWorker.frameType ACK'];
+
+  attachFencedFrame('resources/serviceWorker-frameType-inner.html');
+
+  const frame_type_result = await nextValueFromServer(frame_type_key);
+  assert_equals(frame_type_result, "top-level",
+                "The service worker for the top-level fenced frame has the " +
+                "right value for `serviceWorker.frameType`");
+
+  // Write an ACK, so that the fenced frame knows it can send message over the
+  // `serviceWorker.frameType` channel again.
+  writeValueToServer(frame_type_ack_key, "ACK");
+
+  const nested_frame_type_result = await nextValueFromServer(frame_type_key);
+  assert_equals(nested_frame_type_result, "nested",
+                "The service worker for the iframe inside the top-level " +
+                "fenced frame has the right value for `serviceWorker.frameType`");
+}, 'serviceWorker.frameType');
+
+</script>
+</body>
\ No newline at end of file
diff --git a/third_party/nearby/DIR_METADATA b/third_party/nearby/DIR_METADATA
index 135a13c..3d76e46 100644
--- a/third_party/nearby/DIR_METADATA
+++ b/third_party/nearby/DIR_METADATA
@@ -1,3 +1,3 @@
 monorail: {
-  component: "UI>Browser>Sharing>Nearby"
+  component: "OS>Systems>Multidevice>Nearby"
 }
diff --git a/third_party/securemessage/DIR_METADATA b/third_party/securemessage/DIR_METADATA
index 135a13c..3d76e46 100644
--- a/third_party/securemessage/DIR_METADATA
+++ b/third_party/securemessage/DIR_METADATA
@@ -1,3 +1,3 @@
 monorail: {
-  component: "UI>Browser>Sharing>Nearby"
+  component: "OS>Systems>Multidevice>Nearby"
 }
diff --git a/third_party/tflite/README.chromium b/third_party/tflite/README.chromium
index 2d470940..5861662 100644
--- a/third_party/tflite/README.chromium
+++ b/third_party/tflite/README.chromium
@@ -1,8 +1,8 @@
 Name: TensorFlow Lite
 Short Name: tflite
 URL: https://github.com/tensorflow/tensorflow
-Version: 1500fdacf05723138b55cc7a81149cdb0e780d46
-Date: 2021/11/10
+Version: 95c0ea4aaefbad28d5b7d976250bca06c006943a
+Date: 2021/11/16
 License: Apache 2.0
 License File: LICENSE
 Security Critical: Yes
diff --git a/third_party/ukey2/DIR_METADATA b/third_party/ukey2/DIR_METADATA
index 135a13c..3d76e46 100644
--- a/third_party/ukey2/DIR_METADATA
+++ b/third_party/ukey2/DIR_METADATA
@@ -1,3 +1,3 @@
 monorail: {
-  component: "UI>Browser>Sharing>Nearby"
+  component: "OS>Systems>Multidevice>Nearby"
 }
diff --git a/tools/check_grd_for_unused_strings.py b/tools/check_grd_for_unused_strings.py
index d11c3e1..3d9091e 100755
--- a/tools/check_grd_for_unused_strings.py
+++ b/tools/check_grd_for_unused_strings.py
@@ -139,32 +139,35 @@
     ui_strings_dir = os.path.join(ui_dir, 'strings')
     ui_chromeos_dir = os.path.join(ui_dir, 'chromeos')
     grd_files = [
-      os.path.join(ash_base_dir, 'ash_strings.grd'),
-      os.path.join(ash_shortcut_viewer_dir, 'shortcut_viewer_strings.grd'),
-      os.path.join(chrome_app_dir, 'chromium_strings.grd'),
-      os.path.join(chrome_app_dir, 'generated_resources.grd'),
-      os.path.join(chrome_app_dir, 'google_chrome_strings.grd'),
-      os.path.join(chrome_app_res_dir, 'locale_settings.grd'),
-      os.path.join(chrome_app_res_dir, 'locale_settings_chromiumos.grd'),
-      os.path.join(chrome_app_res_dir, 'locale_settings_google_chromeos.grd'),
-      os.path.join(chrome_app_res_dir, 'locale_settings_linux.grd'),
-      os.path.join(chrome_app_res_dir, 'locale_settings_mac.grd'),
-      os.path.join(chrome_app_res_dir, 'locale_settings_win.grd'),
-      os.path.join(chrome_app_dir, 'theme', 'theme_resources.grd'),
-      os.path.join(chrome_dir, 'browser', 'browser_resources.grd'),
-      os.path.join(chrome_dir, 'common', 'common_resources.grd'),
-      os.path.join(chrome_dir, 'renderer', 'resources',
-                   'renderer_resources.grd'),
-      os.path.join(device_base_dir, 'bluetooth', 'bluetooth_strings.grd'),
-      os.path.join(device_base_dir, 'fido', 'fido_strings.grd'),
-      os.path.join(services_dir, 'services_strings.grd'),
-      os.path.join(src_dir, 'chromeos', 'chromeos_strings.grd'),
-      os.path.join(src_dir, 'extensions', 'strings', 'extensions_strings.grd'),
-      os.path.join(src_dir, 'ui', 'resources', 'ui_resources.grd'),
-      os.path.join(src_dir, 'ui', 'webui', 'resources', 'webui_resources.grd'),
-      os.path.join(ui_strings_dir, 'app_locale_settings.grd'),
-      os.path.join(ui_strings_dir, 'ui_strings.grd'),
-      os.path.join(ui_chromeos_dir, 'ui_chromeos_strings.grd'),
+        os.path.join(ash_base_dir, 'ash_strings.grd'),
+        os.path.join(ash_shortcut_viewer_dir, 'shortcut_viewer_strings.grd'),
+        os.path.join(chrome_app_dir, 'chromium_strings.grd'),
+        os.path.join(chrome_app_dir, 'generated_resources.grd'),
+        os.path.join(chrome_app_dir, 'google_chrome_strings.grd'),
+        os.path.join(chrome_app_res_dir, 'locale_settings.grd'),
+        os.path.join(chrome_app_res_dir, 'locale_settings_chromiumos.grd'),
+        os.path.join(chrome_app_res_dir, 'locale_settings_google_chromeos.grd'),
+        os.path.join(chrome_app_res_dir, 'locale_settings_linux.grd'),
+        os.path.join(chrome_app_res_dir, 'locale_settings_mac.grd'),
+        os.path.join(chrome_app_res_dir, 'locale_settings_win.grd'),
+        os.path.join(chrome_app_dir, 'theme', 'theme_resources.grd'),
+        os.path.join(chrome_dir, 'browser', 'browser_resources.grd'),
+        os.path.join(chrome_dir, 'common', 'common_resources.grd'),
+        os.path.join(chrome_dir, 'renderer', 'resources',
+                     'renderer_resources.grd'),
+        os.path.join(device_base_dir, 'bluetooth', 'bluetooth_strings.grd'),
+        os.path.join(device_base_dir, 'fido', 'fido_strings.grd'),
+        os.path.join(services_dir, 'services_strings.grd'),
+        os.path.join(src_dir, 'chromeos', 'chromeos_strings.grd'),
+        os.path.join(src_dir, 'extensions', 'strings',
+                     'extensions_strings.grd'),
+        os.path.join(src_dir, 'ui', 'resources', 'ui_resources.grd'),
+        os.path.join(src_dir, 'ui', 'webui', 'resources',
+                     'webui_resources.grd'),
+        os.path.join(ui_strings_dir, 'app_locale_settings.grd'),
+        os.path.join(ui_strings_dir, 'ax_strings.grd'),
+        os.path.join(ui_strings_dir, 'ui_strings.grd'),
+        os.path.join(ui_chromeos_dir, 'ui_chromeos_strings.grd'),
     ]
 
   # If no source directories were given, default them:
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index e637420b..ba425cc 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -857,9 +857,12 @@
   "ui/strings/app_locale_settings.grd": {
     "messages": [4780],
   },
-  "ui/strings/ui_strings.grd": {
+  "ui/strings/ax_strings.grd": {
     "messages": [4800],
   },
+  "ui/strings/ui_strings.grd": {
+    "messages": [4810],
+  },
   "ui/views/examples/views_examples_resources.grd": {
     "messages": [4820],
   },
diff --git a/tools/gritsettings/translation_expectations.pyl b/tools/gritsettings/translation_expectations.pyl
index 291b302a..93801b9 100644
--- a/tools/gritsettings/translation_expectations.pyl
+++ b/tools/gritsettings/translation_expectations.pyl
@@ -74,6 +74,7 @@
       "ui/accessibility/extensions/strings/accessibility_extensions_strings.grd",
       "ui/android/java/strings/android_ui_strings.grd",
       "ui/chromeos/ui_chromeos_strings.grd",
+      "ui/strings/ax_strings.grd",
       "ui/strings/ui_strings.grd",
       "weblayer/browser/java/weblayer_strings.grd",
     ],
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index b231af3b..09ebcae 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -27127,6 +27127,7 @@
   <int value="926" label="HistoryClustersVisible"/>
   <int value="927" label="ChromadToCloudMigrationEnabled"/>
   <int value="928" label="CopyPreventionSettings"/>
+  <int value="929" label="UserAgentReduction"/>
 </enum>
 
 <enum name="EnterprisePolicyDeviceIdValidity">
@@ -32481,10 +32482,12 @@
   <int value="1243" label="WebAnimationHyphenatedProperty"/>
   <int value="1244"
       label="FormControlsCollectionReturnsRadioNodeListForFieldSet"/>
-  <int value="1245" label="ApplicationCacheManifestSelectInsecureOrigin"/>
-  <int value="1246" label="ApplicationCacheManifestSelectSecureOrigin"/>
-  <int value="1247" label="ApplicationCacheAPIInsecureOrigin"/>
-  <int value="1248" label="ApplicationCacheAPISecureOrigin"/>
+  <int value="1245"
+      label="OBSOLETE_ApplicationCacheManifestSelectInsecureOrigin"/>
+  <int value="1246"
+      label="OBSOLETE_ApplicationCacheManifestSelectSecureOrigin"/>
+  <int value="1247" label="OBSOLETE_ApplicationCacheAPIInsecureOrigin"/>
+  <int value="1248" label="OBSOLETE_ApplicationCacheAPISecureOrigin"/>
   <int value="1249" label="CSSAtRuleApply"/>
   <int value="1250" label="CSSSelectorPseudoAny"/>
   <int value="1251" label="PannerNodeSetVelocity"/>
@@ -34080,7 +34083,7 @@
   <int value="2769" label="HTMLTemplateElement"/>
   <int value="2770" label="NoSysexWebMIDIWithoutPermission"/>
   <int value="2771" label="NoSysexWebMIDIOnInsecureOrigin"/>
-  <int value="2772" label="ApplicationCacheInstalledButNoManifest"/>
+  <int value="2772" label="OBSOLETE_ApplicationCacheInstalledButNoManifest"/>
   <int value="2773" label="PerMethodCanMakePaymentQuota"/>
   <int value="2774"
       label="OBSOLETE_CSSValueAppearanceButtonForNonButtonRendered"/>
@@ -50257,6 +50260,7 @@
   <int value="-875217114"
       label="OmniboxEnableClipboardProviderTextSuggestions:enabled"/>
   <int value="-874602599" label="HorizontalTabSwitcherAndroid:enabled"/>
+  <int value="-873334439" label="PersistShareHubOnAppSwitch:disabled"/>
   <int value="-872764392" label="ContextualSuggestionsBottomSheet:disabled"/>
   <int value="-871784177" label="DeprecateMenagerieAPI:enabled"/>
   <int value="-871520682"
@@ -51045,6 +51049,7 @@
   <int value="-265697837" label="PhoneHub:disabled"/>
   <int value="-263645996" label="InteractiveWindowCycleList:enabled"/>
   <int value="-263150202" label="BundledConnectionHelp:disabled"/>
+  <int value="-262368430" label="PersistShareHubOnAppSwitch:enabled"/>
   <int value="-262122630" label="ArcEnableDocumentsProviderInFilesApp:enabled"/>
   <int value="-261398170" label="ChromeLabs:enabled"/>
   <int value="-258081634" label="AutofillAssistantDirectActions:disabled"/>
@@ -63674,11 +63679,10 @@
   <int value="0" label="Unknown"/>
   <int value="1" label="Success"/>
   <int value="2" label="Pending"/>
-  <int value="3" label="ErrorInternalError"/>
-  <int value="4" label="ErrorModelFileNotAvailable"/>
-  <int value="5" label="ErrorModelFileNotValid"/>
-  <int value="6" label="ErrorEmptyOrInvalidInput"/>
-  <int value="7" label="ErrorUnknown"/>
+  <int value="3" label="ErrorModelFileNotAvailable"/>
+  <int value="4" label="ErrorModelFileNotValid"/>
+  <int value="5" label="ErrorEmptyOrInvalidInput"/>
+  <int value="6" label="ErrorUnknown"/>
 </enum>
 
 <enum name="OptimizationGuideHintCacheLevelDBStoreLoadMetadataResult">
diff --git a/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS b/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
index 99c05d5e..3b317ce 100644
--- a/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
+++ b/tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
@@ -53,6 +53,7 @@
 ravjit@chromium.org
 rayankans@chromium.org
 reillyg@chromium.org
+rouslan@chromium.org
 rsorokin@chromium.org
 rushans@google.com
 schenney@chromium.org
diff --git a/tools/metrics/histograms/metadata/payment/OWNERS b/tools/metrics/histograms/metadata/payment/OWNERS
new file mode 100644
index 0000000..c39c5ef6
--- /dev/null
+++ b/tools/metrics/histograms/metadata/payment/OWNERS
@@ -0,0 +1,5 @@
+per-file OWNERS=file://tools/metrics/histograms/metadata/METRIC_REVIEWER_OWNERS
+
+# Prefer sending CLs to the owners listed below.
+# Use chromium-metrics-reviews@google.com as a backup.
+rouslan@chromium.org
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 7e920b4..8e41502 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,20 +5,20 @@
             "remote_path": "perfetto_binaries/trace_processor_shell/linux_arm/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "win": {
-            "hash": "d4f0284560a55059b62b4f1db159d97a162dfe6d",
-            "remote_path": "perfetto_binaries/trace_processor_shell/win/39f4b2769434c9abcceba70af7af5fa1890d76ef/trace_processor_shell.exe"
+            "hash": "2abe2291e0e9b51e8750059797f1284422cb27d8",
+            "remote_path": "perfetto_binaries/trace_processor_shell/win/406e7c3674a735f3fc4b751a90d21fd3d74f9511/trace_processor_shell.exe"
         },
         "mac": {
-            "hash": "a99dd2ca8cdd1fe467ae920b39574c69b11c8d01",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/8906f64b1e531db44ec6aa98f1211fced8fc4733/trace_processor_shell"
+            "hash": "0dda5c90a9ea600e8c3dd1276671b55a07260c4d",
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/406e7c3674a735f3fc4b751a90d21fd3d74f9511/trace_processor_shell"
         },
         "linux_arm64": {
             "hash": "5074025a2898ec41a872e70a5719e417acb0a380",
             "remote_path": "perfetto_binaries/trace_processor_shell/linux_arm64/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "linux": {
-            "hash": "30a9fee53dbeedf70f9b1b9abbf8f0207043121f",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/8906f64b1e531db44ec6aa98f1211fced8fc4733/trace_processor_shell"
+            "hash": "4cdd2006b2e87bbf78fe37b58a3e09d6392977e1",
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/406e7c3674a735f3fc4b751a90d21fd3d74f9511/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/ui/accessibility/DEPS b/ui/accessibility/DEPS
index 94aff0c..4d3d82b 100644
--- a/ui/accessibility/DEPS
+++ b/ui/accessibility/DEPS
@@ -11,7 +11,7 @@
   "+ui/base/win",
   "+ui/gfx",
   "+ui/display",
-  "+ui/strings/grit/ui_strings.h",
+  "+ui/strings/grit/ax_strings.h",
 
   # Don't allow dependencies from ui/accessibility on
   # ui/accessibility/platform.
diff --git a/ui/accessibility/ax_enum_util.cc b/ui/accessibility/ax_enum_util.cc
index 9bffdd9..b17a4c2 100644
--- a/ui/accessibility/ax_enum_util.cc
+++ b/ui/accessibility/ax_enum_util.cc
@@ -7,7 +7,7 @@
 #include "ui/accessibility/ax_enums.mojom.h"
 
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/strings/grit/ui_strings.h"
+#include "ui/strings/grit/ax_strings.h"
 
 namespace ui {
 
diff --git a/ui/accessibility/platform/ax_platform_node_cocoa.mm b/ui/accessibility/platform/ax_platform_node_cocoa.mm
index d69725a..90ceb73 100644
--- a/ui/accessibility/platform/ax_platform_node_cocoa.mm
+++ b/ui/accessibility/platform/ax_platform_node_cocoa.mm
@@ -15,7 +15,7 @@
 #include "ui/accessibility/platform/ax_private_attributes_mac.h"
 #include "ui/base/l10n/l10n_util.h"
 #import "ui/gfx/mac/coordinate_conversion.h"
-#include "ui/strings/grit/ui_strings.h"
+#include "ui/strings/grit/ax_strings.h"
 
 namespace ui {
 
@@ -415,6 +415,10 @@
 // Returns AXValue, or nil if AXValue isn't an NSString.
 - (NSString*)getAXValueAsString;
 
+// Returns this node's internal role, i.e. the one that is stored in
+// the internal accessibility tree as opposed to the platform tree.
+- (ax::mojom::Role)internalRole;
+
 // Returns the native wrapper for the given node id.
 - (AXPlatformNodeCocoa*)fromNodeID:(ui::AXNodeID)id;
 @end
@@ -482,6 +486,12 @@
   return [value isKindOfClass:[NSString class]] ? value : nil;
 }
 
+- (ax::mojom::Role)internalRole {
+  if ([self instanceActive])
+    return _node->GetRole();
+  return ax::mojom::Role::kUnknown;
+}
+
 - (AXPlatformNodeCocoa*)fromNodeID:(ui::AXNodeID)id {
   ui::AXPlatformNode* cell = _node->GetDelegate()->GetFromNodeID(id);
   if (cell)
@@ -962,22 +972,7 @@
 }
 
 - (NSString*)AXRoleDescription {
-  if (_node->HasStringAttribute(ax::mojom::StringAttribute::kRoleDescription)) {
-    return [base::SysUTF8ToNSString(_node->GetStringAttribute(
-        ax::mojom::StringAttribute::kRoleDescription)) lowercaseString];
-  }
-  switch (_node->GetRole()) {
-    case ax::mojom::Role::kTab:
-      // There is no NSAccessibilityTabRole or similar (AXRadioButton is used
-      // instead). Do the same as NSTabView and put "tab" in the description.
-      return [l10n_util::GetNSStringWithFixup(IDS_AX_ROLE_TAB) lowercaseString];
-    case ax::mojom::Role::kDisclosureTriangle:
-      return [l10n_util::GetNSStringWithFixup(IDS_AX_ROLE_DISCLOSURE_TRIANGLE)
-          lowercaseString];
-    default:
-      break;
-  }
-  return NSAccessibilityRoleDescription([self AXRole], [self AXSubrole]);
+  return [self accessibilityRoleDescription];
 }
 
 - (NSString*)AXSubrole {
@@ -1281,10 +1276,6 @@
   return [self AXSubrole];
 }
 
-- (NSString*)accessibilityRoleDescription {
-  return [self AXRoleDescription];
-}
-
 - (BOOL)isAccessibilitySelectorAllowed:(SEL)selector {
   TRACE_EVENT1(
       "accessibility", "AXPlatformNodeCocoa::isAccessibilitySelectorAllowed",
@@ -1477,7 +1468,135 @@
 }
 
 //
-// NSAccessibility protocol: configuring table and outline views
+// NSAccessibility protocol: assigning roles.
+//
+
+- (NSString*)accessibilityRoleDescription {
+  TRACE_EVENT1("accessibility", "accessibilityRoleDescription",
+               "role=", ui::ToString([self internalRole]));
+  if (![self instanceActive])
+    return nil;
+
+  // Image annotations.
+  if (_node->GetData().GetImageAnnotationStatus() ==
+          ax::mojom::ImageAnnotationStatus::kEligibleForAnnotation ||
+      _node->GetData().GetImageAnnotationStatus() ==
+          ax::mojom::ImageAnnotationStatus::kSilentlyEligibleForAnnotation) {
+    return base::SysUTF16ToNSString(
+        _node->GetDelegate()->GetLocalizedRoleDescriptionForUnlabeledImage());
+  }
+
+  // ARIA role description.
+  std::string roleDescription;
+  if (_node->GetStringAttribute(ax::mojom::StringAttribute::kRoleDescription,
+                                &roleDescription)) {
+    return [base::SysUTF8ToNSString(_node->GetStringAttribute(
+        ax::mojom::StringAttribute::kRoleDescription)) lowercaseString];
+  }
+
+  NSString* role = [self accessibilityRole];
+
+  // The following descriptions are specific to webkit.
+  if ([role isEqualToString:@"AXWebArea"]) {
+    return l10n_util::GetNSString(IDS_AX_ROLE_WEB_AREA);
+  }
+
+  if ([role isEqualToString:NSAccessibilityLinkRole]) {
+    return l10n_util::GetNSString(IDS_AX_ROLE_LINK);
+  }
+
+  if ([role isEqualToString:@"AXHeading"]) {
+    return l10n_util::GetNSString(IDS_AX_ROLE_HEADING);
+  }
+
+  if (([role isEqualToString:NSAccessibilityGroupRole] ||
+       [role isEqualToString:NSAccessibilityRadioButtonRole]) &&
+      !_node->GetDelegate()->IsWebAreaForPresentationalIframe()) {
+    std::string role_attribute;
+    if (_node->GetHtmlAttribute("role", &role_attribute)) {
+      ax::mojom::Role internalRole = _node->GetRole();
+      if ((internalRole != ax::mojom::Role::kBlockquote &&
+           internalRole != ax::mojom::Role::kCaption &&
+           internalRole != ax::mojom::Role::kGroup &&
+           internalRole != ax::mojom::Role::kListItem &&
+           internalRole != ax::mojom::Role::kMark &&
+           internalRole != ax::mojom::Role::kParagraph) ||
+          internalRole == ax::mojom::Role::kTab) {
+        // TODO(dtseng): This is not localized; see crbug/84814.
+        return base::SysUTF8ToNSString(role_attribute);
+      }
+    }
+  }
+
+  switch (_node->GetRole()) {
+    case ax::mojom::Role::kArticle:
+      return l10n_util::GetNSString(IDS_AX_ROLE_ARTICLE);
+    case ax::mojom::Role::kBanner:
+      return l10n_util::GetNSString(IDS_AX_ROLE_BANNER);
+    case ax::mojom::Role::kCheckBox:
+      return l10n_util::GetNSString(IDS_AX_ROLE_CHECK_BOX);
+    case ax::mojom::Role::kComment:
+      return l10n_util::GetNSString(IDS_AX_ROLE_COMMENT);
+    case ax::mojom::Role::kComplementary:
+      return l10n_util::GetNSString(IDS_AX_ROLE_COMPLEMENTARY);
+    case ax::mojom::Role::kContentInfo:
+      return l10n_util::GetNSString(IDS_AX_ROLE_CONTENT_INFO);
+    case ax::mojom::Role::kDescriptionList:
+      return l10n_util::GetNSString(IDS_AX_ROLE_DESCRIPTION_LIST);
+    case ax::mojom::Role::kDescriptionListDetail:
+      return l10n_util::GetNSString(IDS_AX_ROLE_DEFINITION);
+    case ax::mojom::Role::kDescriptionListTerm:
+      return l10n_util::GetNSString(IDS_AX_ROLE_DESCRIPTION_TERM);
+    case ax::mojom::Role::kDisclosureTriangle:
+      return l10n_util::GetNSString(IDS_AX_ROLE_DISCLOSURE_TRIANGLE);
+    case ax::mojom::Role::kFigure:
+      return l10n_util::GetNSString(IDS_AX_ROLE_FIGURE);
+    case ax::mojom::Role::kFooter:
+      return l10n_util::GetNSString(IDS_AX_ROLE_FOOTER);
+    case ax::mojom::Role::kForm:
+      return l10n_util::GetNSString(IDS_AX_ROLE_FORM);
+    case ax::mojom::Role::kHeader:
+      return l10n_util::GetNSString(IDS_AX_ROLE_BANNER);
+    case ax::mojom::Role::kMain:
+      return l10n_util::GetNSString(IDS_AX_ROLE_MAIN_CONTENT);
+    case ax::mojom::Role::kMark:
+      return l10n_util::GetNSString(IDS_AX_ROLE_MARK);
+    case ax::mojom::Role::kMath:
+    case ax::mojom::Role::kMathMLMath:
+      return l10n_util::GetNSString(IDS_AX_ROLE_MATH);
+    case ax::mojom::Role::kNavigation:
+      return l10n_util::GetNSString(IDS_AX_ROLE_NAVIGATIONAL_LINK);
+    case ax::mojom::Role::kRegion:
+      return l10n_util::GetNSString(IDS_AX_ROLE_REGION);
+    case ax::mojom::Role::kSpinButton:
+      // This control is similar to what VoiceOver calls a "stepper".
+      return l10n_util::GetNSString(IDS_AX_ROLE_STEPPER);
+    case ax::mojom::Role::kStatus:
+      return l10n_util::GetNSString(IDS_AX_ROLE_STATUS);
+    case ax::mojom::Role::kSearchBox:
+      return l10n_util::GetNSString(IDS_AX_ROLE_SEARCH_BOX);
+    case ax::mojom::Role::kSuggestion:
+      return l10n_util::GetNSString(IDS_AX_ROLE_SUGGESTION);
+    case ax::mojom::Role::kSwitch:
+      return l10n_util::GetNSString(IDS_AX_ROLE_SWITCH);
+    case ax::mojom::Role::kTab:
+      // There is no NSAccessibilityTabRole or similar (AXRadioButton is used
+      // instead). Do the same as NSTabView and put "tab" in the description.
+      return l10n_util::GetNSString(IDS_AX_ROLE_TAB);
+    case ax::mojom::Role::kTerm:
+      return l10n_util::GetNSString(IDS_AX_ROLE_DESCRIPTION_TERM);
+    case ax::mojom::Role::kToggleButton:
+      return l10n_util::GetNSString(IDS_AX_ROLE_TOGGLE_BUTTON);
+    default:
+      break;
+  }
+
+  return NSAccessibilityRoleDescription(role, [self accessibilitySubrole]);
+}
+
+//
+// NSAccessibility protocol: configuring table and outline views.
+//
 
 - (NSArray*)accessibilityColumnHeaderUIElements {
   if (![self instanceActive])
@@ -1680,7 +1799,7 @@
   bool foundBaseElement = false;
   AXPlatformNodeCocoa* subscript = nullptr;
   for (AXPlatformNodeCocoa* child in [self AXChildren]) {
-    if ([child node]->GetRole() == ax::mojom::Role::kMathMLPrescriptDelimiter)
+    if ([child internalRole] == ax::mojom::Role::kMathMLPrescriptDelimiter)
       break;
     if (!foundBaseElement) {
       foundBaseElement = true;
@@ -1706,8 +1825,8 @@
   AXPlatformNodeCocoa* subscript = nullptr;
   for (AXPlatformNodeCocoa* child in [self AXChildren]) {
     if (!foundPrescriptDelimiter) {
-      foundPrescriptDelimiter = ([child node]->GetRole() ==
-                                 ax::mojom::Role::kMathMLPrescriptDelimiter);
+      foundPrescriptDelimiter =
+          ([child internalRole] == ax::mojom::Role::kMathMLPrescriptDelimiter);
       continue;
     }
     if (!subscript) {
diff --git a/ui/aura/screen_ozone.cc b/ui/aura/screen_ozone.cc
index 6020a195..95ec97e3 100644
--- a/ui/aura/screen_ozone.cc
+++ b/ui/aura/screen_ozone.cc
@@ -97,8 +97,8 @@
   return platform_screen_->GetPrimaryDisplay();
 }
 
-void ScreenOzone::SetScreenSaverSuspended(bool suspend) {
-  platform_screen_->SetScreenSaverSuspended(suspend);
+bool ScreenOzone::SetScreenSaverSuspended(bool suspend) {
+  return platform_screen_->SetScreenSaverSuspended(suspend);
 }
 
 bool ScreenOzone::IsScreenSaverActive() const {
diff --git a/ui/aura/screen_ozone.h b/ui/aura/screen_ozone.h
index f4a6395..e5bd10e 100644
--- a/ui/aura/screen_ozone.h
+++ b/ui/aura/screen_ozone.h
@@ -47,7 +47,7 @@
   display::Display GetDisplayMatching(
       const gfx::Rect& match_rect) const override;
   display::Display GetPrimaryDisplay() const override;
-  void SetScreenSaverSuspended(bool suspend) override;
+  bool SetScreenSaverSuspended(bool suspend) override;
   bool IsScreenSaverActive() const override;
   base::TimeDelta CalculateIdleTime() const override;
   void AddObserver(display::DisplayObserver* observer) override;
diff --git a/ui/base/DEPS b/ui/base/DEPS
index 753260a..03a6e31 100644
--- a/ui/base/DEPS
+++ b/ui/base/DEPS
@@ -16,6 +16,7 @@
   "+ui/resources/grit/webui_generated_resources.h",
   "+ui/resources/grit/webui_resources.h",
   "+ui/strings/grit/app_locale_settings.h",
+  "+ui/strings/grit/ax_strings.h",
   "+ui/strings/grit/ui_strings.h",
   "+url"
 ]
diff --git a/ui/base/resource/resource_bundle.cc b/ui/base/resource/resource_bundle.cc
index c51808a..957c5ee 100644
--- a/ui/base/resource/resource_bundle.cc
+++ b/ui/base/resource/resource_bundle.cc
@@ -1153,7 +1153,7 @@
     if (size - pos < kPngChunkMetadataSize)
       break;
     uint32_t length = 0;
-    base::ReadBigEndian(reinterpret_cast<const char*>(buf + pos), &length);
+    base::ReadBigEndian(buf + pos, &length);
     if (size - pos - kPngChunkMetadataSize < length)
       break;
     if (length == 0 && memcmp(buf + pos + sizeof(uint32_t), kPngScaleChunkType,
diff --git a/ui/base/resource/resource_bundle_unittest.cc b/ui/base/resource/resource_bundle_unittest.cc
index 6c5fa010..758141e 100644
--- a/ui/base/resource/resource_bundle_unittest.cc
+++ b/ui/base/resource/resource_bundle_unittest.cc
@@ -83,12 +83,11 @@
                          bitmap_data->begin() + base::size(kPngMagic),
                          kPngMagic));
   auto ihdr_start = bitmap_data->begin() + base::size(kPngMagic);
-  char ihdr_length_data[sizeof(uint32_t)];
+  uint8_t ihdr_length_data[sizeof(uint32_t)];
   for (size_t i = 0; i < sizeof(uint32_t); ++i)
     ihdr_length_data[i] = *(ihdr_start + i);
   uint32_t ihdr_chunk_length = 0;
-  base::ReadBigEndian(reinterpret_cast<char*>(ihdr_length_data),
-                      &ihdr_chunk_length);
+  base::ReadBigEndian(ihdr_length_data, &ihdr_chunk_length);
   EXPECT_TRUE(
       std::equal(ihdr_start + sizeof(uint32_t),
                  ihdr_start + sizeof(uint32_t) + sizeof(kPngIHDRChunkType),
diff --git a/ui/base/x/x11_util.cc b/ui/base/x/x11_util.cc
index eb08ff1..e6c938e 100644
--- a/ui/base/x/x11_util.cc
+++ b/ui/base/x/x11_util.cc
@@ -823,12 +823,13 @@
   return window_rect.size() == gfx::Size(width, height);
 }
 
-void SuspendX11ScreenSaver(bool suspend) {
+bool SuspendX11ScreenSaver(bool suspend) {
   static const bool kScreenSaverAvailable = IsX11ScreenSaverAvailable();
   if (!kScreenSaverAvailable)
-    return;
+    return false;
 
   x11::Connection::Get()->screensaver().Suspend({suspend});
+  return true;
 }
 
 void StoreGpuExtraInfoIntoListValue(x11::VisualId system_visual,
diff --git a/ui/base/x/x11_util.h b/ui/base/x/x11_util.h
index efaaa353..cd03d7d 100644
--- a/ui/base/x/x11_util.h
+++ b/ui/base/x/x11_util.h
@@ -346,8 +346,9 @@
 // Returns true if a given window is in full-screen mode.
 COMPONENT_EXPORT(UI_BASE_X) bool IsX11WindowFullScreen(x11::Window window);
 
-// Suspends or resumes the X screen saver.  Must be called on the UI thread.
-COMPONENT_EXPORT(UI_BASE_X) void SuspendX11ScreenSaver(bool suspend);
+// Suspends or resumes the X screen saver, and returns whether the operation was
+// successful.  Must be called on the UI thread.
+COMPONENT_EXPORT(UI_BASE_X) bool SuspendX11ScreenSaver(bool suspend);
 
 // Returns true if the window manager supports the given hint.
 COMPONENT_EXPORT(UI_BASE_X) bool WmSupportsHint(x11::Atom atom);
diff --git a/ui/display/screen.cc b/ui/display/screen.cc
index b2d5cbc..475ac33b 100644
--- a/ui/display/screen.cc
+++ b/ui/display/screen.cc
@@ -71,8 +71,9 @@
   display_id_for_new_windows_ = display_id;
 }
 
-void Screen::SetScreenSaverSuspended(bool suspend) {
+bool Screen::SetScreenSaverSuspended(bool suspend) {
   NOTIMPLEMENTED_LOG_ONCE();
+  return false;
 }
 
 bool Screen::IsScreenSaverActive() const {
diff --git a/ui/display/screen.h b/ui/display/screen.h
index 096d2ef2..78e10ed5 100644
--- a/ui/display/screen.h
+++ b/ui/display/screen.h
@@ -118,8 +118,9 @@
   // (both of which may or may not be `nearest_id`).
   display::ScreenInfos GetScreenInfosNearestDisplay(int64_t nearest_id) const;
 
-  // Suspends the platform-specific screensaver, if applicable.
-  virtual void SetScreenSaverSuspended(bool suspend);
+  // Suspends or un-suspends the platform-specific screensaver, and returns
+  // whether the operation was successful.
+  virtual bool SetScreenSaverSuspended(bool suspend);
 
   // Returns whether the screensaver is currently running.
   virtual bool IsScreenSaverActive() const;
diff --git a/ui/ozone/platform/wayland/host/wayland_screen.cc b/ui/ozone/platform/wayland/host/wayland_screen.cc
index 2cfe030..6201f46 100644
--- a/ui/ozone/platform/wayland/host/wayland_screen.cc
+++ b/ui/ozone/platform/wayland/host/wayland_screen.cc
@@ -284,9 +284,9 @@
   return display_matching ? *display_matching : GetPrimaryDisplay();
 }
 
-void WaylandScreen::SetScreenSaverSuspended(bool suspend) {
+bool WaylandScreen::SetScreenSaverSuspended(bool suspend) {
   if (!connection_->zwp_idle_inhibit_manager())
-    return;
+    return false;
 
   if (suspend) {
     // Wayland inhibits idle behaviour on certain output, and implies that a
@@ -300,7 +300,7 @@
     const auto* current_window = window_manager->GetCurrentFocusedWindow();
     if (!current_window) {
       LOG(WARNING) << "Cannot inhibit going idle when no window is focused";
-      return;
+      return false;
     }
     DCHECK(current_window->root_surface());
     idle_inhibitor_ = connection_->zwp_idle_inhibit_manager()->CreateInhibitor(
@@ -308,6 +308,8 @@
   } else {
     idle_inhibitor_.reset();
   }
+
+  return true;
 }
 
 bool WaylandScreen::IsScreenSaverActive() const {
diff --git a/ui/ozone/platform/wayland/host/wayland_screen.h b/ui/ozone/platform/wayland/host/wayland_screen.h
index 916951e..5b59b77 100644
--- a/ui/ozone/platform/wayland/host/wayland_screen.h
+++ b/ui/ozone/platform/wayland/host/wayland_screen.h
@@ -64,7 +64,7 @@
       const gfx::Point& point) const override;
   display::Display GetDisplayMatching(
       const gfx::Rect& match_rect) const override;
-  void SetScreenSaverSuspended(bool suspend) override;
+  bool SetScreenSaverSuspended(bool suspend) override;
   bool IsScreenSaverActive() const override;
   base::TimeDelta CalculateIdleTime() const override;
   void AddObserver(display::DisplayObserver* observer) override;
diff --git a/ui/ozone/platform/x11/x11_screen_ozone.cc b/ui/ozone/platform/x11/x11_screen_ozone.cc
index f9cbf03..58f7606 100644
--- a/ui/ozone/platform/x11/x11_screen_ozone.cc
+++ b/ui/ozone/platform/x11/x11_screen_ozone.cc
@@ -114,8 +114,8 @@
   return matching_display ? *matching_display : GetPrimaryDisplay();
 }
 
-void X11ScreenOzone::SetScreenSaverSuspended(bool suspend) {
-  SuspendX11ScreenSaver(suspend);
+bool X11ScreenOzone::SetScreenSaverSuspended(bool suspend) {
+  return SuspendX11ScreenSaver(suspend);
 }
 
 bool X11ScreenOzone::IsScreenSaverActive() const {
diff --git a/ui/ozone/platform/x11/x11_screen_ozone.h b/ui/ozone/platform/x11/x11_screen_ozone.h
index b664bc3..c197be1 100644
--- a/ui/ozone/platform/x11/x11_screen_ozone.h
+++ b/ui/ozone/platform/x11/x11_screen_ozone.h
@@ -48,7 +48,7 @@
       const gfx::Point& point) const override;
   display::Display GetDisplayMatching(
       const gfx::Rect& match_rect) const override;
-  void SetScreenSaverSuspended(bool suspend) override;
+  bool SetScreenSaverSuspended(bool suspend) override;
   bool IsScreenSaverActive() const override;
   base::TimeDelta CalculateIdleTime() const override;
   void AddObserver(display::DisplayObserver* observer) override;
diff --git a/ui/ozone/public/platform_screen.cc b/ui/ozone/public/platform_screen.cc
index a5fa843..bc517733 100644
--- a/ui/ozone/public/platform_screen.cc
+++ b/ui/ozone/public/platform_screen.cc
@@ -24,8 +24,9 @@
   return {};
 }
 
-void PlatformScreen::SetScreenSaverSuspended(bool suspend) {
+bool PlatformScreen::SetScreenSaverSuspended(bool suspend) {
   NOTIMPLEMENTED_LOG_ONCE();
+  return false;
 }
 
 bool PlatformScreen::IsScreenSaverActive() const {
diff --git a/ui/ozone/public/platform_screen.h b/ui/ozone/public/platform_screen.h
index 57b3b77..43dc998 100644
--- a/ui/ozone/public/platform_screen.h
+++ b/ui/ozone/public/platform_screen.h
@@ -86,11 +86,11 @@
   virtual display::Display GetDisplayMatching(
       const gfx::Rect& match_rect) const = 0;
 
-  // Suspends the platform-specific screensaver, if applicable.
-  // Can be called more than once with the same value for |suspend|, but those
-  // states should not stack: the first alternating value should toggle the
-  // state of the suspend.
-  virtual void SetScreenSaverSuspended(bool suspend);
+  // Suspends or un-suspends the platform-specific screensaver, and returns
+  // whether the operation was successful. Can be called more than once with the
+  // same value for |suspend|, but those states should not stack: the first
+  // alternating value should toggle the state of the suspend.
+  virtual bool SetScreenSaverSuspended(bool suspend);
 
   // Returns whether the screensaver is currently running.
   virtual bool IsScreenSaverActive() const;
diff --git a/ui/resources/BUILD.gn b/ui/resources/BUILD.gn
index 8fef193..5ec0970 100644
--- a/ui/resources/BUILD.gn
+++ b/ui/resources/BUILD.gn
@@ -151,6 +151,7 @@
     "$root_gen_dir/ui/resources/webui_generated_resources.pak",
     "$root_gen_dir/ui/resources/webui_resources.pak",
     "$root_gen_dir/ui/strings/app_locale_settings_en-US.pak",
+    "$root_gen_dir/ui/strings/ax_strings_en-US.pak",
     "$root_gen_dir/ui/strings/ui_strings_en-US.pak",
   ]
 
@@ -225,6 +226,7 @@
 
   sources = [
     "$root_gen_dir/ui/strings/app_locale_settings_en-US.pak",
+    "$root_gen_dir/ui/strings/ax_strings_en-US.pak",
     "$root_gen_dir/ui/strings/ui_strings_en-US.pak",
   ]
 
diff --git a/ui/strings/BUILD.gn b/ui/strings/BUILD.gn
index 09ee0bb5..50efb81 100644
--- a/ui/strings/BUILD.gn
+++ b/ui/strings/BUILD.gn
@@ -5,15 +5,24 @@
 import("//build/config/locales.gni")
 import("//tools/grit/grit_rule.gni")
 
-# Meta target that includes both ui_strings and app_locale_settings. Most
-# targets want both. You can depend on the individually if you need to.
+# Meta target that includes both ax/ui_strings and app_locale_settings. Most
+# targets want them all. You can depend on the individually if you need to.
 group("strings") {
   public_deps = [
     ":app_locale_settings",
+    ":ax_strings",
     ":ui_strings",
   ]
 }
 
+grit("ax_strings") {
+  source = "ax_strings.grd"
+  outputs = [ "grit/ax_strings.h" ]
+  foreach(locale, locales_with_pseudolocales) {
+    outputs += [ "ax_strings_$locale.pak" ]
+  }
+}
+
 grit("ui_strings") {
   source = "ui_strings.grd"
   outputs = [ "grit/ui_strings.h" ]
diff --git a/ui/strings/ax_strings.grd b/ui/strings/ax_strings.grd
new file mode 100644
index 0000000..7d7902f
--- /dev/null
+++ b/ui/strings/ax_strings.grd
@@ -0,0 +1,678 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This file contains definitions of resources that will be translated for
+each locale. Specifically, these are AX strings that are used by app/ that
+need to be translated for each locale.-->
+
+<grit base_dir="." latest_public_release="0" current_release="1"
+      output_all_resource_defines="false" source_lang_id="en">
+  <outputs>
+    <output filename="grit/ax_strings.h" type="rc_header">
+      <emit emit_type='prepend'></emit>
+    </output>
+    <if expr="is_android">
+      <output filename="ax_strings_af.pak" type="data_package" lang="af" />
+      <output filename="ax_strings_as.pak" type="data_package" lang="as" />
+      <output filename="ax_strings_az.pak" type="data_package" lang="az" />
+      <output filename="ax_strings_be.pak" type="data_package" lang="be" />
+      <output filename="ax_strings_bs.pak" type="data_package" lang="bs" />
+      <output filename="ax_strings_eu.pak" type="data_package" lang="eu" />
+      <output filename="ax_strings_fr-CA.pak" type="data_package" lang="fr-CA" />
+      <output filename="ax_strings_gl.pak" type="data_package" lang="gl" />
+      <output filename="ax_strings_hy.pak" type="data_package" lang="hy" />
+      <output filename="ax_strings_is.pak" type="data_package" lang="is" />
+      <output filename="ax_strings_ka.pak" type="data_package" lang="ka" />
+      <output filename="ax_strings_kk.pak" type="data_package" lang="kk" />
+      <output filename="ax_strings_km.pak" type="data_package" lang="km" />
+      <output filename="ax_strings_ky.pak" type="data_package" lang="ky" />
+      <output filename="ax_strings_lo.pak" type="data_package" lang="lo" />
+      <output filename="ax_strings_mk.pak" type="data_package" lang="mk" />
+      <output filename="ax_strings_mn.pak" type="data_package" lang="mn" />
+      <output filename="ax_strings_my.pak" type="data_package" lang="my" />
+      <output filename="ax_strings_ne.pak" type="data_package" lang="ne" />
+      <output filename="ax_strings_or.pak" type="data_package" lang="or" />
+      <output filename="ax_strings_pa.pak" type="data_package" lang="pa" />
+      <output filename="ax_strings_si.pak" type="data_package" lang="si" />
+      <output filename="ax_strings_sq.pak" type="data_package" lang="sq" />
+      <output filename="ax_strings_sr-Latn.pak" type="data_package" lang="sr-Latn" />
+      <output filename="ax_strings_ur.pak" type="data_package" lang="ur" />
+      <output filename="ax_strings_uz.pak" type="data_package" lang="uz" />
+      <output filename="ax_strings_zh-HK.pak" type="data_package" lang="zh-HK" />
+      <output filename="ax_strings_zu.pak" type="data_package" lang="zu" />
+    </if>
+    <if expr="chromeos or lacros">
+      <output filename="ax_strings_is.pak" type="data_package" lang="is" />
+    </if>
+    <output filename="ax_strings_am.pak" type="data_package" lang="am" />
+    <output filename="ax_strings_ar.pak" type="data_package" lang="ar" />
+    <output filename="ax_strings_bg.pak" type="data_package" lang="bg" />
+    <output filename="ax_strings_bn.pak" type="data_package" lang="bn" />
+    <output filename="ax_strings_ca.pak" type="data_package" lang="ca" />
+    <output filename="ax_strings_cs.pak" type="data_package" lang="cs" />
+    <output filename="ax_strings_da.pak" type="data_package" lang="da" />
+    <output filename="ax_strings_de.pak" type="data_package" lang="de" />
+    <output filename="ax_strings_el.pak" type="data_package" lang="el" />
+    <output filename="ax_strings_en-GB.pak" type="data_package" lang="en-GB" />
+    <output filename="ax_strings_en-US.pak" type="data_package" lang="en" />
+    <output filename="ax_strings_es.pak" type="data_package" lang="es" />
+    <if expr="is_ios">
+      <!-- iOS uses es-MX for es-419 -->
+      <output filename="ax_strings_es-MX.pak" type="data_package" lang="es-419" />
+    </if>
+    <if expr="not is_ios">
+      <output filename="ax_strings_es-419.pak" type="data_package" lang="es-419" />
+    </if>
+    <output filename="ax_strings_et.pak" type="data_package" lang="et" />
+    <output filename="ax_strings_fa.pak" type="data_package" lang="fa" />
+    <output filename="ax_strings_fi.pak" type="data_package" lang="fi" />
+    <output filename="ax_strings_fil.pak" type="data_package" lang="fil" />
+    <output filename="ax_strings_fr.pak" type="data_package" lang="fr" />
+    <output filename="ax_strings_gu.pak" type="data_package" lang="gu" />
+    <output filename="ax_strings_he.pak" type="data_package" lang="he" />
+    <output filename="ax_strings_hi.pak" type="data_package" lang="hi" />
+    <output filename="ax_strings_hr.pak" type="data_package" lang="hr" />
+    <output filename="ax_strings_hu.pak" type="data_package" lang="hu" />
+    <output filename="ax_strings_id.pak" type="data_package" lang="id" />
+    <output filename="ax_strings_it.pak" type="data_package" lang="it" />
+    <output filename="ax_strings_ja.pak" type="data_package" lang="ja" />
+    <output filename="ax_strings_kn.pak" type="data_package" lang="kn" />
+    <output filename="ax_strings_ko.pak" type="data_package" lang="ko" />
+    <output filename="ax_strings_lt.pak" type="data_package" lang="lt" />
+    <output filename="ax_strings_lv.pak" type="data_package" lang="lv" />
+    <output filename="ax_strings_ml.pak" type="data_package" lang="ml" />
+    <output filename="ax_strings_mr.pak" type="data_package" lang="mr" />
+    <output filename="ax_strings_ms.pak" type="data_package" lang="ms" />
+    <output filename="ax_strings_nl.pak" type="data_package" lang="nl" />
+    <!-- The translation console uses 'no' for Norwegian Bokmål. It should
+         be 'nb'. -->
+    <output filename="ax_strings_nb.pak" type="data_package" lang="no" />
+    <output filename="ax_strings_pl.pak" type="data_package" lang="pl" />
+    <if expr="is_ios">
+      <!-- iOS uses pt for pt-BR -->
+      <output filename="ax_strings_pt.pak" type="data_package" lang="pt-BR" />
+    </if>
+    <if expr="not is_ios">
+      <output filename="ax_strings_pt-BR.pak" type="data_package" lang="pt-BR" />
+    </if>
+    <output filename="ax_strings_pt-PT.pak" type="data_package" lang="pt-PT" />
+    <output filename="ax_strings_ro.pak" type="data_package" lang="ro" />
+    <output filename="ax_strings_ru.pak" type="data_package" lang="ru" />
+    <output filename="ax_strings_sk.pak" type="data_package" lang="sk" />
+    <output filename="ax_strings_sl.pak" type="data_package" lang="sl" />
+    <output filename="ax_strings_sr.pak" type="data_package" lang="sr" />
+    <output filename="ax_strings_sv.pak" type="data_package" lang="sv" />
+    <output filename="ax_strings_sw.pak" type="data_package" lang="sw" />
+    <output filename="ax_strings_ta.pak" type="data_package" lang="ta" />
+    <output filename="ax_strings_te.pak" type="data_package" lang="te" />
+    <output filename="ax_strings_th.pak" type="data_package" lang="th" />
+    <output filename="ax_strings_tr.pak" type="data_package" lang="tr" />
+    <output filename="ax_strings_uk.pak" type="data_package" lang="uk" />
+    <output filename="ax_strings_vi.pak" type="data_package" lang="vi" />
+    <output filename="ax_strings_zh-CN.pak" type="data_package" lang="zh-CN" />
+    <output filename="ax_strings_zh-TW.pak" type="data_package" lang="zh-TW" />
+
+    <!-- Pseudolocales -->
+    <output filename="ax_strings_ar-XB.pak" type="data_package" lang="ar-XB" />
+    <output filename="ax_strings_en-XA.pak" type="data_package" lang="en-XA" />
+  </outputs>
+  <translations>
+    <file path="translations/ax_strings_af.xtb" lang="af" />
+    <file path="translations/ax_strings_am.xtb" lang="am" />
+    <file path="translations/ax_strings_ar.xtb" lang="ar" />
+    <file path="translations/ax_strings_as.xtb" lang="as" />
+    <file path="translations/ax_strings_az.xtb" lang="az" />
+    <file path="translations/ax_strings_be.xtb" lang="be" />
+    <file path="translations/ax_strings_bg.xtb" lang="bg" />
+    <file path="translations/ax_strings_bn.xtb" lang="bn" />
+    <file path="translations/ax_strings_bs.xtb" lang="bs" />
+    <file path="translations/ax_strings_ca.xtb" lang="ca" />
+    <file path="translations/ax_strings_cs.xtb" lang="cs" />
+    <file path="translations/ax_strings_da.xtb" lang="da" />
+    <file path="translations/ax_strings_de.xtb" lang="de" />
+    <file path="translations/ax_strings_el.xtb" lang="el" />
+    <file path="translations/ax_strings_en-GB.xtb" lang="en-GB" />
+    <file path="translations/ax_strings_es.xtb" lang="es" />
+    <file path="translations/ax_strings_es-419.xtb" lang="es-419" />
+    <file path="translations/ax_strings_et.xtb" lang="et" />
+    <file path="translations/ax_strings_eu.xtb" lang="eu" />
+    <file path="translations/ax_strings_fa.xtb" lang="fa" />
+    <file path="translations/ax_strings_fi.xtb" lang="fi" />
+    <file path="translations/ax_strings_fil.xtb" lang="fil" />
+    <file path="translations/ax_strings_fr.xtb" lang="fr" />
+    <file path="translations/ax_strings_fr-CA.xtb" lang="fr-CA" />
+    <file path="translations/ax_strings_gl.xtb" lang="gl" />
+    <file path="translations/ax_strings_gu.xtb" lang="gu" />
+    <file path="translations/ax_strings_hi.xtb" lang="hi" />
+    <file path="translations/ax_strings_hr.xtb" lang="hr" />
+    <file path="translations/ax_strings_hu.xtb" lang="hu" />
+    <file path="translations/ax_strings_hy.xtb" lang="hy" />
+    <file path="translations/ax_strings_id.xtb" lang="id" />
+    <file path="translations/ax_strings_is.xtb" lang="is" />
+    <file path="translations/ax_strings_it.xtb" lang="it" />
+    <!-- The translation console uses 'iw' for Hebrew, but we use 'he'. -->
+    <file path="translations/ax_strings_iw.xtb" lang="he" />
+    <file path="translations/ax_strings_ja.xtb" lang="ja" />
+    <file path="translations/ax_strings_ka.xtb" lang="ka" />
+    <file path="translations/ax_strings_kk.xtb" lang="kk" />
+    <file path="translations/ax_strings_km.xtb" lang="km" />
+    <file path="translations/ax_strings_kn.xtb" lang="kn" />
+    <file path="translations/ax_strings_ko.xtb" lang="ko" />
+    <file path="translations/ax_strings_ky.xtb" lang="ky" />
+    <file path="translations/ax_strings_lo.xtb" lang="lo" />
+    <file path="translations/ax_strings_lt.xtb" lang="lt" />
+    <file path="translations/ax_strings_lv.xtb" lang="lv" />
+    <file path="translations/ax_strings_mk.xtb" lang="mk" />
+    <file path="translations/ax_strings_ml.xtb" lang="ml" />
+    <file path="translations/ax_strings_mn.xtb" lang="mn" />
+    <file path="translations/ax_strings_mr.xtb" lang="mr" />
+    <file path="translations/ax_strings_ms.xtb" lang="ms" />
+    <file path="translations/ax_strings_my.xtb" lang="my" />
+    <file path="translations/ax_strings_ne.xtb" lang="ne" />
+    <file path="translations/ax_strings_nl.xtb" lang="nl" />
+    <file path="translations/ax_strings_no.xtb" lang="no" />
+    <file path="translations/ax_strings_or.xtb" lang="or" />
+    <file path="translations/ax_strings_pa.xtb" lang="pa" />
+    <file path="translations/ax_strings_pl.xtb" lang="pl" />
+    <file path="translations/ax_strings_pt-BR.xtb" lang="pt-BR" />
+    <file path="translations/ax_strings_pt-PT.xtb" lang="pt-PT" />
+    <file path="translations/ax_strings_ro.xtb" lang="ro" />
+    <file path="translations/ax_strings_ru.xtb" lang="ru" />
+    <file path="translations/ax_strings_si.xtb" lang="si" />
+    <file path="translations/ax_strings_sk.xtb" lang="sk" />
+    <file path="translations/ax_strings_sl.xtb" lang="sl" />
+    <file path="translations/ax_strings_sq.xtb" lang="sq" />
+    <file path="translations/ax_strings_sr.xtb" lang="sr" />
+    <file path="translations/ax_strings_sr-Latn.xtb" lang="sr-Latn" />
+    <file path="translations/ax_strings_sv.xtb" lang="sv" />
+    <file path="translations/ax_strings_sw.xtb" lang="sw" />
+    <file path="translations/ax_strings_ta.xtb" lang="ta" />
+    <file path="translations/ax_strings_te.xtb" lang="te" />
+    <file path="translations/ax_strings_th.xtb" lang="th" />
+    <file path="translations/ax_strings_tr.xtb" lang="tr" />
+    <file path="translations/ax_strings_uk.xtb" lang="uk" />
+    <file path="translations/ax_strings_ur.xtb" lang="ur" />
+    <file path="translations/ax_strings_uz.xtb" lang="uz" />
+    <file path="translations/ax_strings_vi.xtb" lang="vi" />
+    <file path="translations/ax_strings_zh-CN.xtb" lang="zh-CN" />
+    <file path="translations/ax_strings_zh-HK.xtb" lang="zh-HK" />
+    <file path="translations/ax_strings_zh-TW.xtb" lang="zh-TW" />
+    <file path="translations/ax_strings_zu.xtb" lang="zu" />
+  </translations>
+  <release seq="1">
+    <messages fallback_to_english="true">
+      <!--Accessible action strings-->
+      <message name="IDS_AX_ACTIVATE_ACTION_VERB" desc="Verb stating the action that will occur when an element such as a text field is selected, as used by accessibility.">
+        activate
+      </message>
+      <message name="IDS_AX_CHECK_ACTION_VERB" desc="Verb stating the action that will occur when an unchecked checkbox is clicked, as used by accessibility.">
+        check
+      </message>
+      <message name="IDS_AX_CLICK_ACTION_VERB" desc="Verb stating the action that will occur when clicking on a generic clickable object, when we don't have a more specific action description, as used by accessibility.">
+        click
+      </message>
+      <message name="IDS_AX_CLICK_ANCESTOR_ACTION_VERB" desc="Verb stating the action that will occur when clicking on a generic object that is not itself clickable but one of its ancestors is, as used by accessibility.">
+        click ancestor
+      </message>
+      <message name="IDS_AX_JUMP_ACTION_VERB" desc="Verb stating the action that will occur when an element such as an in-page link is activated, as used by accessibility.">
+        jump
+      </message>
+      <message name="IDS_AX_OPEN_ACTION_VERB" desc="Verb stating the action that will occur when an element such as a pop-up button is pressed, as used by accessibility.">
+        open
+      </message>
+      <message name="IDS_AX_PRESS_ACTION_VERB" desc="Verb stating the action that will occur when an element such as a button is pressed, as used by accessibility.">
+        press
+      </message>
+      <message name="IDS_AX_SELECT_ACTION_VERB" desc="Verb stating the action that will occur when an element such as a radio button is selected, as used by accessibility.">
+        select
+      </message>
+      <message name="IDS_AX_UNCHECK_ACTION_VERB" desc="Verb stating the action that will occur when a checked checkbox is clicked, as used by accessibility.">
+        uncheck
+      </message>
+
+      <!-- Accessible role localized names -->
+      <message name="IDS_AX_ROLE_ARTICLE" desc="Accessibility role description for article">
+        article
+      </message>
+      <!-- https://w3c.github.io/html-aam/#el-audio -->
+      <message name="IDS_AX_ROLE_AUDIO" desc="Accessible role description for audio">
+        audio
+      </message>
+      <message name="IDS_AX_ROLE_BANNER" desc="Accessibility role description for banner">
+        banner
+      </message>
+      <message name="IDS_AX_ROLE_CODE" desc="Accessibility role description for a section whose content represents a fragment of computer code">
+        code
+      </message>
+      <!-- https://w3c.github.io/html-aam/#el-input-color -->
+      <message name="IDS_AX_ROLE_COLOR_WELL" desc="Accessibility role description for a color picker">
+        color picker
+      </message>
+      <message name="IDS_AX_ROLE_COMMENT" desc="Accessibility role description for a single comment">
+        comment
+      </message>
+      <message name="IDS_AX_ROLE_COMPLEMENTARY" desc="Accessibility role description for complementary">
+        complementary
+      </message>
+      <message name="IDS_AX_ROLE_CONTENT_DELETION" desc="Accessibility role description for content deletion, meaning content that is has been or is suggested to be removed from a document, such as in a revision review">
+        deletion
+      </message>
+      <message name="IDS_AX_ROLE_CONTENT_INSERTION" desc="Accessibility role description for content insertion, meaning content that has been marked or is suggested to be inserted into a document, such as in a revision review">
+        insertion
+      </message>
+      <message name="IDS_AX_ROLE_CHECK_BOX" desc="Accessibility role description for a check box">
+        checkbox
+      </message>
+      <message name="IDS_AX_ROLE_CONTENT_INFO" desc="Accessibility role description for credits and information about the content of the page, like copyrights and privacy statements">
+        content information
+      </message>
+      <message name="IDS_AX_ROLE_DATE" desc="Accessibility role description for a date input">
+        date picker
+      </message>
+      <message name="IDS_AX_ROLE_DATE_TIME_LOCAL" desc="Accessibility role description for a datetime-local input">
+        local date and time picker
+      </message>
+      <message name="IDS_AX_ROLE_DEFINITION" desc="Accessibility role description for a definition">
+        definition
+      </message>
+      <message name="IDS_AX_ROLE_DESCRIPTION_LIST" desc="Accessibility role description for a definition list">
+        definition list
+      </message>
+      <message name="IDS_AX_ROLE_DESCRIPTION_TERM" desc="Accessibility role description for description term (as in a description list)">
+        term
+      </message>
+      <!-- https://w3c.github.io/html-aam/#el-details -->
+      <message name="IDS_AX_ROLE_DETAILS" desc="Accessibility role description for details">
+        details
+      </message>
+      <message name="IDS_AX_ROLE_DISCLOSURE_TRIANGLE" desc="Accessibility role description for a disclosure triangle, a control shaped like a triangle that expands or collapses to show or hide extra content">
+        disclosure triangle
+      </message>
+      <message name="IDS_AX_ROLE_DOC_ABSTRACT" desc="Accessibility role description for abstract, meaning the summary of the contents of an article or book">
+        abstract
+      </message>
+      <message name="IDS_AX_ROLE_DOC_ACKNOWLEDGMENTS" desc="Accessibility role description for acknowledgments">
+        acknowledgments
+      </message>
+      <message name="IDS_AX_ROLE_DOC_AFTERWORD" desc="Accessibility role description for afterword">
+        afterword
+      </message>
+      <message name="IDS_AX_ROLE_DOC_APPENDIX" desc="Accessibility role description for appendix, meaning the summary of the contents of an article or book">
+        appendix
+      </message>
+      <message name="IDS_AX_ROLE_DOC_BACKLINK" desc="Accessibility role description for back link">
+        back link
+      </message>
+      <message name="IDS_AX_ROLE_DOC_BIBLIO_ENTRY" desc="Accessibility role description for bibliography entry">
+        bibliography entry
+      </message>
+      <message name="IDS_AX_ROLE_DOC_BIBLIOGRAPHY" desc="Accessibility role description for bibliography">
+        bibliography
+      </message>
+      <message name="IDS_AX_ROLE_DOC_BIBLIO_REF" desc="Accessibility role description for bibliography reference">
+        bibliography reference
+      </message>
+      <message name="IDS_AX_ROLE_DOC_CHAPTER" desc="Accessibility role description for chapter">
+        chapter
+      </message>
+      <message name="IDS_AX_ROLE_DOC_COLOPHON" desc="Accessibility role description for colophon">
+        colophon
+      </message>
+      <message name="IDS_AX_ROLE_DOC_CONCLUSION" desc="Accessibility role description for conclusion">
+        conclusion
+      </message>
+      <message name="IDS_AX_ROLE_DOC_COVER" desc="Accessibility role description for cover">
+        cover
+      </message>
+      <message name="IDS_AX_ROLE_DOC_CREDIT" desc="Accessibility role description for credit, a public acknowledgement of someone who was responsible for helping with something">
+        credit
+      </message>
+      <message name="IDS_AX_ROLE_DOC_CREDITS" desc="Accessibility role description for credits">
+        credits
+      </message>
+      <message name="IDS_AX_ROLE_DOC_DEDICATION" desc="Accessibility role description for dedication">
+        dedication
+      </message>
+      <message name="IDS_AX_ROLE_DOC_ENDNOTE" desc="Accessibility role description for endnote">
+        endnote
+      </message>
+      <message name="IDS_AX_ROLE_DOC_ENDNOTES" desc="Accessibility role description for endnotes">
+        endnotes
+      </message>
+      <message name="IDS_AX_ROLE_DOC_EPIGRAPH" desc="Accessibility role description for epigraph">
+        epigraph
+      </message>
+      <message name="IDS_AX_ROLE_DOC_EPILOGUE" desc="Accessibility role description for epilogue">
+        epilogue
+      </message>
+      <message name="IDS_AX_ROLE_DOC_ERRATA" desc="Accessibility role description for errata">
+        errata
+      </message>
+      <message name="IDS_AX_ROLE_DOC_EXAMPLE" desc="Accessibility role description for example">
+        example
+      </message>
+      <message name="IDS_AX_ROLE_DOC_FOOTNOTE" desc="Accessibility role description for footnote">
+        footnote
+      </message>
+      <message name="IDS_AX_ROLE_DOC_FOREWORD" desc="Accessibility role description for foreword">
+        foreword
+      </message>
+      <message name="IDS_AX_ROLE_DOC_GLOSSARY" desc="Accessibility role description for glossary">
+        glossary
+      </message>
+      <message name="IDS_AX_ROLE_DOC_GLOSS_REF" desc="Accessibility role description for glossary reference">
+        glossary reference
+      </message>
+      <message name="IDS_AX_ROLE_DOC_INDEX" desc="Accessibility role description for index">
+        index
+      </message>
+      <message name="IDS_AX_ROLE_DOC_INTRODUCTION" desc="Accessibility role description for introduction">
+        introduction
+      </message>
+      <message name="IDS_AX_ROLE_DOC_NOTE_REF" desc="Accessibility role description for note reference">
+        note reference
+      </message>
+      <message name="IDS_AX_ROLE_DOC_NOTICE" desc="Accessibility role description for notice">
+        notice
+      </message>
+      <message name="IDS_AX_ROLE_DOC_PAGE_BREAK" desc="Accessibility role description for page break">
+        page break
+      </message>
+      <message name="IDS_AX_ROLE_DOC_PAGE_FOOTER" desc="Accessibility role description for page footer">
+        page footer
+      </message>
+      <message name="IDS_AX_ROLE_DOC_PAGE_HEADER" desc="Accessibility role description for page header">
+        page header
+      </message>
+      <message name="IDS_AX_ROLE_DOC_PAGE_LIST" desc="Accessibility role description for page list">
+        page list
+      </message>
+      <message name="IDS_AX_ROLE_DOC_PART" desc="Accessibility role description for part, as in a part of a book">
+        part
+      </message>
+      <message name="IDS_AX_ROLE_DOC_PREFACE" desc="Accessibility role description for preface">
+        preface
+      </message>
+      <message name="IDS_AX_ROLE_DOC_PROLOGUE" desc="Accessibility role description for prologue">
+        prologue
+      </message>
+      <message name="IDS_AX_ROLE_DOC_PULLQUOTE" desc="Accessibility role description for pullquote">
+        pullquote
+      </message>
+      <message name="IDS_AX_ROLE_DOC_QNA" desc="Accessibility role description for Q+A (questions and answers)">
+        Q&amp;A
+      </message>
+      <message name="IDS_AX_ROLE_DOC_SUBTITLE" desc="Accessibility role description for subtitle">
+        subtitle
+      </message>
+      <message name="IDS_AX_ROLE_DOC_TIP" desc="Accessibility role description for tip, as in a suggestion or idea">
+        tip
+      </message>
+      <message name="IDS_AX_ROLE_DOC_TOC" desc="Accessibility role description for table of contents">
+        table of contents
+      </message>
+      <!-- https://w3c.github.io/html-aam/#el-input-email -->
+      <message name="IDS_AX_ROLE_EMAIL" desc="Accessibility role description for email input">
+        email
+      </message>
+      <message name="IDS_AX_ROLE_EMPHASIS" desc="Accessibility role description for one or more emphasized characters">
+        emphasis
+      </message>
+      <message name="IDS_AX_ROLE_FEED" desc="Accessibility role description for a scrollable list of articles.">
+        feed
+      </message>
+      <message name="IDS_AX_ROLE_FIGURE" desc="Accessibility role description for figure">
+        figure
+      </message>
+      <message name="IDS_AX_ROLE_FORM" desc="Accessibility role description for form">
+        form
+      </message>
+      <message name="IDS_AX_ROLE_FOOTER" desc="Accessibility role description for footers">
+        footer
+      </message>
+      <message name="IDS_AX_ROLE_GRAPHICS_DOCUMENT" desc="Accessibility role description for graphics document">
+        graphics document
+      </message>
+      <message name="IDS_AX_ROLE_GRAPHICS_OBJECT" desc="Accessibility role description for graphics object">
+        graphics object
+      </message>
+      <message name="IDS_AX_ROLE_GRAPHICS_SYMBOL" desc="Accessibility role description for graphics symbol">
+        graphics symbol
+      </message>
+      <message name="IDS_AX_ROLE_HEADER" desc="Accessibility role description for header">
+        header
+      </message>
+      <message name="IDS_AX_ROLE_HEADING" desc="Accessibility role description for headings">
+        heading
+      </message>
+      <message name="IDS_AX_ROLE_LINK" desc="Accessibility role description for link">
+        link
+      </message>
+      <message name="IDS_AX_ROLE_MAIN_CONTENT" desc="Accessibility role description for main content of the document.">
+        main
+      </message>
+      <!-- https://w3c.github.io/html-aam/#el-mark -->
+      <message name="IDS_AX_ROLE_MARK" desc="Accessibility role description for highlighted content.">
+        highlight
+      </message>
+      <message name="IDS_AX_ROLE_MATH" desc="Accessibility role description for math">
+        math
+      </message>
+      <!-- https://w3c.github.io/html-aam/#el-meter -->
+      <message name="IDS_AX_ROLE_METER" desc="Accessibility role description for a meter, for example a temperature indicator or progress bar">
+          meter
+      </message>
+      <message name="IDS_AX_ROLE_MONTH" desc="Accessibility role description for a month input">
+        month picker
+      </message>
+      <message name="IDS_AX_ROLE_NAVIGATIONAL_LINK" desc="Accessibility role description for group of navigational links.">
+        navigation
+      </message>
+      <!-- https://w3c.github.io/html-aam/#el-output -->
+      <message name="IDS_AX_ROLE_OUTPUT" desc="Accessibility role description for output">
+        output
+      </message>
+      <message name="IDS_AX_ROLE_REGION" desc="Accessibility role description for region">
+        region
+      </message>
+      <!-- https://www.w3.org/TR/core-aam-1.1/#role-map-searchbox -->
+      <if expr="is_win">
+        <message name="IDS_AX_ROLE_SEARCH_BOX" desc="UIA role description for search text field">
+          search box
+        </message>
+      </if>
+      <if expr="not is_win">
+        <message name="IDS_AX_ROLE_SEARCH_BOX" desc="Accessibility role description for search text field">
+          search text field
+        </message>
+      </if>
+      <!-- https://w3c.github.io/html-aam/#el-section -->
+      <message name="IDS_AX_ROLE_SECTION" desc="Accessibility role description for a section of a document, like the main body text, the table of contents, a header, or a footer">
+        section
+      </message>
+      <message name="IDS_AX_ROLE_STATUS" desc="Accessibility role description for status">
+        status
+      </message>
+      <if expr="is_macosx">
+        <message name="IDS_AX_ROLE_STEPPER" desc="Accessibility role description for a stepper - a control where you can use up/down arrows to increment or decrement it. The name 'stepper' is how this user interface element is described by VoiceOver on Mac OS X; the translation should be consistent with VoiceOver.">
+          stepper
+        </message>
+      </if>
+      <message name="IDS_AX_ROLE_STRONG" desc="Accessibility role description for content which is important, serious, or urgent">
+        strong
+      </message>
+      <message name="IDS_AX_ROLE_SUBSCRIPT" desc="Accessibility role description for subscript" is_accessibility_with_no_ui="true">
+        subscript
+      </message>
+      <message name="IDS_AX_ROLE_SUGGESTION" desc="Accessibility role description for suggestion, meaning a suggested change to some content">
+        suggestion
+      </message>
+      <message name="IDS_AX_ROLE_SUPERSCRIPT" desc="Accessibility role description for superscript" is_accessibility_with_no_ui="true">
+        superscript
+      </message>
+      <message name="IDS_AX_ROLE_SWITCH" desc="Accessibility role description for switch">
+        switch
+      </message>
+      <message name="IDS_AX_ROLE_TAB" desc="Accessibility role description for a tab">
+        tab
+      </message>
+      <!-- https://w3c.github.io/html-aam/#el-input-tel -->
+      <message name="IDS_AX_ROLE_TELEPHONE" desc="Accessibility role description for telephone number input">
+        telephone
+      </message>
+      <!-- https://w3c.github.io/html-aam/#el-time -->
+      <message name="IDS_AX_ROLE_TIME" desc="Accessibility role description for a time">
+        time
+      </message>
+      <message name="IDS_AX_ROLE_TOGGLE_BUTTON" desc="Accessibility role description for a toggle button">
+        toggle button
+      </message>
+      <!-- https://w3c.github.io/html-aam/#el-input-url -->
+      <message name="IDS_AX_ROLE_URL" desc="Accessibility role description for URL input">
+        url
+      </message>
+      <message name="IDS_AX_ROLE_WEB_AREA" desc="Accessibility role description for web area">
+        HTML content
+      </message>
+      <message name="IDS_AX_ROLE_WEEK" desc="Accessibility role description for a week input">
+        week picker
+      </message>
+      <if expr="is_android">
+        <message name="IDS_AX_ROLE_ALERT" desc="Accessibility role description for an alert">
+          alert
+        </message>
+        <message name="IDS_AX_ROLE_ALERT_DIALOG" desc="Accessibility role description for an alert dialog">
+          alert_dialog
+        </message>
+        <message name="IDS_AX_ROLE_APPLICATION" desc="Accessibility role description for an application">
+          application
+        </message>
+        <message name="IDS_AX_ROLE_BLOCKQUOTE" desc="Accessibility role description for a blockquote">
+          blockquote
+        </message>
+        <message name="IDS_AX_ROLE_BUTTON" desc="Accessibility role description for a button">
+          button
+        </message>
+        <message name="IDS_AX_ROLE_COLUMN_HEADER" desc="Accessibility role description for a column header">
+          column header
+        </message>
+        <message name="IDS_AX_ROLE_COMBO_BOX" desc="Accessibility role description for a combo box, an editable text control with an associated drop-down list">
+          combo box
+        </message>
+        <message name="IDS_AX_ROLE_DATE_TIME" desc="Accessibility role description for a date / time picker">
+          date and time picker
+        </message>
+        <message name="IDS_AX_ROLE_DIALOG" desc="Accessibility role description for a dialog">
+          dialog
+        </message>
+        <message name="IDS_AX_ROLE_DIRECTORY" desc="Accessibility role description for a directory, for example a table of contents or list of employees">
+          directory
+        </message>
+        <message name="IDS_AX_ROLE_DOCUMENT" desc="Accessibility role description for a document">
+          document
+        </message>
+        <message name="IDS_AX_ROLE_EMBEDDED_OBJECT" desc="Accessibility role description for an embedded object, like a video inside a web page">
+          object
+        </message>
+        <message name="IDS_AX_ROLE_GRAPHIC" desc="Accessibility role description for a graphic or image">
+          graphic
+        </message>
+        <message name="IDS_AX_ROLE_HEADING_WITH_LEVEL" desc="Accessibility role description for a heading with a heading level, for example 'heading 1' is the most important heading and 'heading 3' is less important">
+          heading <ph name="HEADING_LEVEL">$1<ex>1</ex></ph>
+        </message>
+        <message name="IDS_AX_ROLE_INPUT_TIME" desc="Accessibility role description for a time picker control">
+          time picker
+        </message>
+        <message name="IDS_AX_ROLE_LIST_BOX" desc="Accessibility role description for a list box control">
+          list box
+        </message>
+        <message name="IDS_AX_ROLE_LOG" desc="Accessibility role description for an information log, such as a chat log or error log">
+          log
+        </message>
+        <message name="IDS_AX_ROLE_MARQUEE" desc="Accessibility role description for a marquee, such as a stock ticker or ad banner that frequently changes">
+          marquee
+        </message>
+        <message name="IDS_AX_ROLE_MENU" desc="Accessibility role description for a menu">
+          menu
+        </message>
+        <message name="IDS_AX_ROLE_MENU_BAR" desc="Accessibility role description for a menu bar">
+          menu bar
+        </message>
+        <message name="IDS_AX_ROLE_MENU_ITEM" desc="Accessibility role description for a menu item">
+          menu item
+        </message>
+        <message name="IDS_AX_ROLE_NOTE" desc="Accessibility role description for a note">
+          note
+        </message>
+        <message name="IDS_AX_ROLE_PDF_HIGHLIGHT" desc="Accessibility role description for PDF highlight.">
+          highlight
+        </message>
+        <message name="IDS_AX_ROLE_POP_UP_BUTTON" desc="Accessibility role description for a pop up button">
+          pop up button
+        </message>
+        <message name="IDS_AX_ROLE_POP_UP_BUTTON_MENU" desc="Accessibility role description for a pop up button that opens a menu">
+          menu pop up button
+        </message>
+        <message name="IDS_AX_ROLE_POP_UP_BUTTON_DIALOG" desc="Accessibility role description for a pop up button that opens a dialog">
+          dialog pop up button
+        </message>
+        <message name="IDS_AX_ROLE_PROGRESS_INDICATOR" desc="Accessibility role description for a progress indicator">
+          progress indicator
+        </message>
+        <message name="IDS_AX_ROLE_RADIO" desc="Accessibility role description for a radio button">
+          radio button
+        </message>
+        <message name="IDS_AX_ROLE_RADIO_GROUP" desc="Accessibility role description for a group of related radio buttons">
+          radio group
+        </message>
+        <message name="IDS_AX_ROLE_ROW_GROUP" desc="Accessibility role description for a row group in a table">
+          row group
+        </message>
+        <message name="IDS_AX_ROLE_ROW_HEADER" desc="Accessibility role description for a row header in a table">
+          row header
+        </message>
+        <message name="IDS_AX_ROLE_SCROLL_BAR" desc="Accessibility role description for a scroll bar">
+          scroll bar
+        </message>
+        <message name="IDS_AX_ROLE_SEARCH" desc="Accessibility role description for the section of a web page containing controls for searching the page or site">
+          search
+        </message>
+        <message name="IDS_AX_ROLE_SLIDER" desc="Accessibility role description for a slider">
+          slider
+        </message>
+        <message name="IDS_AX_ROLE_SPIN_BUTTON" desc="Accessibility role description for a spin button, a control containing a numerical value that can be incremented or decremented by 1">
+          spin button
+        </message>
+        <message name="IDS_AX_ROLE_SPLITTER" desc="Accessibility role description for a splitter, a control that splits the window in two pieces and can usually be dragged">
+          splitter
+        </message>
+        <message name="IDS_AX_ROLE_TABLE" desc="Accessibility role description for a table">
+          table
+        </message>
+        <message name="IDS_AX_ROLE_TAB_LIST" desc="Accessibility role description for a tab list">
+          tab list
+        </message>
+        <message name="IDS_AX_ROLE_TAB_PANEL" desc="Accessibility role description for a tab panel, the part of the window that changes when selecting a new tab">
+          tab panel
+        </message>
+        <message name="IDS_AX_ROLE_TIMER" desc="Accessibility role description for a timer">
+          timer
+        </message>
+        <message name="IDS_AX_ROLE_TOOLBAR" desc="Accessibility role description for a toolbar">
+          toolbar
+        </message>
+        <message name="IDS_AX_ROLE_TOOLTIP" desc="Accessibility role description for a tooltip">
+          tooltip
+        </message>
+        <message name="IDS_AX_ROLE_TREE" desc="Accessibility role description for a tree">
+          tree
+        </message>
+        <message name="IDS_AX_ROLE_TREE_GRID" desc="Accessibility role description for a tree grid">
+          tree grid
+        </message>
+        <message name="IDS_AX_ROLE_TREE_ITEM" desc="Accessibility role description for a tree item">
+          tree item
+        </message>
+      </if>
+   </messages>
+  </release>
+</grit>
diff --git a/ui/strings/ui_strings_grd/IDS_AX_ROLE_MONTH.png.sha1 b/ui/strings/ax_strings_grd/IDS_AX_ROLE_MONTH.png.sha1
similarity index 100%
rename from ui/strings/ui_strings_grd/IDS_AX_ROLE_MONTH.png.sha1
rename to ui/strings/ax_strings_grd/IDS_AX_ROLE_MONTH.png.sha1
diff --git a/ui/strings/ui_strings_grd/IDS_AX_ROLE_POP_UP_BUTTON_DIALOG.png.sha1 b/ui/strings/ax_strings_grd/IDS_AX_ROLE_POP_UP_BUTTON_DIALOG.png.sha1
similarity index 100%
rename from ui/strings/ui_strings_grd/IDS_AX_ROLE_POP_UP_BUTTON_DIALOG.png.sha1
rename to ui/strings/ax_strings_grd/IDS_AX_ROLE_POP_UP_BUTTON_DIALOG.png.sha1
diff --git a/ui/strings/ui_strings_grd/IDS_AX_ROLE_POP_UP_BUTTON_MENU.png.sha1 b/ui/strings/ax_strings_grd/IDS_AX_ROLE_POP_UP_BUTTON_MENU.png.sha1
similarity index 100%
rename from ui/strings/ui_strings_grd/IDS_AX_ROLE_POP_UP_BUTTON_MENU.png.sha1
rename to ui/strings/ax_strings_grd/IDS_AX_ROLE_POP_UP_BUTTON_MENU.png.sha1
diff --git a/ui/strings/translations/ax_strings_af.xtb b/ui/strings/translations/ax_strings_af.xtb
new file mode 100644
index 0000000..821e5ee40
--- /dev/null
+++ b/ui/strings/translations/ax_strings_af.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="af">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_am.xtb b/ui/strings/translations/ax_strings_am.xtb
new file mode 100644
index 0000000..7c508d6
--- /dev/null
+++ b/ui/strings/translations/ax_strings_am.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="am">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_ar.xtb b/ui/strings/translations/ax_strings_ar.xtb
new file mode 100644
index 0000000..3cf7110
--- /dev/null
+++ b/ui/strings/translations/ax_strings_ar.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="ar">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_as.xtb b/ui/strings/translations/ax_strings_as.xtb
new file mode 100644
index 0000000..c752487b
--- /dev/null
+++ b/ui/strings/translations/ax_strings_as.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="as">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_az.xtb b/ui/strings/translations/ax_strings_az.xtb
new file mode 100644
index 0000000..b1cc821
--- /dev/null
+++ b/ui/strings/translations/ax_strings_az.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="az">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_be.xtb b/ui/strings/translations/ax_strings_be.xtb
new file mode 100644
index 0000000..bcdd0d5
--- /dev/null
+++ b/ui/strings/translations/ax_strings_be.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="be">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_bg.xtb b/ui/strings/translations/ax_strings_bg.xtb
new file mode 100644
index 0000000..166d0e5
--- /dev/null
+++ b/ui/strings/translations/ax_strings_bg.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="bg">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_bn.xtb b/ui/strings/translations/ax_strings_bn.xtb
new file mode 100644
index 0000000..a576d94
--- /dev/null
+++ b/ui/strings/translations/ax_strings_bn.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="bn">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_bs.xtb b/ui/strings/translations/ax_strings_bs.xtb
new file mode 100644
index 0000000..eca132c
--- /dev/null
+++ b/ui/strings/translations/ax_strings_bs.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="bs">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_ca.xtb b/ui/strings/translations/ax_strings_ca.xtb
new file mode 100644
index 0000000..0fbb6dd8
--- /dev/null
+++ b/ui/strings/translations/ax_strings_ca.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="ca">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_cs.xtb b/ui/strings/translations/ax_strings_cs.xtb
new file mode 100644
index 0000000..1c98e10
--- /dev/null
+++ b/ui/strings/translations/ax_strings_cs.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="cs">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_da.xtb b/ui/strings/translations/ax_strings_da.xtb
new file mode 100644
index 0000000..0aebbe9
--- /dev/null
+++ b/ui/strings/translations/ax_strings_da.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="da">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_de.xtb b/ui/strings/translations/ax_strings_de.xtb
new file mode 100644
index 0000000..402a5492
--- /dev/null
+++ b/ui/strings/translations/ax_strings_de.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="de">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_el.xtb b/ui/strings/translations/ax_strings_el.xtb
new file mode 100644
index 0000000..2adcc986
--- /dev/null
+++ b/ui/strings/translations/ax_strings_el.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="el">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_en-GB.xtb b/ui/strings/translations/ax_strings_en-GB.xtb
new file mode 100644
index 0000000..eae2906
--- /dev/null
+++ b/ui/strings/translations/ax_strings_en-GB.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="en-GB">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_es-419.xtb b/ui/strings/translations/ax_strings_es-419.xtb
new file mode 100644
index 0000000..746a72fb
--- /dev/null
+++ b/ui/strings/translations/ax_strings_es-419.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="es-419">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_es.xtb b/ui/strings/translations/ax_strings_es.xtb
new file mode 100644
index 0000000..d65e78ac
--- /dev/null
+++ b/ui/strings/translations/ax_strings_es.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="es">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_et.xtb b/ui/strings/translations/ax_strings_et.xtb
new file mode 100644
index 0000000..0a56c7c
--- /dev/null
+++ b/ui/strings/translations/ax_strings_et.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="et">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_eu.xtb b/ui/strings/translations/ax_strings_eu.xtb
new file mode 100644
index 0000000..dc792d0
--- /dev/null
+++ b/ui/strings/translations/ax_strings_eu.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="eu">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_fa.xtb b/ui/strings/translations/ax_strings_fa.xtb
new file mode 100644
index 0000000..b9004629
--- /dev/null
+++ b/ui/strings/translations/ax_strings_fa.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="fa">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_fi.xtb b/ui/strings/translations/ax_strings_fi.xtb
new file mode 100644
index 0000000..304e292
--- /dev/null
+++ b/ui/strings/translations/ax_strings_fi.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="fi">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_fil.xtb b/ui/strings/translations/ax_strings_fil.xtb
new file mode 100644
index 0000000..49d3dfb4
--- /dev/null
+++ b/ui/strings/translations/ax_strings_fil.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="fil">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_fr-CA.xtb b/ui/strings/translations/ax_strings_fr-CA.xtb
new file mode 100644
index 0000000..43f11e1
--- /dev/null
+++ b/ui/strings/translations/ax_strings_fr-CA.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="fr-CA">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_fr.xtb b/ui/strings/translations/ax_strings_fr.xtb
new file mode 100644
index 0000000..05afae0
--- /dev/null
+++ b/ui/strings/translations/ax_strings_fr.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="fr">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_gl.xtb b/ui/strings/translations/ax_strings_gl.xtb
new file mode 100644
index 0000000..9d51572e
--- /dev/null
+++ b/ui/strings/translations/ax_strings_gl.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="gl">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_gu.xtb b/ui/strings/translations/ax_strings_gu.xtb
new file mode 100644
index 0000000..1d145ee
--- /dev/null
+++ b/ui/strings/translations/ax_strings_gu.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="gu">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_hi.xtb b/ui/strings/translations/ax_strings_hi.xtb
new file mode 100644
index 0000000..2cccc44
--- /dev/null
+++ b/ui/strings/translations/ax_strings_hi.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="hi">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_hr.xtb b/ui/strings/translations/ax_strings_hr.xtb
new file mode 100644
index 0000000..a15480b
--- /dev/null
+++ b/ui/strings/translations/ax_strings_hr.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="hr">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_hu.xtb b/ui/strings/translations/ax_strings_hu.xtb
new file mode 100644
index 0000000..cbd7254
--- /dev/null
+++ b/ui/strings/translations/ax_strings_hu.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="hu">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_hy.xtb b/ui/strings/translations/ax_strings_hy.xtb
new file mode 100644
index 0000000..9565f42
--- /dev/null
+++ b/ui/strings/translations/ax_strings_hy.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="hy">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_id.xtb b/ui/strings/translations/ax_strings_id.xtb
new file mode 100644
index 0000000..916bc58
--- /dev/null
+++ b/ui/strings/translations/ax_strings_id.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="id">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_is.xtb b/ui/strings/translations/ax_strings_is.xtb
new file mode 100644
index 0000000..2c1bf7dd
--- /dev/null
+++ b/ui/strings/translations/ax_strings_is.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="is">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_it.xtb b/ui/strings/translations/ax_strings_it.xtb
new file mode 100644
index 0000000..93f0d4c6
--- /dev/null
+++ b/ui/strings/translations/ax_strings_it.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="it">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_iw.xtb b/ui/strings/translations/ax_strings_iw.xtb
new file mode 100644
index 0000000..f5b2c5bb
--- /dev/null
+++ b/ui/strings/translations/ax_strings_iw.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="iw">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_ja.xtb b/ui/strings/translations/ax_strings_ja.xtb
new file mode 100644
index 0000000..4f05747
--- /dev/null
+++ b/ui/strings/translations/ax_strings_ja.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="ja">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_ka.xtb b/ui/strings/translations/ax_strings_ka.xtb
new file mode 100644
index 0000000..5d10332
--- /dev/null
+++ b/ui/strings/translations/ax_strings_ka.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="ka">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_kk.xtb b/ui/strings/translations/ax_strings_kk.xtb
new file mode 100644
index 0000000..d64a7f82
--- /dev/null
+++ b/ui/strings/translations/ax_strings_kk.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="kk">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_km.xtb b/ui/strings/translations/ax_strings_km.xtb
new file mode 100644
index 0000000..1950f60
--- /dev/null
+++ b/ui/strings/translations/ax_strings_km.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="km">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_kn.xtb b/ui/strings/translations/ax_strings_kn.xtb
new file mode 100644
index 0000000..36efa375
--- /dev/null
+++ b/ui/strings/translations/ax_strings_kn.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="kn">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_ko.xtb b/ui/strings/translations/ax_strings_ko.xtb
new file mode 100644
index 0000000..1fb31f7d
--- /dev/null
+++ b/ui/strings/translations/ax_strings_ko.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="ko">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_ky.xtb b/ui/strings/translations/ax_strings_ky.xtb
new file mode 100644
index 0000000..b8e05b04
--- /dev/null
+++ b/ui/strings/translations/ax_strings_ky.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="ky">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_lo.xtb b/ui/strings/translations/ax_strings_lo.xtb
new file mode 100644
index 0000000..f0eab92
--- /dev/null
+++ b/ui/strings/translations/ax_strings_lo.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="lo">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_lt.xtb b/ui/strings/translations/ax_strings_lt.xtb
new file mode 100644
index 0000000..08c9f2020
--- /dev/null
+++ b/ui/strings/translations/ax_strings_lt.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="lt">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_lv.xtb b/ui/strings/translations/ax_strings_lv.xtb
new file mode 100644
index 0000000..4631fb3
--- /dev/null
+++ b/ui/strings/translations/ax_strings_lv.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="lv">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_mk.xtb b/ui/strings/translations/ax_strings_mk.xtb
new file mode 100644
index 0000000..7d067963
--- /dev/null
+++ b/ui/strings/translations/ax_strings_mk.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="mk">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_ml.xtb b/ui/strings/translations/ax_strings_ml.xtb
new file mode 100644
index 0000000..400e222
--- /dev/null
+++ b/ui/strings/translations/ax_strings_ml.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="ml">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_mn.xtb b/ui/strings/translations/ax_strings_mn.xtb
new file mode 100644
index 0000000..c99cae1f
--- /dev/null
+++ b/ui/strings/translations/ax_strings_mn.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="mn">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_mr.xtb b/ui/strings/translations/ax_strings_mr.xtb
new file mode 100644
index 0000000..4eef17c0
--- /dev/null
+++ b/ui/strings/translations/ax_strings_mr.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="mr">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_ms.xtb b/ui/strings/translations/ax_strings_ms.xtb
new file mode 100644
index 0000000..ba587606
--- /dev/null
+++ b/ui/strings/translations/ax_strings_ms.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="ms">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_my.xtb b/ui/strings/translations/ax_strings_my.xtb
new file mode 100644
index 0000000..a63d14a
--- /dev/null
+++ b/ui/strings/translations/ax_strings_my.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="my">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_ne.xtb b/ui/strings/translations/ax_strings_ne.xtb
new file mode 100644
index 0000000..6b3b68e
--- /dev/null
+++ b/ui/strings/translations/ax_strings_ne.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="ne">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_nl.xtb b/ui/strings/translations/ax_strings_nl.xtb
new file mode 100644
index 0000000..db20e60
--- /dev/null
+++ b/ui/strings/translations/ax_strings_nl.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="nl">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_no.xtb b/ui/strings/translations/ax_strings_no.xtb
new file mode 100644
index 0000000..01e4f570
--- /dev/null
+++ b/ui/strings/translations/ax_strings_no.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="no">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_or.xtb b/ui/strings/translations/ax_strings_or.xtb
new file mode 100644
index 0000000..fee86b93e
--- /dev/null
+++ b/ui/strings/translations/ax_strings_or.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="or">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_pa.xtb b/ui/strings/translations/ax_strings_pa.xtb
new file mode 100644
index 0000000..de0ce5d
--- /dev/null
+++ b/ui/strings/translations/ax_strings_pa.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="pa">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_pl.xtb b/ui/strings/translations/ax_strings_pl.xtb
new file mode 100644
index 0000000..c085470c
--- /dev/null
+++ b/ui/strings/translations/ax_strings_pl.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="pl">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_pt-BR.xtb b/ui/strings/translations/ax_strings_pt-BR.xtb
new file mode 100644
index 0000000..c5ed02c1
--- /dev/null
+++ b/ui/strings/translations/ax_strings_pt-BR.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="pt-BR">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_pt-PT.xtb b/ui/strings/translations/ax_strings_pt-PT.xtb
new file mode 100644
index 0000000..7ae2942
--- /dev/null
+++ b/ui/strings/translations/ax_strings_pt-PT.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="pt-PT">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_ro.xtb b/ui/strings/translations/ax_strings_ro.xtb
new file mode 100644
index 0000000..9c90ff6
--- /dev/null
+++ b/ui/strings/translations/ax_strings_ro.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="ro">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_ru.xtb b/ui/strings/translations/ax_strings_ru.xtb
new file mode 100644
index 0000000..1f1ff19
--- /dev/null
+++ b/ui/strings/translations/ax_strings_ru.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="ru">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_si.xtb b/ui/strings/translations/ax_strings_si.xtb
new file mode 100644
index 0000000..a9bed61
--- /dev/null
+++ b/ui/strings/translations/ax_strings_si.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="si">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_sk.xtb b/ui/strings/translations/ax_strings_sk.xtb
new file mode 100644
index 0000000..f86f7c9e
--- /dev/null
+++ b/ui/strings/translations/ax_strings_sk.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="sk">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_sl.xtb b/ui/strings/translations/ax_strings_sl.xtb
new file mode 100644
index 0000000..187f2d9
--- /dev/null
+++ b/ui/strings/translations/ax_strings_sl.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="sl">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_sq.xtb b/ui/strings/translations/ax_strings_sq.xtb
new file mode 100644
index 0000000..26b43e3f
--- /dev/null
+++ b/ui/strings/translations/ax_strings_sq.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="sq">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_sr-Latn.xtb b/ui/strings/translations/ax_strings_sr-Latn.xtb
new file mode 100644
index 0000000..8d34f69
--- /dev/null
+++ b/ui/strings/translations/ax_strings_sr-Latn.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="sr-Latn">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_sr.xtb b/ui/strings/translations/ax_strings_sr.xtb
new file mode 100644
index 0000000..0e08a5f
--- /dev/null
+++ b/ui/strings/translations/ax_strings_sr.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="sr">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_sv.xtb b/ui/strings/translations/ax_strings_sv.xtb
new file mode 100644
index 0000000..7401494
--- /dev/null
+++ b/ui/strings/translations/ax_strings_sv.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="sv">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_sw.xtb b/ui/strings/translations/ax_strings_sw.xtb
new file mode 100644
index 0000000..ecd2ceac
--- /dev/null
+++ b/ui/strings/translations/ax_strings_sw.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="sw">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_ta.xtb b/ui/strings/translations/ax_strings_ta.xtb
new file mode 100644
index 0000000..80131d9
--- /dev/null
+++ b/ui/strings/translations/ax_strings_ta.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="ta">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_te.xtb b/ui/strings/translations/ax_strings_te.xtb
new file mode 100644
index 0000000..c13003e4
--- /dev/null
+++ b/ui/strings/translations/ax_strings_te.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="te">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_th.xtb b/ui/strings/translations/ax_strings_th.xtb
new file mode 100644
index 0000000..30bb8bc
--- /dev/null
+++ b/ui/strings/translations/ax_strings_th.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="th">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_tr.xtb b/ui/strings/translations/ax_strings_tr.xtb
new file mode 100644
index 0000000..9fe6fa7
--- /dev/null
+++ b/ui/strings/translations/ax_strings_tr.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="tr">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_uk.xtb b/ui/strings/translations/ax_strings_uk.xtb
new file mode 100644
index 0000000..a0fc02c6
--- /dev/null
+++ b/ui/strings/translations/ax_strings_uk.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="uk">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_ur.xtb b/ui/strings/translations/ax_strings_ur.xtb
new file mode 100644
index 0000000..4e2c766a
--- /dev/null
+++ b/ui/strings/translations/ax_strings_ur.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="ur">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_uz.xtb b/ui/strings/translations/ax_strings_uz.xtb
new file mode 100644
index 0000000..e6560a5
--- /dev/null
+++ b/ui/strings/translations/ax_strings_uz.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="uz">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_vi.xtb b/ui/strings/translations/ax_strings_vi.xtb
new file mode 100644
index 0000000..0c8f321
--- /dev/null
+++ b/ui/strings/translations/ax_strings_vi.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="vi">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_zh-CN.xtb b/ui/strings/translations/ax_strings_zh-CN.xtb
new file mode 100644
index 0000000..f3f9e00
--- /dev/null
+++ b/ui/strings/translations/ax_strings_zh-CN.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="zh-CN">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_zh-HK.xtb b/ui/strings/translations/ax_strings_zh-HK.xtb
new file mode 100644
index 0000000..64506dec8
--- /dev/null
+++ b/ui/strings/translations/ax_strings_zh-HK.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="zh-HK">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_zh-TW.xtb b/ui/strings/translations/ax_strings_zh-TW.xtb
new file mode 100644
index 0000000..a6bf0df
--- /dev/null
+++ b/ui/strings/translations/ax_strings_zh-TW.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="zh-TW">
+</translationbundle>
diff --git a/ui/strings/translations/ax_strings_zu.xtb b/ui/strings/translations/ax_strings_zu.xtb
new file mode 100644
index 0000000..27bbe07
--- /dev/null
+++ b/ui/strings/translations/ax_strings_zu.xtb
@@ -0,0 +1,4 @@
+<?xml version="1.0" ?>"
+<!DOCTYPE translationbundle>
+<translationbundle lang="zu">
+</translationbundle>
diff --git a/ui/strings/ui_strings.grd b/ui/strings/ui_strings.grd
index 3b6bbf6..4af00188 100644
--- a/ui/strings/ui_strings.grd
+++ b/ui/strings/ui_strings.grd
@@ -495,480 +495,6 @@
         Upload
       </message>
 
-      <!--Accessible action strings-->
-      <message name="IDS_AX_ACTIVATE_ACTION_VERB" desc="Verb stating the action that will occur when an element such as a text field is selected, as used by accessibility.">
-        activate
-      </message>
-      <message name="IDS_AX_CHECK_ACTION_VERB" desc="Verb stating the action that will occur when an unchecked checkbox is clicked, as used by accessibility.">
-        check
-      </message>
-      <message name="IDS_AX_CLICK_ACTION_VERB" desc="Verb stating the action that will occur when clicking on a generic clickable object, when we don't have a more specific action description, as used by accessibility.">
-        click
-      </message>
-      <message name="IDS_AX_CLICK_ANCESTOR_ACTION_VERB" desc="Verb stating the action that will occur when clicking on a generic object that is not itself clickable but one of its ancestors is, as used by accessibility.">
-        click ancestor
-      </message>
-      <message name="IDS_AX_JUMP_ACTION_VERB" desc="Verb stating the action that will occur when an element such as an in-page link is activated, as used by accessibility.">
-        jump
-      </message>
-      <message name="IDS_AX_OPEN_ACTION_VERB" desc="Verb stating the action that will occur when an element such as a pop-up button is pressed, as used by accessibility.">
-        open
-      </message>
-<message name="IDS_AX_PRESS_ACTION_VERB" desc="Verb stating the action that will occur when an element such as a button is pressed, as used by accessibility.">
-        press
-      </message>
-      <message name="IDS_AX_SELECT_ACTION_VERB" desc="Verb stating the action that will occur when an element such as a radio button is selected, as used by accessibility.">
-        select
-      </message>
-      <message name="IDS_AX_UNCHECK_ACTION_VERB" desc="Verb stating the action that will occur when a checked checkbox is clicked, as used by accessibility.">
-        uncheck
-      </message>
-
-      <!-- Accessible role localized names -->
-      <message name="IDS_AX_ROLE_ARTICLE" desc="Accessibility role description for article">
-        article
-      </message>
-      <!-- https://w3c.github.io/html-aam/#el-audio -->
-      <message name="IDS_AX_ROLE_AUDIO" desc="Accessible role description for audio">
-        audio
-      </message>
-      <message name="IDS_AX_ROLE_BANNER" desc="Accessibility role description for banner">
-        banner
-      </message>
-      <message name="IDS_AX_ROLE_CODE" desc="Accessibility role description for a section whose content represents a fragment of computer code">
-        code
-      </message>
-      <!-- https://w3c.github.io/html-aam/#el-input-color -->
-      <message name="IDS_AX_ROLE_COLOR_WELL" desc="Accessibility role description for a color picker">
-        color picker
-      </message>
-      <message name="IDS_AX_ROLE_COMMENT" desc="Accessibility role description for a single comment">
-        comment
-      </message>
-      <message name="IDS_AX_ROLE_COMPLEMENTARY" desc="Accessibility role description for complementary">
-        complementary
-      </message>
-      <message name="IDS_AX_ROLE_CONTENT_DELETION" desc="Accessibility role description for content deletion, meaning content that is has been or is suggested to be removed from a document, such as in a revision review">
-        deletion
-      </message>
-      <message name="IDS_AX_ROLE_CONTENT_INSERTION" desc="Accessibility role description for content insertion, meaning content that has been marked or is suggested to be inserted into a document, such as in a revision review">
-        insertion
-      </message>
-      <message name="IDS_AX_ROLE_CHECK_BOX" desc="Accessibility role description for a check box">
-        checkbox
-      </message>
-      <message name="IDS_AX_ROLE_CONTENT_INFO" desc="Accessibility role description for credits and information about the content of the page, like copyrights and privacy statements">
-        content information
-      </message>
-      <message name="IDS_AX_ROLE_DATE" desc="Accessibility role description for a date input">
-        date picker
-      </message>
-      <message name="IDS_AX_ROLE_DATE_TIME_LOCAL" desc="Accessibility role description for a datetime-local input">
-        local date and time picker
-      </message>
-      <message name="IDS_AX_ROLE_DEFINITION" desc="Accessibility role description for a definition">
-        definition
-      </message>
-      <message name="IDS_AX_ROLE_DESCRIPTION_LIST" desc="Accessibility role description for a definition list">
-        definition list
-      </message>
-      <message name="IDS_AX_ROLE_DESCRIPTION_TERM" desc="Accessibility role description for description term (as in a description list)">
-        term
-      </message>
-      <!-- https://w3c.github.io/html-aam/#el-details -->
-      <message name="IDS_AX_ROLE_DETAILS" desc="Accessibility role description for details">
-        details
-      </message>
-      <message name="IDS_AX_ROLE_DISCLOSURE_TRIANGLE" desc="Accessibility role description for a disclosure triangle, a control shaped like a triangle that expands or collapses to show or hide extra content">
-        disclosure triangle
-      </message>
-      <message name="IDS_AX_ROLE_DOC_ABSTRACT" desc="Accessibility role description for abstract, meaning the summary of the contents of an article or book">
-        abstract
-      </message>
-      <message name="IDS_AX_ROLE_DOC_ACKNOWLEDGMENTS" desc="Accessibility role description for acknowledgments">
-        acknowledgments
-      </message>
-      <message name="IDS_AX_ROLE_DOC_AFTERWORD" desc="Accessibility role description for afterword">
-        afterword
-      </message>
-      <message name="IDS_AX_ROLE_DOC_APPENDIX" desc="Accessibility role description for appendix, meaning the summary of the contents of an article or book">
-        appendix
-      </message>
-      <message name="IDS_AX_ROLE_DOC_BACKLINK" desc="Accessibility role description for back link">
-        back link
-      </message>
-      <message name="IDS_AX_ROLE_DOC_BIBLIO_ENTRY" desc="Accessibility role description for bibliography entry">
-        bibliography entry
-      </message>
-      <message name="IDS_AX_ROLE_DOC_BIBLIOGRAPHY" desc="Accessibility role description for bibliography">
-        bibliography
-      </message>
-      <message name="IDS_AX_ROLE_DOC_BIBLIO_REF" desc="Accessibility role description for bibliography reference">
-        bibliography reference
-      </message>
-      <message name="IDS_AX_ROLE_DOC_CHAPTER" desc="Accessibility role description for chapter">
-        chapter
-      </message>
-      <message name="IDS_AX_ROLE_DOC_COLOPHON" desc="Accessibility role description for colophon">
-        colophon
-      </message>
-      <message name="IDS_AX_ROLE_DOC_CONCLUSION" desc="Accessibility role description for conclusion">
-        conclusion
-      </message>
-      <message name="IDS_AX_ROLE_DOC_COVER" desc="Accessibility role description for cover">
-        cover
-      </message>
-      <message name="IDS_AX_ROLE_DOC_CREDIT" desc="Accessibility role description for credit, a public acknowledgement of someone who was responsible for helping with something">
-        credit
-      </message>
-      <message name="IDS_AX_ROLE_DOC_CREDITS" desc="Accessibility role description for credits">
-        credits
-      </message>
-      <message name="IDS_AX_ROLE_DOC_DEDICATION" desc="Accessibility role description for dedication">
-        dedication
-      </message>
-      <message name="IDS_AX_ROLE_DOC_ENDNOTE" desc="Accessibility role description for endnote">
-        endnote
-      </message>
-      <message name="IDS_AX_ROLE_DOC_ENDNOTES" desc="Accessibility role description for endnotes">
-        endnotes
-      </message>
-      <message name="IDS_AX_ROLE_DOC_EPIGRAPH" desc="Accessibility role description for epigraph">
-        epigraph
-      </message>
-      <message name="IDS_AX_ROLE_DOC_EPILOGUE" desc="Accessibility role description for epilogue">
-        epilogue
-      </message>
-      <message name="IDS_AX_ROLE_DOC_ERRATA" desc="Accessibility role description for errata">
-        errata
-      </message>
-      <message name="IDS_AX_ROLE_DOC_EXAMPLE" desc="Accessibility role description for example">
-        example
-      </message>
-      <message name="IDS_AX_ROLE_DOC_FOOTNOTE" desc="Accessibility role description for footnote">
-        footnote
-      </message>
-      <message name="IDS_AX_ROLE_DOC_FOREWORD" desc="Accessibility role description for foreword">
-        foreword
-      </message>
-      <message name="IDS_AX_ROLE_DOC_GLOSSARY" desc="Accessibility role description for glossary">
-        glossary
-      </message>
-      <message name="IDS_AX_ROLE_DOC_GLOSS_REF" desc="Accessibility role description for glossary reference">
-        glossary reference
-      </message>
-      <message name="IDS_AX_ROLE_DOC_INDEX" desc="Accessibility role description for index">
-        index
-      </message>
-      <message name="IDS_AX_ROLE_DOC_INTRODUCTION" desc="Accessibility role description for introduction">
-        introduction
-      </message>
-      <message name="IDS_AX_ROLE_DOC_NOTE_REF" desc="Accessibility role description for note reference">
-        note reference
-      </message>
-      <message name="IDS_AX_ROLE_DOC_NOTICE" desc="Accessibility role description for notice">
-        notice
-      </message>
-      <message name="IDS_AX_ROLE_DOC_PAGE_BREAK" desc="Accessibility role description for page break">
-        page break
-      </message>
-      <message name="IDS_AX_ROLE_DOC_PAGE_FOOTER" desc="Accessibility role description for page footer">
-        page footer
-      </message>
-      <message name="IDS_AX_ROLE_DOC_PAGE_HEADER" desc="Accessibility role description for page header">
-        page header
-      </message>
-      <message name="IDS_AX_ROLE_DOC_PAGE_LIST" desc="Accessibility role description for page list">
-        page list
-      </message>
-      <message name="IDS_AX_ROLE_DOC_PART" desc="Accessibility role description for part, as in a part of a book">
-        part
-      </message>
-      <message name="IDS_AX_ROLE_DOC_PREFACE" desc="Accessibility role description for preface">
-        preface
-      </message>
-      <message name="IDS_AX_ROLE_DOC_PROLOGUE" desc="Accessibility role description for prologue">
-        prologue
-      </message>
-      <message name="IDS_AX_ROLE_DOC_PULLQUOTE" desc="Accessibility role description for pullquote">
-        pullquote
-      </message>
-      <message name="IDS_AX_ROLE_DOC_QNA" desc="Accessibility role description for Q+A (questions and answers)">
-        Q&amp;A
-      </message>
-      <message name="IDS_AX_ROLE_DOC_SUBTITLE" desc="Accessibility role description for subtitle">
-        subtitle
-      </message>
-      <message name="IDS_AX_ROLE_DOC_TIP" desc="Accessibility role description for tip, as in a suggestion or idea">
-        tip
-      </message>
-      <message name="IDS_AX_ROLE_DOC_TOC" desc="Accessibility role description for table of contents">
-        table of contents
-      </message>
-      <!-- https://w3c.github.io/html-aam/#el-input-email -->
-      <message name="IDS_AX_ROLE_EMAIL" desc="Accessibility role description for email input">
-        email
-      </message>
-      <message name="IDS_AX_ROLE_EMPHASIS" desc="Accessibility role description for one or more emphasized characters">
-        emphasis
-      </message>
-      <message name="IDS_AX_ROLE_FEED" desc="Accessibility role description for a scrollable list of articles.">
-        feed
-      </message>
-      <message name="IDS_AX_ROLE_FIGURE" desc="Accessibility role description for figure">
-        figure
-      </message>
-      <message name="IDS_AX_ROLE_FORM" desc="Accessibility role description for form">
-        form
-      </message>
-      <message name="IDS_AX_ROLE_FOOTER" desc="Accessibility role description for footers">
-        footer
-      </message>
-      <message name="IDS_AX_ROLE_GRAPHICS_DOCUMENT" desc="Accessibility role description for graphics document">
-        graphics document
-      </message>
-      <message name="IDS_AX_ROLE_GRAPHICS_OBJECT" desc="Accessibility role description for graphics object">
-        graphics object
-      </message>
-      <message name="IDS_AX_ROLE_GRAPHICS_SYMBOL" desc="Accessibility role description for graphics symbol">
-        graphics symbol
-      </message>
-      <message name="IDS_AX_ROLE_HEADER" desc="Accessibility role description for header">
-        header
-      </message>
-      <message name="IDS_AX_ROLE_HEADING" desc="Accessibility role description for headings">
-        heading
-      </message>
-      <message name="IDS_AX_ROLE_LINK" desc="Accessibility role description for link">
-        link
-      </message>
-      <message name="IDS_AX_ROLE_MAIN_CONTENT" desc="Accessibility role description for main content of the document.">
-        main
-      </message>
-      <!-- https://w3c.github.io/html-aam/#el-mark -->
-      <message name="IDS_AX_ROLE_MARK" desc="Accessibility role description for highlighted content.">
-        highlight
-      </message>
-      <message name="IDS_AX_ROLE_MATH" desc="Accessibility role description for math">
-        math
-      </message>
-      <!-- https://w3c.github.io/html-aam/#el-meter -->
-      <message name="IDS_AX_ROLE_METER" desc="Accessibility role description for a meter, for example a temperature indicator or progress bar">
-          meter
-      </message>
-      <message name="IDS_AX_ROLE_MONTH" desc="Accessibility role description for a month input">
-        month picker
-      </message>
-      <message name="IDS_AX_ROLE_NAVIGATIONAL_LINK" desc="Accessibility role description for group of navigational links.">
-        navigation
-      </message>
-      <!-- https://w3c.github.io/html-aam/#el-output -->
-      <message name="IDS_AX_ROLE_OUTPUT" desc="Accessibility role description for output">
-        output
-      </message>
-      <message name="IDS_AX_ROLE_REGION" desc="Accessibility role description for region">
-        region
-      </message>
-      <!-- https://www.w3.org/TR/core-aam-1.1/#role-map-searchbox -->
-      <if expr="is_win">
-        <message name="IDS_AX_ROLE_SEARCH_BOX" desc="UIA role description for search text field">
-          search box
-        </message>
-      </if>
-      <if expr="not is_win">
-        <message name="IDS_AX_ROLE_SEARCH_BOX" desc="Accessibility role description for search text field">
-          search text field
-        </message>
-      </if>
-      <!-- https://w3c.github.io/html-aam/#el-section -->
-      <message name="IDS_AX_ROLE_SECTION" desc="Accessibility role description for a section of a document, like the main body text, the table of contents, a header, or a footer">
-        section
-      </message>
-      <message name="IDS_AX_ROLE_STATUS" desc="Accessibility role description for status">
-        status
-      </message>
-      <if expr="is_macosx">
-        <message name="IDS_AX_ROLE_STEPPER" desc="Accessibility role description for a stepper - a control where you can use up/down arrows to increment or decrement it. The name 'stepper' is how this user interface element is described by VoiceOver on Mac OS X; the translation should be consistent with VoiceOver.">
-          stepper
-        </message>
-      </if>
-      <message name="IDS_AX_ROLE_STRONG" desc="Accessibility role description for content which is important, serious, or urgent">
-        strong
-      </message>
-      <message name="IDS_AX_ROLE_SUBSCRIPT" desc="Accessibility role description for subscript" is_accessibility_with_no_ui="true">
-        subscript
-      </message>
-      <message name="IDS_AX_ROLE_SUGGESTION" desc="Accessibility role description for suggestion, meaning a suggested change to some content">
-        suggestion
-      </message>
-      <message name="IDS_AX_ROLE_SUPERSCRIPT" desc="Accessibility role description for superscript" is_accessibility_with_no_ui="true">
-        superscript
-      </message>
-      <message name="IDS_AX_ROLE_SWITCH" desc="Accessibility role description for switch">
-        switch
-      </message>
-      <message name="IDS_AX_ROLE_TAB" desc="Accessibility role description for a tab">
-        tab
-      </message>
-      <!-- https://w3c.github.io/html-aam/#el-input-tel -->
-      <message name="IDS_AX_ROLE_TELEPHONE" desc="Accessibility role description for telephone number input">
-        telephone
-      </message>
-      <!-- https://w3c.github.io/html-aam/#el-time -->
-      <message name="IDS_AX_ROLE_TIME" desc="Accessibility role description for a time">
-        time
-      </message>
-      <message name="IDS_AX_ROLE_TOGGLE_BUTTON" desc="Accessibility role description for a toggle button">
-        toggle button
-      </message>
-      <!-- https://w3c.github.io/html-aam/#el-input-url -->
-      <message name="IDS_AX_ROLE_URL" desc="Accessibility role description for URL input">
-        url
-      </message>
-      <message name="IDS_AX_ROLE_WEB_AREA" desc="Accessibility role description for web area">
-        HTML content
-      </message>
-      <message name="IDS_AX_ROLE_WEEK" desc="Accessibility role description for a week input">
-        week picker
-      </message>
-      <if expr="is_android">
-        <message name="IDS_AX_ROLE_ALERT" desc="Accessibility role description for an alert">
-          alert
-        </message>
-        <message name="IDS_AX_ROLE_ALERT_DIALOG" desc="Accessibility role description for an alert dialog">
-          alert_dialog
-        </message>
-        <message name="IDS_AX_ROLE_APPLICATION" desc="Accessibility role description for an application">
-          application
-        </message>
-        <message name="IDS_AX_ROLE_BLOCKQUOTE" desc="Accessibility role description for a blockquote">
-          blockquote
-        </message>
-        <message name="IDS_AX_ROLE_BUTTON" desc="Accessibility role description for a button">
-          button
-        </message>
-        <message name="IDS_AX_ROLE_COLUMN_HEADER" desc="Accessibility role description for a column header">
-          column header
-        </message>
-        <message name="IDS_AX_ROLE_COMBO_BOX" desc="Accessibility role description for a combo box, an editable text control with an associated drop-down list">
-          combo box
-        </message>
-        <message name="IDS_AX_ROLE_DATE_TIME" desc="Accessibility role description for a date / time picker">
-          date and time picker
-        </message>
-        <message name="IDS_AX_ROLE_DIALOG" desc="Accessibility role description for a dialog">
-          dialog
-        </message>
-        <message name="IDS_AX_ROLE_DIRECTORY" desc="Accessibility role description for a directory, for example a table of contents or list of employees">
-          directory
-        </message>
-        <message name="IDS_AX_ROLE_DOCUMENT" desc="Accessibility role description for a document">
-          document
-        </message>
-        <message name="IDS_AX_ROLE_EMBEDDED_OBJECT" desc="Accessibility role description for an embedded object, like a video inside a web page">
-          object
-        </message>
-        <message name="IDS_AX_ROLE_GRAPHIC" desc="Accessibility role description for a graphic or image">
-          graphic
-        </message>
-        <message name="IDS_AX_ROLE_HEADING_WITH_LEVEL" desc="Accessibility role description for a heading with a heading level, for example 'heading 1' is the most important heading and 'heading 3' is less important">
-          heading <ph name="HEADING_LEVEL">$1<ex>1</ex></ph>
-        </message>
-        <message name="IDS_AX_ROLE_INPUT_TIME" desc="Accessibility role description for a time picker control">
-          time picker
-        </message>
-        <message name="IDS_AX_ROLE_LIST_BOX" desc="Accessibility role description for a list box control">
-          list box
-        </message>
-        <message name="IDS_AX_ROLE_LOG" desc="Accessibility role description for an information log, such as a chat log or error log">
-          log
-        </message>
-        <message name="IDS_AX_ROLE_MARQUEE" desc="Accessibility role description for a marquee, such as a stock ticker or ad banner that frequently changes">
-          marquee
-        </message>
-        <message name="IDS_AX_ROLE_MENU" desc="Accessibility role description for a menu">
-          menu
-        </message>
-        <message name="IDS_AX_ROLE_MENU_BAR" desc="Accessibility role description for a menu bar">
-          menu bar
-        </message>
-        <message name="IDS_AX_ROLE_MENU_ITEM" desc="Accessibility role description for a menu item">
-          menu item
-        </message>
-        <message name="IDS_AX_ROLE_NOTE" desc="Accessibility role description for a note">
-          note
-        </message>
-        <message name="IDS_AX_ROLE_PDF_HIGHLIGHT" desc="Accessibility role description for PDF highlight.">
-          highlight
-        </message>
-        <message name="IDS_AX_ROLE_POP_UP_BUTTON" desc="Accessibility role description for a pop up button">
-          pop up button
-        </message>
-        <message name="IDS_AX_ROLE_POP_UP_BUTTON_MENU" desc="Accessibility role description for a pop up button that opens a menu">
-          menu pop up button
-        </message>
-        <message name="IDS_AX_ROLE_POP_UP_BUTTON_DIALOG" desc="Accessibility role description for a pop up button that opens a dialog">
-          dialog pop up button
-        </message>
-        <message name="IDS_AX_ROLE_PROGRESS_INDICATOR" desc="Accessibility role description for a progress indicator">
-          progress indicator
-        </message>
-        <message name="IDS_AX_ROLE_RADIO" desc="Accessibility role description for a radio button">
-          radio button
-        </message>
-        <message name="IDS_AX_ROLE_RADIO_GROUP" desc="Accessibility role description for a group of related radio buttons">
-          radio group
-        </message>
-        <message name="IDS_AX_ROLE_ROW_GROUP" desc="Accessibility role description for a row group in a table">
-          row group
-        </message>
-        <message name="IDS_AX_ROLE_ROW_HEADER" desc="Accessibility role description for a row header in a table">
-          row header
-        </message>
-        <message name="IDS_AX_ROLE_SCROLL_BAR" desc="Accessibility role description for a scroll bar">
-          scroll bar
-        </message>
-        <message name="IDS_AX_ROLE_SEARCH" desc="Accessibility role description for the section of a web page containing controls for searching the page or site">
-          search
-        </message>
-        <message name="IDS_AX_ROLE_SLIDER" desc="Accessibility role description for a slider">
-          slider
-        </message>
-        <message name="IDS_AX_ROLE_SPIN_BUTTON" desc="Accessibility role description for a spin button, a control containing a numerical value that can be incremented or decremented by 1">
-          spin button
-        </message>
-        <message name="IDS_AX_ROLE_SPLITTER" desc="Accessibility role description for a splitter, a control that splits the window in two pieces and can usually be dragged">
-          splitter
-        </message>
-        <message name="IDS_AX_ROLE_TABLE" desc="Accessibility role description for a table">
-          table
-        </message>
-        <message name="IDS_AX_ROLE_TAB_LIST" desc="Accessibility role description for a tab list">
-          tab list
-        </message>
-        <message name="IDS_AX_ROLE_TAB_PANEL" desc="Accessibility role description for a tab panel, the part of the window that changes when selecting a new tab">
-          tab panel
-        </message>
-        <message name="IDS_AX_ROLE_TIMER" desc="Accessibility role description for a timer">
-          timer
-        </message>
-        <message name="IDS_AX_ROLE_TOOLBAR" desc="Accessibility role description for a toolbar">
-          toolbar
-        </message>
-        <message name="IDS_AX_ROLE_TOOLTIP" desc="Accessibility role description for a tooltip">
-          tooltip
-        </message>
-        <message name="IDS_AX_ROLE_TREE" desc="Accessibility role description for a tree">
-          tree
-        </message>
-        <message name="IDS_AX_ROLE_TREE_GRID" desc="Accessibility role description for a tree grid">
-          tree grid
-        </message>
-        <message name="IDS_AX_ROLE_TREE_ITEM" desc="Accessibility role description for a tree item">
-          tree item
-        </message>
-      </if>
-
       <!--Accessible name strings-->
       <message name="IDS_APP_ACCNAME_BACK" desc="The accessible name for the back button on the window frame.">
         Back button
diff --git a/weblayer/shell/BUILD.gn b/weblayer/shell/BUILD.gn
index a5f7cc0..25218e9 100644
--- a/weblayer/shell/BUILD.gn
+++ b/weblayer/shell/BUILD.gn
@@ -158,6 +158,7 @@
     "$root_gen_dir/ui/resources/webui_generated_resources.pak",
     "$root_gen_dir/ui/resources/webui_resources.pak",
     "$root_gen_dir/ui/strings/app_locale_settings_en-US.pak",
+    "$root_gen_dir/ui/strings/ax_strings_en-US.pak",
     "$root_gen_dir/ui/strings/ui_strings_en-US.pak",
     "$root_gen_dir/weblayer/weblayer_resources.pak",
   ]