diff --git a/DEPS b/DEPS
index 9f95a75..4864fa0 100644
--- a/DEPS
+++ b/DEPS
@@ -269,7 +269,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'faa99cb9314f9d14da7aa4e0f396987db66a49ee',
+  'skia_revision': '6080d9df9ef71fb84fc4a0a282ba3252df58969d',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -277,11 +277,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'dd9b4afe5645890638655a740fb9fe381a663779',
+  'angle_revision': '1d8227dab6f14dc344fffc3a6b18eae24336faf2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': 'e0a1188d76522d47c84c71a42b767ac19d3c7893',
+  'swiftshader_revision': 'f1c2c0b0728152d7ca24472e696e07e957e6d09a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
@@ -296,7 +296,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Fuchsia sdk
   # and whatever else without interference from each other.
-  'fuchsia_version': 'version:8.20220511.0.1',
+  'fuchsia_version': 'version:8.20220511.3.1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling google-toolbox-for-mac
   # and whatever else without interference from each other.
@@ -340,7 +340,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': 'd1ddc12119ab1365426dac869c09711ee3a463ed',
+  'catapult_revision': 'e9b55266586c1188837e156587ff75221ed18120',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -384,11 +384,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '75a31aa10aa6767d6dd1906568b93345b3c16bb9',
+  'dawn_revision': '6400e3bfc0cd31cb0edd3cd10b0c34588c864351',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'quiche_revision': '327663d2cc0a59e75d6af13992dc734b49d209de',
+  'quiche_revision': '6139e0819ba88e6112d22832f73f77759201bf3f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -741,11 +741,11 @@
     Var('chromium_git') + '/external/github.com/toji/webvr.info.git' + '@' + 'c58ae99b9ff9e2aa4c524633519570bf33536248',
 
   'src/docs/website': {
-    'url': Var('chromium_git') + '/website.git' + '@' + 'd9af1a84b16b8538baa67b2522eb27b14a5df003',
+    'url': Var('chromium_git') + '/website.git' + '@' + '10d617dd2e8f68be93a818825539621ef033bf09',
   },
 
   'src/ios/third_party/earl_grey2/src': {
-      'url': Var('chromium_git') + '/external/github.com/google/EarlGrey.git' + '@' + 'bd0a99d9942d0cfc755f76e8fb8b23840ed0e317',
+      'url': Var('chromium_git') + '/external/github.com/google/EarlGrey.git' + '@' + 'd28ba1132c96e3267db78ceef59c07486fad3d30',
       'condition': 'checkout_ios',
   },
 
@@ -821,7 +821,7 @@
   },
 
   'src/media/cdm/api':
-    Var('chromium_git') + '/chromium/cdm.git' + '@' + 'fc5afac6847dc61addc1177103aa602e71a9ecac',
+    Var('chromium_git') + '/chromium/cdm.git' + '@' + 'fef0b5aa1bd31efb88dfab804bdbe614f3d54f28',
 
   'src/native_client': {
       'url': Var('chromium_git') + '/native_client/src/native_client.git' + '@' + Var('nacl_revision'),
@@ -914,7 +914,7 @@
     'packages': [
       {
           'package': 'chromium/third_party/androidx',
-          'version': '2V7ZDLIKJDlBnGSSJXQDu9ZmBIHGqJzUGaNLc9A8nkoC',
+          'version': 'hBgSeEmZ8GuP4BLyiCIFTDSRneDoyni6mk1go9MHX_IC',
       },
     ],
     'condition': 'checkout_android',
@@ -947,7 +947,7 @@
       'packages': [
           {
               'package': 'chromium/third_party/android_build_tools/aapt2',
-              'version': 'RDutOGK_MVVg63biRaUn8n43zaISYedSEtTJAw-gSegC',
+              'version': 'kZqQH92bSO1p0a7_hcrana_9YjtSBU1te7TEtNVBoCUC',
           },
       ],
       'condition': 'checkout_android',
@@ -1008,6 +1008,9 @@
   'src/third_party/angle':
     Var('chromium_git') + '/angle/angle.git' + '@' +  Var('angle_revision'),
 
+  'src/third_party/content_analysis_sdk/src':
+    Var('chromium_git') + '/external/github.com/chromium/content_analysis_sdk.git' + '@' + 'ffaa2941d25f9321b4ab4f7beacd729494ae144c',
+
   'src/third_party/dav1d/libdav1d':
     Var('chromium_git') + '/external/github.com/videolan/dav1d.git' + '@' + '87f9a81cd770e49394a45deca7a3df41243de00b',
 
@@ -1130,7 +1133,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '31bfd519956b011b822769f227fd7dcf679f2f43',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '9997ceb9a1c8680b029e85dc9fe7515dec23cf69',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -1338,7 +1341,7 @@
 
   'src/third_party/jsoncpp/source':
     Var('chromium_git') + '/external/github.com/open-source-parsers/jsoncpp.git'
-      + '@' + '9059f5cad030ba11d37818847443a53918c327b1', # release 1.9.4
+      + '@' + '42e892d96e47b1f6e29844cc705e148ec4856448', # release 1.9.4
 
   'src/third_party/junit/src': {
       'url': Var('chromium_git') + '/external/junit.git' + '@' + '64155f8a9babcfcf4263cf4d08253a1556e75481',
@@ -1408,7 +1411,7 @@
   },
 
   'src/third_party/libunwindstack': {
-      'url': Var('chromium_git') + '/chromium/src/third_party/libunwindstack.git' + '@' + '6868358481bb1e5e20d155c1084dc436c88b5e6b',
+      'url': Var('chromium_git') + '/chromium/src/third_party/libunwindstack.git' + '@' + '3c86843ae0f8d560ae0d15b92e34ce88cf83057a',
       'condition': 'checkout_android',
   },
 
@@ -1527,7 +1530,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '478630351306440b9bfb971ee7914704fecacd75',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '14feb311906a9adf5ada506bc925c31637ba8983',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1663,7 +1666,7 @@
       'condition': 'checkout_android',
   },
 
-  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@3e76ffe3d4b39f7ec38581712dbde5c2e63ef0fb',
+  'src/third_party/vulkan-deps': '{chromium_git}/vulkan-deps@c6a60f3cc711e0758ebb58d8d1db9f97b5973ccc',
 
   'src/third_party/vulkan_memory_allocator':
     Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git' + '@' + 'ebe84bec02c041d28f902da0214bf442743fc907',
@@ -1699,10 +1702,10 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'cf04aebdf9b53bb2853f22a81465688daf879ec6',
 
   'src/third_party/webgpu-cts/src':
-    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '09bcde2fd4ba4c265001a6cb394447f6a4f27671',
+    Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + '6822b938953a4ce890773fe7d0b241b7144aac84',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '9e5aeb9d92981818f1ed6dd9719df2edf0e373f0',
+    Var('webrtc_git') + '/src.git' + '@' + 'b17745973f39275683b6d90d62d386ac23e1b4d2',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1775,7 +1778,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@070bf9de22add8973cbef0e81336da9463d7397b',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@00582901c2c7a797273509470561c6f5b6911c1d',
     'condition': 'checkout_src_internal',
   },
 
@@ -1783,7 +1786,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/assistant/ambient',
-        'version': 'version:feel_the_breeze_performance',
+        'version': 'version:float_on_by_background_color_fix',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -1827,7 +1830,7 @@
     'packages': [
       {
         'package': 'chromeos_internal/apps/projector_app/app',
-        'version': 'Ouuh_QVH5Vhs25lO6hUI1P7F9kceKy2xDp23WlML33cC',
+        'version': 'YSthu9jwblc2473ndWEBRDLA_QkZHoBZRg9YQSqM0gMC',
       },
     ],
     'condition': 'checkout_chromeos and checkout_src_internal',
@@ -4215,6 +4218,18 @@
     ],
   },
 
+  # Download test data for Maps telemetry_gpu_integration_test.
+  {
+    'name': 'maps_perf_test_load_dataset',
+    'pattern': '\\.sha1',
+    'action': [ 'python3',
+                'src/third_party/depot_tools/download_from_google_storage.py',
+                '--no_resume',
+                '--no_auth',
+                '--bucket', 'chromium-telemetry',
+                '-s', 'src/tools/perf/page_sets/maps_perf_test/load_dataset.sha1',
+    ],
+  },
 
   # This is used to ensure that all network operations are properly
   # annotated so we can document what they're for.
diff --git a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
index d1b09ab5..bea8957 100644
--- a/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/ProductionSupportedFlagList.java
@@ -251,6 +251,8 @@
                     "Enables the <param> element's URL-setting features (this functionality is"
                             + " being deprecated and removed, so ENABLED is the safe/current"
                             + " behavior, and DISABLED is the tested/new behavior)."),
+            Flag.baseFeature(ContentFeatures.PRELOAD_COOKIES,
+                    "Enables preload cookie database on NetworkContext creation."),
             Flag.baseFeature(ContentFeatures.NAVIGATION_REQUEST_PRECONNECT,
                     "Enables preconnecting for frame requests."),
             Flag.baseFeature(ContentFeatures.NAVIGATION_NETWORK_RESPONSE_QUEUE,
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index c5570549..337846e 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -290,6 +290,8 @@
     "capture_mode/capture_mode_source_view.cc",
     "capture_mode/capture_mode_source_view.h",
     "capture_mode/capture_mode_test_api.cc",
+    "capture_mode/capture_mode_toast_controller.cc",
+    "capture_mode/capture_mode_toast_controller.h",
     "capture_mode/capture_mode_toggle_button.cc",
     "capture_mode/capture_mode_toggle_button.h",
     "capture_mode/capture_mode_type_view.cc",
@@ -1841,8 +1843,6 @@
     "wm/desks/root_window_desk_switch_animator.h",
     "wm/desks/scroll_arrow_button.cc",
     "wm/desks/scroll_arrow_button.h",
-    "wm/desks/templates/desks_templates_metrics_util.cc",
-    "wm/desks/templates/desks_templates_metrics_util.h",
     "wm/desks/templates/restore_data_collector.cc",
     "wm/desks/templates/restore_data_collector.h",
     "wm/desks/templates/save_desk_template_button.cc",
@@ -1863,6 +1863,8 @@
     "wm/desks/templates/saved_desk_item_view.h",
     "wm/desks/templates/saved_desk_library_view.cc",
     "wm/desks/templates/saved_desk_library_view.h",
+    "wm/desks/templates/saved_desk_metrics_util.cc",
+    "wm/desks/templates/saved_desk_metrics_util.h",
     "wm/desks/templates/saved_desk_name_view.cc",
     "wm/desks/templates/saved_desk_name_view.h",
     "wm/desks/templates/saved_desk_presenter.cc",
@@ -2865,7 +2867,7 @@
     "wm/desks/desk_animation_impl_unittest.cc",
     "wm/desks/desks_unittests.cc",
     "wm/desks/root_window_desk_switch_animator_unittest.cc",
-    "wm/desks/templates/desks_templates_unittest.cc",
+    "wm/desks/templates/saved_desk_unittest.cc",
     "wm/drag_window_resizer_unittest.cc",
     "wm/float/float_controller_unittest.cc",
     "wm/fullscreen_window_finder_unittest.cc",
@@ -3331,8 +3333,8 @@
     "wm/desks/desks_test_util.h",
     "wm/desks/root_window_desk_switch_animator_test_api.cc",
     "wm/desks/root_window_desk_switch_animator_test_api.h",
-    "wm/desks/templates/desks_templates_test_util.cc",
-    "wm/desks/templates/desks_templates_test_util.h",
+    "wm/desks/templates/saved_desk_test_util.cc",
+    "wm/desks/templates/saved_desk_test_util.h",
     "wm/gestures/back_gesture/test_back_gesture_contextual_nudge_delegate.cc",
     "wm/gestures/back_gesture/test_back_gesture_contextual_nudge_delegate.h",
     "wm/lock_state_controller_test_api.cc",
diff --git a/ash/accelerators/accelerator_commands.cc b/ash/accelerators/accelerator_commands.cc
index 7b254eb..2dc8ad8 100644
--- a/ash/accelerators/accelerator_commands.cc
+++ b/ash/accelerators/accelerator_commands.cc
@@ -17,8 +17,10 @@
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
 #include "ash/system/keyboard_brightness_control_delegate.h"
+#include "ash/system/model/system_tray_model.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/system/time/calendar_metrics.h"
+#include "ash/system/time/calendar_model.h"
 #include "ash/system/unified/unified_system_tray.h"
 #include "ash/system/unified/unified_system_tray_bubble.h"
 #include "ash/wm/float/float_controller.h"
@@ -55,6 +57,10 @@
 
 }  // namespace
 
+void DumpCalendarModel() {
+  Shell::Get()->system_tray_model()->calendar_model()->DebugDump();
+}
+
 void CycleBackwardMru() {
   Shell::Get()->window_cycle_controller()->HandleCycleWindow(
       WindowCycleController::WindowCyclingDirection::kBackward);
diff --git a/ash/accelerators/accelerator_commands.h b/ash/accelerators/accelerator_commands.h
index 6ef98ed..d09a452 100644
--- a/ash/accelerators/accelerator_commands.h
+++ b/ash/accelerators/accelerator_commands.h
@@ -14,6 +14,9 @@
 namespace ash {
 namespace accelerators {
 
+// Logs a dump of CalendarModel internal data.
+ASH_EXPORT void DumpCalendarModel();
+
 // Cycle backwards in the MRU window list. Usually Alt-Shift-Tab.
 ASH_EXPORT void CycleBackwardMru();
 
diff --git a/ash/accelerators/accelerator_controller_impl.cc b/ash/accelerators/accelerator_controller_impl.cc
index 9e84825..cbe8933e 100644
--- a/ash/accelerators/accelerator_controller_impl.cc
+++ b/ash/accelerators/accelerator_controller_impl.cc
@@ -1891,6 +1891,7 @@
     case DESKS_ACTIVATE_7:
     case DESKS_TOGGLE_ASSIGN_TO_ALL_DESKS:
       return true;
+    case DEBUG_DUMP_CALENDAR_MODEL:
     case DEBUG_KEYBOARD_BACKLIGHT_TOGGLE:
     case DEBUG_MICROPHONE_MUTE_TOGGLE:
     case DEBUG_PRINT_LAYER_HIERARCHY:
@@ -2132,6 +2133,7 @@
     case DESKS_TOGGLE_ASSIGN_TO_ALL_DESKS:
       HandleToggleAssignToAllDesks();
       break;
+    case DEBUG_DUMP_CALENDAR_MODEL:
     case DEBUG_KEYBOARD_BACKLIGHT_TOGGLE:
     case DEBUG_MICROPHONE_MUTE_TOGGLE:
     case DEBUG_PRINT_LAYER_HIERARCHY:
diff --git a/ash/accelerators/accelerator_table.cc b/ash/accelerators/accelerator_table.cc
index c1c1d659..4675015 100644
--- a/ash/accelerators/accelerator_table.cc
+++ b/ash/accelerators/accelerator_table.cc
@@ -63,6 +63,7 @@
 
 const AcceleratorData kDebugAcceleratorData[] = {
     {true, ui::VKEY_N, kDebugModifier, TOGGLE_WIFI},
+    {true, ui::VKEY_C, kDebugModifier, DEBUG_DUMP_CALENDAR_MODEL},
     {true, ui::VKEY_X, kDebugModifier, DEBUG_KEYBOARD_BACKLIGHT_TOGGLE},
     {true, ui::VKEY_M, kDebugModifier, DEBUG_MICROPHONE_MUTE_TOGGLE},
     {true, ui::VKEY_O, kDebugModifier, DEBUG_SHOW_TOAST},
@@ -216,6 +217,7 @@
 const AcceleratorAction kActionsAllowedAtModalWindow[] = {
     BRIGHTNESS_DOWN,
     BRIGHTNESS_UP,
+    DEBUG_DUMP_CALENDAR_MODEL,
     DEBUG_KEYBOARD_BACKLIGHT_TOGGLE,
     DEBUG_MICROPHONE_MUTE_TOGGLE,
     DEBUG_TOGGLE_TOUCH_PAD,
@@ -296,6 +298,7 @@
 const AcceleratorAction kActionsAllowedInAppModeOrPinnedMode[] = {
     BRIGHTNESS_DOWN,
     BRIGHTNESS_UP,
+    DEBUG_DUMP_CALENDAR_MODEL,
     DEBUG_KEYBOARD_BACKLIGHT_TOGGLE,
     DEBUG_MICROPHONE_MUTE_TOGGLE,
     DEBUG_PRINT_LAYER_HIERARCHY,
@@ -388,6 +391,7 @@
 const AcceleratorAction kActionsKeepingMenuOpen[] = {
     BRIGHTNESS_DOWN,
     BRIGHTNESS_UP,
+    DEBUG_DUMP_CALENDAR_MODEL,
     DEBUG_KEYBOARD_BACKLIGHT_TOGGLE,
     DEBUG_MICROPHONE_MUTE_TOGGLE,
     DEBUG_TOGGLE_TOUCH_PAD,
diff --git a/ash/accelerators/debug_commands.cc b/ash/accelerators/debug_commands.cc
index e0b87ecd..64fc1bb 100644
--- a/ash/accelerators/debug_commands.cc
+++ b/ash/accelerators/debug_commands.cc
@@ -102,6 +102,10 @@
   }
 }
 
+void HandleDumpCalendarModel() {
+  accelerators::DumpCalendarModel();
+}
+
 void HandleToggleKeyboardBacklight() {
   if (ash::features::IsKeyboardBacklightToggleEnabled()) {
     base::RecordAction(base::UserMetricsAction("Accel_Keyboard_Backlight"));
@@ -166,6 +170,9 @@
     return;
 
   switch (action) {
+    case DEBUG_DUMP_CALENDAR_MODEL:
+      HandleDumpCalendarModel();
+      break;
     case DEBUG_KEYBOARD_BACKLIGHT_TOGGLE:
       HandleToggleKeyboardBacklight();
       break;
diff --git a/ash/app_list/views/app_list_bubble_view.cc b/ash/app_list/views/app_list_bubble_view.cc
index 0d390512..6d22ada 100644
--- a/ash/app_list/views/app_list_bubble_view.cc
+++ b/ash/app_list/views/app_list_bubble_view.cc
@@ -300,6 +300,13 @@
     Layout();
   DCHECK(!needs_layout());
 
+  ui::AnimationThroughputReporter reporter(
+      layer()->GetAnimator(),
+      metrics_util::ForSmoothness(base::BindRepeating([](int value) {
+        base::UmaHistogramPercentage(
+            "Apps.ClamshellLauncher.AnimationSmoothness.Open", value);
+      })));
+
   // Animation specification for bottom shelf:
   //
   // Y Position: Down 8px → End position (visually moves up)
diff --git a/ash/app_list/views/app_list_bubble_view_unittest.cc b/ash/app_list/views/app_list_bubble_view_unittest.cc
index 1746640c..8900ef2 100644
--- a/ash/app_list/views/app_list_bubble_view_unittest.cc
+++ b/ash/app_list/views/app_list_bubble_view_unittest.cc
@@ -455,6 +455,8 @@
   // Smoothness was recorded.
   histograms.ExpectTotalCount(
       "Apps.ClamshellLauncher.AnimationSmoothness.OpenAppsPage", 1);
+  histograms.ExpectTotalCount("Apps.ClamshellLauncher.AnimationSmoothness.Open",
+                              1);
 }
 
 TEST_F(AppListBubbleViewTest, HideAnimationsRecordsSmoothnessHistogram) {
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 872845ab..696caa96 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -4007,6 +4007,9 @@
       <message name="IDS_ASH_SCREEN_CAPTURE_SHOW_CAMERA_USER_NUDGE" desc="The message shown in a toast widget to nudge the user and alert them to check out the new settings that allow them to show camera during video recording.">
         You can now record yourself and your screen at the same time
       </message>
+      <message name="IDS_ASH_SCREEN_CAPTURE_SURFACE_TOO_SMALL_USER_NUDGE" desc="The message shown in a toast widget to alert the user that the current capture surface is too small to show the camera preview.">
+        Region is too small to fit camera
+      </message>
       <message name="IDS_ASH_SCREEN_CAPTURE_TOOLTIP_COLLAPSE_SELFIE_CAMERA" desc="Tooltip of the collapse resize button for screen capture selfie camera.">
         Collapse camera
       </message>
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_SURFACE_TOO_SMALL_USER_NUDGE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_SURFACE_TOO_SMALL_USER_NUDGE.png.sha1
new file mode 100644
index 0000000..65979b5
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_SURFACE_TOO_SMALL_USER_NUDGE.png.sha1
@@ -0,0 +1 @@
+20b537efce6190a3c41db3f984008536ebf385b4
\ No newline at end of file
diff --git a/ash/capture_mode/capture_mode_camera_controller.cc b/ash/capture_mode/capture_mode_camera_controller.cc
index c93025cd..8a37cd0 100644
--- a/ash/capture_mode/capture_mode_camera_controller.cc
+++ b/ash/capture_mode/capture_mode_camera_controller.cc
@@ -71,8 +71,6 @@
 constexpr base::TimeDelta kCameraPreviewFadeInDuration =
     base::Milliseconds(150);
 
-constexpr float kCameraPreviewScaleUpFactor = 0.8f;
-
 // Defines a map type to map a camera model ID (or display name) to the number
 // of cameras of that model that are currently connected.
 using ModelIdToCountMap = std::map<std::string, int>;
@@ -349,6 +347,14 @@
   aura::Window* const camera_preview_window_;
 };
 
+capture_mode_util::AnimationParams BuildCameraVisibilityAnimationParams(
+    bool target_visibility,
+    bool apply_scale_up_animation) {
+  return {target_visibility ? kCameraPreviewFadeInDuration
+                            : kCameraPreviewFadeOutDuration,
+          gfx::Tween::LINEAR, apply_scale_up_animation};
+}
+
 }  // namespace
 
 // -----------------------------------------------------------------------------
@@ -547,12 +553,22 @@
           CalculatePreviewWidgetTargetBounds(confine_bounds, size_specs.size),
           animate);
 
-  const bool did_visibility_change = SetCameraPreviewVisibility(
-      size_specs.should_be_visible, should_animate_visibility);
+  const bool did_visibility_change = capture_mode_util::SetWidgetVisibility(
+      camera_preview_widget_.get(), size_specs.should_be_visible,
+      !should_animate_visibility
+          ? absl::nullopt
+          : absl::make_optional<capture_mode_util::AnimationParams>(
+                BuildCameraVisibilityAnimationParams(
+                    /*target_visibility=*/size_specs.should_be_visible,
+                    /*apply_scale_up_animation=*/is_first_bounds_update_)));
 
-  if (controller->IsActive() && (did_visibility_change || did_bounds_change)) {
+  if (controller->IsActive() && !controller->is_recording_in_progress()) {
     controller->capture_mode_session()
-        ->OnCameraPreviewBoundsOrVisibilityChanged();
+        ->OnCameraPreviewBoundsOrVisibilityChanged(
+            /*capture_surface_became_too_small=*/size_specs
+                .is_surface_too_small,
+            /*did_bounds_or_visibility_change=*/did_visibility_change ||
+                did_bounds_change);
   }
 
   if (did_bounds_change) {
@@ -1017,103 +1033,6 @@
   }
 }
 
-bool CaptureModeCameraController::SetCameraPreviewVisibility(
-    bool target_visibility,
-    bool animate) {
-  DCHECK(camera_preview_widget_);
-
-  // Note that we use `aura::Window::TargetVisibility()` rather than
-  // `views::Widget::IsVisible()` (which in turn uses
-  // `aura::Window::IsVisible()`). The reason is because the latter takes into
-  // account whether window's layer is drawn or not. We want to calculate the
-  // current visibility only based on the actual visibility of the window
-  // itself, so that we can correctly compare it against `target_visibility`.
-  // Note that the preview may be a child of the unparented container (which is
-  // always hidden), yet the preview's window is shown.
-  const bool current_visibility =
-      camera_preview_widget_->GetNativeWindow()->TargetVisibility() &&
-      camera_preview_widget_->GetLayer()->GetTargetOpacity() > 0.f;
-  if (target_visibility == current_visibility)
-    return false;
-
-  if (animate) {
-    if (target_visibility)
-      FadeInCameraPreview();
-    else
-      FadeOutCameraPreview();
-  } else {
-    if (target_visibility)
-      camera_preview_widget_->Show();
-    else
-      camera_preview_widget_->Hide();
-  }
-
-  capture_mode_util::TriggerAccessibilityAlertSoon(
-      current_visibility ? IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_HIDDEN
-                         : IDS_ASH_SCREEN_CAPTURE_CAMERA_PREVIEW_ON);
-  return true;
-}
-
-void CaptureModeCameraController::FadeInCameraPreview() {
-  DCHECK(camera_preview_widget_);
-  auto* layer = camera_preview_widget_->GetLayer();
-  DCHECK(!camera_preview_widget_->GetNativeWindow()->TargetVisibility() ||
-         layer->GetTargetOpacity() < 1.f);
-
-  if (!camera_preview_widget_->GetNativeWindow()->TargetVisibility())
-    camera_preview_widget_->Show();
-  if (layer->opacity() == 1.f)
-    layer->SetOpacity(0.f);
-
-  if (is_first_bounds_update_) {
-    layer->SetTransform(capture_mode_util::GetScaleTransformAboutCenter(
-        layer, kCameraPreviewScaleUpFactor));
-  }
-
-  views::AnimationBuilder builder;
-  auto& animation_sequence_block =
-      builder
-          .SetPreemptionStrategy(
-              ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
-          .Once()
-          .SetDuration(kCameraPreviewFadeInDuration)
-          .SetOpacity(layer, 1.f, gfx::Tween::LINEAR);
-
-  // We should only set transform here if `is_first_bounds_update_` is true,
-  // otherwise, it may mess up with the snap animation in
-  // `SetCameraPreviewBounds`.
-  if (is_first_bounds_update_) {
-    animation_sequence_block.SetTransform(layer, gfx::Transform(),
-                                          gfx::Tween::ACCEL_20_DECEL_100);
-  }
-}
-
-void CaptureModeCameraController::FadeOutCameraPreview() {
-  DCHECK(camera_preview_widget_);
-  DCHECK(camera_preview_widget_->GetNativeWindow()->TargetVisibility());
-
-  auto* layer = camera_preview_widget_->GetLayer();
-  DCHECK_EQ(layer->GetTargetOpacity(), 1.f);
-
-  views::AnimationBuilder()
-      .OnEnded(base::BindOnce(
-          [](base::WeakPtr<CaptureModeCameraController> controller) {
-            if (!controller || !controller->camera_preview_widget_)
-              return;
-            // Please notice, the order matters here. If we set the layer's
-            // opacity back to 1.f before calling `Hide`, flickering can be
-            // seen.
-            controller->camera_preview_widget_->Hide();
-            controller->camera_preview_widget_->GetLayer()->SetOpacity(1.f);
-          },
-          weak_ptr_factory_.GetWeakPtr()))
-      .SetPreemptionStrategy(
-          ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
-      .Once()
-      .SetDuration(kCameraPreviewFadeOutDuration)
-      .SetOpacity(layer, 0.f, gfx::Tween::LINEAR);
-}
-
 bool CaptureModeCameraController::SetCameraPreviewBounds(
     const gfx::Rect& target_bounds,
     bool animate) {
diff --git a/ash/capture_mode/capture_mode_camera_controller.h b/ash/capture_mode/capture_mode_camera_controller.h
index d6d0666..a6687508 100644
--- a/ash/capture_mode/capture_mode_camera_controller.h
+++ b/ash/capture_mode/capture_mode_camera_controller.h
@@ -314,16 +314,6 @@
   // panels.
   void RunPostRefreshCameraPreview(bool was_preview_visible_before);
 
-  // Sets the visibility of the camera preview to the given `target_visibility`
-  // and returns true only if the `target_visibility` is different than the
-  // current.
-  bool SetCameraPreviewVisibility(bool target_visibility, bool animate);
-
-  // Fades in or out the `camera_preview_widget_` and updates its visibility
-  // accordingly.
-  void FadeInCameraPreview();
-  void FadeOutCameraPreview();
-
   // Sets the given `target_bounds` on the camera preview widget, potentially
   // animating to it if `animate` is true. Returns true if the bounds actually
   // changed from the current.
diff --git a/ash/capture_mode/capture_mode_camera_preview_view.cc b/ash/capture_mode/capture_mode_camera_preview_view.cc
index 8b2266f6..492d3a4 100644
--- a/ash/capture_mode/capture_mode_camera_preview_view.cc
+++ b/ash/capture_mode/capture_mode_camera_preview_view.cc
@@ -7,6 +7,7 @@
 #include "ash/accessibility/scoped_a11y_override_window_setter.h"
 #include "ash/capture_mode/capture_mode_constants.h"
 #include "ash/capture_mode/capture_mode_controller.h"
+#include "ash/capture_mode/capture_mode_session.h"
 #include "ash/capture_mode/capture_mode_util.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
@@ -124,7 +125,11 @@
   UpdateResizeButtonTooltip();
 }
 
-CameraPreviewView::~CameraPreviewView() = default;
+CameraPreviewView::~CameraPreviewView() {
+  auto* controller = CaptureModeController::Get();
+  if (controller->IsActive() && !controller->is_recording_in_progress())
+    controller->capture_mode_session()->OnCameraPreviewDestroyed();
+}
 
 void CameraPreviewView::SetIsCollapsible(bool value) {
   if (value != is_collapsible_) {
diff --git a/ash/capture_mode/capture_mode_camera_unittests.cc b/ash/capture_mode/capture_mode_camera_unittests.cc
index 2ee3450c..6c7de052 100644
--- a/ash/capture_mode/capture_mode_camera_unittests.cc
+++ b/ash/capture_mode/capture_mode_camera_unittests.cc
@@ -17,6 +17,7 @@
 #include "ash/capture_mode/capture_mode_settings_test_api.h"
 #include "ash/capture_mode/capture_mode_settings_view.h"
 #include "ash/capture_mode/capture_mode_test_util.h"
+#include "ash/capture_mode/capture_mode_toast_controller.h"
 #include "ash/capture_mode/capture_mode_toggle_button.h"
 #include "ash/capture_mode/capture_mode_types.h"
 #include "ash/capture_mode/capture_mode_util.h"
@@ -162,6 +163,12 @@
   base::RunLoop wait_loop_;
 };
 
+gfx::Rect GetTooSmallToFitCameraRegion() {
+  return {100, 100,
+          capture_mode::kMinCaptureSurfaceShortSideLengthForVisibleCamera - 1,
+          capture_mode::kMinCaptureSurfaceShortSideLengthForVisibleCamera - 1};
+}
+
 }  // namespace
 
 class CaptureModeCameraTest : public AshTestBase {
@@ -2535,6 +2542,309 @@
   }
 }
 
+TEST_F(CaptureModeCameraTest, ToastVisibilityChangeOnCaptureRegionUpdated) {
+  UpdateDisplay("800x600");
+
+  auto* controller =
+      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
+  auto* capture_session = controller->capture_mode_session();
+  auto* camera_controller = GetCameraController();
+  auto* capture_toast_controller = capture_session->capture_toast_controller();
+  AddDefaultCamera();
+  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
+
+  // Set capture region big enough to fit the camera preview. Verify the
+  // current capture toast is `kUserNudge`.
+  const gfx::Rect capture_region(100, 100, 300, 300);
+  SelectCaptureRegion(capture_region);
+  auto* capture_toast_widget = capture_toast_controller->capture_toast_widget();
+  EXPECT_TRUE(capture_toast_widget);
+  ASSERT_TRUE(capture_toast_controller->current_toast_type());
+  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
+            CaptureToastType::kUserNudge);
+
+  // Update capture region small enough to not fit the camera preview. Verify
+  // that the capture toast is updated to `kCameraPreview` and the user nudge is
+  // dismissed forever.
+  const int delta_x =
+      capture_mode::kMinCaptureSurfaceShortSideLengthForVisibleCamera - 30 -
+      capture_region.width();
+  const int delta_y =
+      capture_mode::kMinCaptureSurfaceShortSideLengthForVisibleCamera - 30 -
+      capture_region.height();
+  const gfx::Vector2d delta(delta_x, delta_y);
+  auto* event_generator = GetEventGenerator();
+  event_generator->set_current_screen_location(capture_region.bottom_right());
+  event_generator->PressLeftButton();
+  // Verify that when drag starts, the capture toast is hidden.
+  EXPECT_FALSE(capture_toast_widget->IsVisible());
+  event_generator->MoveMouseTo(capture_region.bottom_right() + delta);
+  event_generator->ReleaseLeftButton();
+  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
+            CaptureToastType::kCameraPreview);
+  EXPECT_TRUE(capture_toast_widget->IsVisible());
+  EXPECT_FALSE(GetUserNudgeController());
+
+  // Start dragging the capture region again to update it, but make it still
+  // small enough to not fit the camera preview. Verify at the beginning of the
+  // drag, preview toast is hidden. After the drag is released, preview toast is
+  // shown again.
+  const gfx::Vector2d delta1(delta_x + 10, delta_y + 10);
+  event_generator->set_current_screen_location(capture_region.bottom_right());
+  event_generator->PressLeftButton();
+  // Verify that when drag starts, the capture toast is hidden.
+  EXPECT_FALSE(capture_toast_widget->IsVisible());
+  event_generator->MoveMouseTo(capture_region.bottom_right() + delta1);
+  event_generator->ReleaseLeftButton();
+  ASSERT_TRUE(capture_toast_controller->current_toast_type());
+  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
+            CaptureToastType::kCameraPreview);
+  EXPECT_TRUE(capture_toast_widget->IsVisible());
+
+  // Update capture region big enough to show the camera preview. Verify the
+  // preview toast is hidden.
+  event_generator->set_current_screen_location(capture_region.origin());
+  event_generator->PressLeftButton();
+  // Verify that when drag starts, the capture toast is hidden.
+  EXPECT_FALSE(capture_toast_widget->IsVisible());
+  event_generator->MoveMouseTo(capture_region.bottom_right());
+  event_generator->ReleaseLeftButton();
+  // Verify that since the capture toast is dismissed, current toast type is
+  // reset.
+  EXPECT_FALSE(capture_toast_controller->current_toast_type());
+  EXPECT_FALSE(capture_toast_widget->IsVisible());
+}
+
+// Tests that the capture toast will be faded out on time out when there are no
+// actions taken.
+TEST_F(CaptureModeCameraTest, ToastVisibilityChangeOnTimeOut) {
+  UpdateDisplay("800x600");
+
+  auto* controller =
+      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
+  auto* capture_session = controller->capture_mode_session();
+  auto* camera_controller = GetCameraController();
+  auto* capture_toast_controller = capture_session->capture_toast_controller();
+  AddDefaultCamera();
+  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
+
+  // Set capture region small enough to not fit the camera preview. Verify the
+  // current capture toast is `kCameraPreview`.
+  const gfx::Rect capture_region = GetTooSmallToFitCameraRegion();
+  SelectCaptureRegion(capture_region);
+  auto* capture_toast_widget = capture_toast_controller->capture_toast_widget();
+  EXPECT_TRUE(capture_toast_widget->IsVisible());
+  ASSERT_TRUE(capture_toast_controller->current_toast_type());
+  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
+            CaptureToastType::kCameraPreview);
+
+  // Verify the timer is running after the toast is shown and when the timer is
+  // fired, the capture toast is hidden.
+  base::OneShotTimer* timer =
+      capture_toast_controller->capture_toast_dismiss_timer_for_test();
+  EXPECT_TRUE(timer->IsRunning());
+  timer->FireNow();
+  EXPECT_FALSE(capture_toast_widget->IsVisible());
+}
+
+TEST_F(CaptureModeCameraTest, ToastVisibilityChangeOnSettingsMenuOpen) {
+  UpdateDisplay("800x600");
+
+  auto* controller =
+      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
+  auto* capture_session = controller->capture_mode_session();
+  auto* camera_controller = GetCameraController();
+  auto* capture_toast_controller = capture_session->capture_toast_controller();
+  AddDefaultCamera();
+  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
+
+  // Set capture region small enough to not fit the camera preview. Verify the
+  // current capture toast is `kCameraPreview`.
+  const gfx::Rect capture_region = GetTooSmallToFitCameraRegion();
+  SelectCaptureRegion(capture_region);
+  auto* capture_toast_widget = capture_toast_controller->capture_toast_widget();
+  EXPECT_TRUE(capture_toast_widget->IsVisible());
+  ASSERT_TRUE(capture_toast_controller->current_toast_type());
+  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
+            CaptureToastType::kCameraPreview);
+
+  // Now open settings menu, verify that preview toast is dismissed immediately
+  // on settings menu open.
+  OpenSettingsView();
+  EXPECT_FALSE(capture_toast_widget->IsVisible());
+}
+
+TEST_F(CaptureModeCameraTest, ToastVisibilityChangeOnCaptureRegionMoved) {
+  UpdateDisplay("800x600");
+
+  auto* controller =
+      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
+  auto* capture_session = controller->capture_mode_session();
+  auto* camera_controller = GetCameraController();
+  auto* capture_toast_controller = capture_session->capture_toast_controller();
+  AddDefaultCamera();
+  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
+
+  // Set capture region small enough to not fit the camera preview. Verify the
+  // current capture toast is `kCameraPreview`.
+  const gfx::Rect capture_region = GetTooSmallToFitCameraRegion();
+  SelectCaptureRegion(capture_region);
+  auto* capture_toast_widget = capture_toast_controller->capture_toast_widget();
+  EXPECT_TRUE(capture_toast_widget->IsVisible());
+  ASSERT_TRUE(capture_toast_controller->current_toast_type());
+  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
+            CaptureToastType::kCameraPreview);
+
+  // Start moving the capture region, verify the preview toast is hidden at the
+  // beginning of the move and is shown once the move is done.
+  const gfx::Vector2d delta(20, 20);
+  auto* event_generator = GetEventGenerator();
+  event_generator->MoveMouseTo(capture_region.origin() + delta);
+  event_generator->PressLeftButton();
+  EXPECT_FALSE(capture_toast_widget->IsVisible());
+  event_generator->MoveMouseTo(capture_region.CenterPoint());
+  event_generator->ReleaseLeftButton();
+  EXPECT_TRUE(capture_toast_widget->IsVisible());
+}
+
+// Tests that the preview toast shows correctly when capture mode is turned on
+// through quick settings which keeps the configurations) from the previous
+// session.
+TEST_F(CaptureModeCameraTest, ToastVisibilityChangeOnCaptureModeTurnedOn) {
+  UpdateDisplay("800x600");
+
+  auto* controller =
+      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
+  auto* capture_session = controller->capture_mode_session();
+  auto* camera_controller = GetCameraController();
+  auto* capture_toast_controller = capture_session->capture_toast_controller();
+  AddDefaultCamera();
+  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
+
+  // Set capture region small enough to not fit the camera preview. Verify the
+  // current capture toast is `kCameraPreview`.
+  const gfx::Rect capture_region = GetTooSmallToFitCameraRegion();
+  SelectCaptureRegion(capture_region);
+  auto* capture_toast_widget = capture_toast_controller->capture_toast_widget();
+  EXPECT_TRUE(capture_toast_widget->IsVisible());
+  ASSERT_TRUE(capture_toast_controller->current_toast_type());
+  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
+            CaptureToastType::kCameraPreview);
+
+  // Close capture mode.
+  controller->Stop();
+
+  // Turn on capture mode again through the quick settings, verify that toast
+  // preview is visible.
+  controller->Start(CaptureModeEntryType::kQuickSettings);
+  capture_session = controller->capture_mode_session();
+  capture_toast_controller = capture_session->capture_toast_controller();
+  EXPECT_TRUE(capture_toast_controller->capture_toast_widget()->IsVisible());
+  ASSERT_TRUE(capture_toast_controller->current_toast_type());
+  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
+            CaptureToastType::kCameraPreview);
+}
+
+TEST_F(CaptureModeCameraTest, ToastVisibilityChangeOnPerformingCapture) {
+  UpdateDisplay("800x600");
+
+  auto* controller =
+      StartCaptureSession(CaptureModeSource::kRegion, CaptureModeType::kVideo);
+  auto* capture_session = controller->capture_mode_session();
+  auto* camera_controller = GetCameraController();
+  auto* capture_toast_controller = capture_session->capture_toast_controller();
+  AddDefaultCamera();
+  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
+
+  // Set capture region small enough to not fit the camera preview. Verify the
+  // current capture toast is `kCameraPreview`.
+  const gfx::Rect capture_region = GetTooSmallToFitCameraRegion();
+  SelectCaptureRegion(capture_region);
+  auto* capture_toast_widget = capture_toast_controller->capture_toast_widget();
+  EXPECT_TRUE(capture_toast_widget->IsVisible());
+  ASSERT_TRUE(capture_toast_controller->current_toast_type());
+  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
+            CaptureToastType::kCameraPreview);
+
+  // Perform the screen recording, verify that the capture toast is going to be
+  // faded out.
+  controller->PerformCapture();
+  EXPECT_EQ(capture_toast_widget->GetLayer()->GetTargetOpacity(), 0.f);
+}
+
+TEST_F(CaptureModeCameraTest, ToastVisibilityChangeOnMultiDisplays) {
+  UpdateDisplay("800x700,801+0-800x700");
+  const gfx::Rect first_display_bounds(0, 0, 800, 700);
+  const gfx::Rect second_display_bounds(801, 0, 800, 700);
+
+  // Set the window's bounds small enough to not fit the camera preview.
+  window()->SetBoundsInScreen(
+      gfx::Rect(600, 500, 100, 100),
+      display::Screen::GetScreen()->GetDisplayNearestWindow(
+          Shell::GetAllRootWindows()[0]));
+
+  // Create a window in the second display and set its bounds small enough to
+  // not fit the camera preview.
+  std::unique_ptr<aura::Window> window1(CreateTestWindow());
+  window1->SetBoundsInScreen(
+      gfx::Rect(1400, 500, 100, 100),
+      display::Screen::GetScreen()->GetDisplayNearestWindow(
+          Shell::GetAllRootWindows()[1]));
+
+  // Start the capture session.
+  auto* controller =
+      StartCaptureSession(CaptureModeSource::kWindow, CaptureModeType::kVideo);
+  auto* camera_controller = GetCameraController();
+  auto* capture_session = controller->capture_mode_session();
+  auto* capture_toast_controller = capture_session->capture_toast_controller();
+  AddDefaultCamera();
+  camera_controller->SetSelectedCamera(CameraId(kDefaultCameraModelId, 1));
+
+  // Move the mouse on top of `window` to select it, since it's too small to fit
+  // the camera preview, verify the preview toast shows and it's on the first
+  // display.
+  auto* event_generator = GetEventGenerator();
+  event_generator->MoveMouseTo(window()->GetBoundsInScreen().CenterPoint());
+  EXPECT_EQ(capture_session->GetSelectedWindow(), window());
+  auto* capture_toast_widget = capture_toast_controller->capture_toast_widget();
+  EXPECT_TRUE(capture_toast_widget->IsVisible());
+  ASSERT_TRUE(capture_toast_controller->current_toast_type());
+  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
+            CaptureToastType::kCameraPreview);
+  first_display_bounds.Contains(
+      capture_toast_widget->GetWindowBoundsInScreen());
+
+  // Now move the mouse to the top of `window1`, since it's also too small to
+  // fit the camera preview, verify the preview toast still shows. Since
+  // `window1` is on the second display, verify the preview toast also shows up
+  // on the second display.
+  event_generator->MoveMouseTo(window1->GetBoundsInScreen().CenterPoint());
+  EXPECT_EQ(capture_session->GetSelectedWindow(), window1.get());
+  EXPECT_TRUE(capture_toast_widget->IsVisible());
+  ASSERT_TRUE(capture_toast_controller->current_toast_type());
+  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
+            CaptureToastType::kCameraPreview);
+  second_display_bounds.Contains(
+      capture_toast_widget->GetWindowBoundsInScreen());
+
+  // Move mouse to the outside of `window1`, verify that preview toast is
+  // dismissed since there's no window selected for now.
+  event_generator->MoveMouseTo({1300, 500});
+  EXPECT_FALSE(capture_toast_widget->IsVisible());
+
+  // Update the bounds of `window` big enough to fit the camera preview.
+  window()->SetBoundsInScreen(
+      gfx::Rect(100, 200, 300, 300),
+      display::Screen::GetScreen()->GetDisplayNearestWindow(
+          Shell::GetAllRootWindows()[0]));
+
+  // Now move the mouse to the top of `window` again, verify that preview toast
+  // is not shown, since the window is big enough to show the camera preview.
+  event_generator->MoveMouseTo(window()->GetBoundsInScreen().CenterPoint());
+  EXPECT_EQ(capture_session->GetSelectedWindow(), window());
+  EXPECT_FALSE(capture_toast_widget->IsVisible());
+}
+
 class CaptureModeCameraPreviewTest
     : public CaptureModeCameraTest,
       public testing::WithParamInterface<CaptureModeSource> {
diff --git a/ash/capture_mode/capture_mode_session.cc b/ash/capture_mode/capture_mode_session.cc
index 9b60168..d826804 100644
--- a/ash/capture_mode/capture_mode_session.cc
+++ b/ash/capture_mode/capture_mode_session.cc
@@ -18,6 +18,7 @@
 #include "ash/capture_mode/capture_mode_session_focus_cycler.h"
 #include "ash/capture_mode/capture_mode_settings_view.h"
 #include "ash/capture_mode/capture_mode_toggle_button.h"
+#include "ash/capture_mode/capture_mode_type_view.h"
 #include "ash/capture_mode/capture_mode_util.h"
 #include "ash/capture_mode/capture_window_observer.h"
 #include "ash/capture_mode/folder_selection_dialog_controller.h"
@@ -39,7 +40,6 @@
 #include "base/callback_helpers.h"
 #include "base/check.h"
 #include "base/memory/ptr_util.h"
-#include "base/strings/stringprintf.h"
 #include "cc/paint/paint_flags.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/client/capture_client.h"
@@ -198,7 +198,7 @@
   return old_value;
 }
 
-// Gets the overlay container inside |root|.
+// Gets the menu container inside |root|.
 aura::Window* GetParentContainer(aura::Window* root) {
   DCHECK(root);
   DCHECK(root->IsRootWindow());
@@ -600,7 +600,8 @@
       magnifier_glass_(kMagnifierParams),
       is_in_projector_mode_(projector_mode),
       cursor_setter_(std::make_unique<CursorSetter>()),
-      focus_cycler_(std::make_unique<CaptureModeSessionFocusCycler>(this)) {}
+      focus_cycler_(std::make_unique<CaptureModeSessionFocusCycler>(this)),
+      capture_toast_controller_(this) {}
 
 CaptureModeSession::~CaptureModeSession() = default;
 
@@ -682,6 +683,16 @@
 
   UpdateFloatingPanelBoundsIfNeeded();
 
+  // call `OnCaptureTypeChanged` after capture bar's initialization is done
+  // instead of in the initialization of the capture mode type view, since
+  // `OnCaptureTypeChanged` may trigger `ShowCaptureToast` which depends on the
+  // capture bar.
+  // Also please note we should call `OnCaptureTypeChanged` in
+  // `CaptureModeTypeView` instead of `CaptureModeSession`, since this is during
+  // the initialization of the capture session, the type change is not triggered
+  // by the user.
+  capture_mode_bar_view_->capture_type_view()->OnCaptureTypeChanged(
+      controller_->type());
   MaybeCreateUserNudge();
 
   auto* camera_controller = controller_->camera_controller();
@@ -694,6 +705,7 @@
 
   aura::Env::GetInstance()->RemovePreTargetHandler(this);
   display_observer_.reset();
+  user_nudge_controller_.reset();
   current_root_->RemoveObserver(this);
   TabletModeController::Get()->RemoveObserver(this);
   if (input_capture_window_) {
@@ -847,6 +859,7 @@
     auto* parent = GetParentContainer(current_root_);
     capture_mode_settings_widget_ = std::make_unique<views::Widget>();
     MaybeDismissUserNudgeForever();
+    capture_toast_controller_.DismissCurrentToastIfAny();
 
     capture_mode_settings_widget_->Init(CreateWidgetParams(
         parent, CaptureModeSettingsView::GetBounds(capture_mode_bar_view_),
@@ -905,11 +918,13 @@
   UpdateCursor(display::Screen::GetScreen()->GetCursorScreenPoint(),
                /*is_touch=*/false);
 
-  // Fade out the capture bar and capture settings if it exists.
+  // Fade out the capture bar, capture settings and capture toast if they exist.
   std::vector<ui::Layer*> layers_to_fade_out{
       capture_mode_bar_widget_->GetLayer()};
   if (capture_mode_settings_widget_)
     layers_to_fade_out.push_back(capture_mode_settings_widget_->GetLayer());
+  if (auto* toast_layer = capture_toast_controller_.MaybeGetToastLayer())
+    layers_to_fade_out.push_back(toast_layer);
 
   for (auto* layer : layers_to_fade_out) {
     ui::ScopedLayerAnimationSettings layer_settings(layer->GetAnimator());
@@ -1504,8 +1519,35 @@
   UpdateCursor(screen_location, is_touch);
 }
 
-void CaptureModeSession::OnCameraPreviewBoundsOrVisibilityChanged() {
-  MaybeUpdateCaptureUisOpacity();
+void CaptureModeSession::OnCameraPreviewBoundsOrVisibilityChanged(
+    bool capture_surface_became_too_small,
+    bool did_bounds_or_visibility_change) {
+  auto* camera_preview_widget = GetCameraPreviewWidget();
+  DCHECK(camera_preview_widget);
+  const bool is_parented_to_unparented_container =
+      camera_preview_widget->GetNativeWindow()->parent()->GetId() ==
+      kShellWindowId_UnparentedContainer;
+  if (capture_surface_became_too_small && !is_drag_in_progress_ &&
+      !is_parented_to_unparented_container) {
+    // Since the user nudge toast has lower priority, if the toast for the
+    // camera preview needs to be shown, user nudge toast should be dismissed
+    // forever when applicable.
+    MaybeDismissUserNudgeForever();
+
+    capture_toast_controller_.ShowCaptureToast(
+        CaptureToastType::kCameraPreview);
+  } else {
+    capture_toast_controller_.MaybeDismissCaptureToast(
+        CaptureToastType::kCameraPreview);
+  }
+
+  if (did_bounds_or_visibility_change)
+    MaybeUpdateCaptureUisOpacity();
+}
+
+void CaptureModeSession::OnCameraPreviewDestroyed() {
+  capture_toast_controller_.MaybeDismissCaptureToast(
+      CaptureToastType::kCameraPreview);
 }
 
 std::vector<views::Widget*> CaptureModeSession::GetAvailableWidgets() {
@@ -1518,6 +1560,8 @@
     result.push_back(capture_mode_settings_widget_.get());
   if (dimensions_label_widget_)
     result.push_back(dimensions_label_widget_.get());
+  if (auto* toast = capture_toast_controller_.capture_toast_widget())
+    result.push_back(toast);
   return result;
 }
 
@@ -1580,6 +1624,7 @@
   parent->StackChildAtTop(capture_mode_bar_widget_->GetNativeWindow());
   if (user_nudge_controller_)
     user_nudge_controller_->Reposition();
+  capture_toast_controller_.MaybeRepositionCaptureToast();
 }
 
 void CaptureModeSession::MaybeCreateUserNudge() {
@@ -1592,7 +1637,7 @@
     return;
 
   user_nudge_controller_ = std::make_unique<UserNudgeController>(
-      capture_mode_bar_view_->settings_button());
+      this, capture_mode_bar_view_->settings_button());
   user_nudge_controller_->SetVisible(true);
 }
 
@@ -1618,9 +1663,9 @@
 
   auto* parent_container = GetParentContainer(current_root_);
   DCHECK(parent_container);
-  auto* overlay_layer = layer();
+  auto* menu_layer = layer();
   auto* parent_container_layer = parent_container->layer();
-  parent_container_layer->StackAtTop(overlay_layer);
+  parent_container_layer->StackAtTop(menu_layer);
 
   std::vector<views::Widget*> widget_in_order;
 
@@ -1630,6 +1675,8 @@
   // belong to the current capture session.
   if (camera_preview_widget && !controller_->is_recording_in_progress())
     widget_in_order.emplace_back(camera_preview_widget);
+  if (auto* toast = capture_toast_controller_.capture_toast_widget())
+    widget_in_order.emplace_back(toast);
   if (capture_label_widget_)
     widget_in_order.emplace_back(capture_label_widget_.get());
   if (capture_mode_bar_widget_)
@@ -1872,6 +1919,10 @@
 
   // Let the capture button handle any events it can handle first.
   if (ShouldCaptureLabelHandleEvent(event_target)) {
+    // Don't hide capture settings when the event is targeted on the the capture
+    // label. The event on the capture label may lead to perform capture, and
+    // `PerformCapture` will take care of settings menu's visibility.
+    std::ignore = deferred_settings_hider.Release();
     UpdateCursor(screen_location, is_touch);
     return;
   }
diff --git a/ash/capture_mode/capture_mode_session.h b/ash/capture_mode/capture_mode_session.h
index d8c260d..5c3cfab 100644
--- a/ash/capture_mode/capture_mode_session.h
+++ b/ash/capture_mode/capture_mode_session.h
@@ -10,6 +10,7 @@
 
 #include "ash/accessibility/magnifier/magnifier_glass.h"
 #include "ash/ash_export.h"
+#include "ash/capture_mode/capture_mode_toast_controller.h"
 #include "ash/capture_mode/capture_mode_types.h"
 #include "ash/capture_mode/folder_selection_dialog_controller.h"
 #include "ash/public/cpp/tablet_mode_observer.h"
@@ -91,6 +92,9 @@
   void set_is_stopping_to_start_video_recording(bool value) {
     is_stopping_to_start_video_recording_ = value;
   }
+  CaptureModeToastController* capture_toast_controller() {
+    return &capture_toast_controller_;
+  }
 
   // Initializes the capture mode session. This should be called right after the
   // object is created.
@@ -221,7 +225,17 @@
   void OnCameraPreviewDragStarted();
   void OnCameraPreviewDragEnded(const gfx::Point& screen_location,
                                 bool is_touch);
-  void OnCameraPreviewBoundsOrVisibilityChanged();
+
+  // Called every time when camera preview is updated.
+  // `capture_surface_became_too_small` indicates whether the camera preview
+  // becomes invisible is due to the capture surface becoming too small.
+  // `did_bounds_or_visibility_change` determines whether the capture UIs'
+  // opacity should be updated.
+  void OnCameraPreviewBoundsOrVisibilityChanged(
+      bool capture_surface_became_too_small,
+      bool did_bounds_or_visibility_change);
+
+  void OnCameraPreviewDestroyed();
 
  private:
   friend class CaptureModeSettingsTestApi;
@@ -540,8 +554,13 @@
   std::unique_ptr<FolderSelectionDialogController>
       folder_selection_dialog_controller_;
 
+  // Controls the user nudge animations.
   std::unique_ptr<UserNudgeController> user_nudge_controller_;
 
+  // Controls creating, destroying or updating the visibility of the capture
+  // toast.
+  CaptureModeToastController capture_toast_controller_;
+
   base::WeakPtrFactory<CaptureModeSession> weak_ptr_factory_{this};
 };
 
diff --git a/ash/capture_mode/capture_mode_test_util.cc b/ash/capture_mode/capture_mode_test_util.cc
index 71bb656..4b9d6b6 100644
--- a/ash/capture_mode/capture_mode_test_util.cc
+++ b/ash/capture_mode/capture_mode_test_util.cc
@@ -157,6 +157,12 @@
   return GetCaptureModeBarView()->capture_source_view()->region_toggle_button();
 }
 
+UserNudgeController* GetUserNudgeController() {
+  auto* session = CaptureModeController::Get()->capture_mode_session();
+  DCHECK(session);
+  return CaptureModeSessionTestApi(session).GetUserNudgeController();
+}
+
 // -----------------------------------------------------------------------------
 // ProjectorCaptureModeIntegrationHelper:
 
diff --git a/ash/capture_mode/capture_mode_test_util.h b/ash/capture_mode/capture_mode_test_util.h
index 693843f..f05c57b 100644
--- a/ash/capture_mode/capture_mode_test_util.h
+++ b/ash/capture_mode/capture_mode_test_util.h
@@ -8,6 +8,7 @@
 #include <string>
 
 #include "ash/capture_mode/capture_mode_types.h"
+#include "ash/capture_mode/user_nudge_controller.h"
 #include "ash/projector/test/mock_projector_client.h"
 #include "base/test/scoped_feature_list.h"
 #include "ui/events/event_constants.h"
@@ -99,6 +100,8 @@
 
 CaptureModeToggleButton* GetRegionToggleButton();
 
+UserNudgeController* GetUserNudgeController();
+
 // Defines a helper class to allow setting up and testing the Projector feature
 // in multiple test fixtures. Note that this helper initializes the Projector-
 // related features in its constructor, so test fixtures that use this should
diff --git a/ash/capture_mode/capture_mode_toast_controller.cc b/ash/capture_mode/capture_mode_toast_controller.cc
new file mode 100644
index 0000000..2500906
--- /dev/null
+++ b/ash/capture_mode/capture_mode_toast_controller.cc
@@ -0,0 +1,241 @@
+// Copyright 2022 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/capture_mode/capture_mode_toast_controller.h"
+
+#include <memory>
+
+#include "ash/capture_mode/capture_mode_session.h"
+#include "ash/capture_mode/capture_mode_util.h"
+#include "ash/constants/ash_features.h"
+#include "ash/public/cpp/shell_window_ids.h"
+#include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_provider.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/compositor/layer.h"
+#include "ui/views/animation/animation_builder.h"
+#include "ui/views/background.h"
+#include "ui/views/controls/label.h"
+
+namespace ash {
+
+namespace {
+
+// Specs for `capture_toast_widget_`.
+constexpr int kToastSpacingFromBar = 8;
+constexpr int kToastDefaultHeight = 36;
+constexpr int kToastVerticalPadding = 8;
+constexpr int kToastHorizontalPadding = 16;
+constexpr int kToastBorderThickness = 1;
+constexpr int kToastCornerRadius = 16;
+constexpr gfx::RoundedCornersF kToastRoundedCorners{kToastCornerRadius};
+
+// Animation duration for updating the visibility of `capture_toast_widget_`.
+constexpr base::TimeDelta kCaptureToastVisibilityChangeDuration =
+    base::Milliseconds(200);
+
+// The duration that `capture_toast_widget_` will remain visible after it's
+// created when there are no actions taken. After this, the toast widget will be
+// dismissed.
+constexpr base::TimeDelta kDelayToDismissToast = base::Seconds(6);
+
+std::u16string GetCaptureToastLabelOnToastType(
+    CaptureToastType capture_toast_type) {
+  const int message_id =
+      capture_toast_type == CaptureToastType::kCameraPreview
+          ? IDS_ASH_SCREEN_CAPTURE_SURFACE_TOO_SMALL_USER_NUDGE
+          : (features::IsCaptureModeSelfieCameraEnabled()
+                 ? IDS_ASH_SCREEN_CAPTURE_SHOW_CAMERA_USER_NUDGE
+                 : IDS_ASH_SCREEN_CAPTURE_FOLDER_SELECTION_USER_NUDGE);
+  return l10n_util::GetStringUTF16(message_id);
+}
+
+// Returns the init params that will be used for the toast widget.
+views::Widget::InitParams CreateWidgetParams(aura::Window* parent,
+                                             const gfx::Rect& bounds) {
+  views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
+  params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
+  params.parent = parent;
+  params.bounds = bounds;
+  params.name = "CaptureModeToastWidget";
+  params.accept_events = false;
+  return params;
+}
+
+}  // namespace
+
+CaptureModeToastController::CaptureModeToastController(
+    CaptureModeSession* session)
+    : capture_session_(session) {}
+
+CaptureModeToastController::~CaptureModeToastController() {
+  if (capture_toast_widget_)
+    capture_toast_widget_->CloseNow();
+}
+
+void CaptureModeToastController::ShowCaptureToast(
+    CaptureToastType capture_toast_type) {
+  current_toast_type_ = capture_toast_type;
+  const std::u16string capture_toast_label =
+      GetCaptureToastLabelOnToastType(capture_toast_type);
+
+  if (!capture_toast_widget_)
+    BuildCaptureToastWidget(capture_toast_label);
+  else
+    toast_label_view_->SetText(capture_toast_label);
+
+  MaybeRepositionCaptureToast();
+  const bool did_visibility_change = capture_mode_util::SetWidgetVisibility(
+      capture_toast_widget_.get(), /*target_visibility=*/true,
+      capture_mode_util::AnimationParams{kCaptureToastVisibilityChangeDuration,
+                                         gfx::Tween::FAST_OUT_SLOW_IN,
+                                         /*apply_scale_up_animation=*/false});
+
+  // Only if the capture toast type is the `kCameraPreview`, the capture toast
+  // should be auto dismissed after `kDelayToDismissToast`.
+  if (did_visibility_change &&
+      capture_toast_type == CaptureToastType::kCameraPreview) {
+    capture_toast_dismiss_timer_.Start(
+        FROM_HERE, kDelayToDismissToast,
+        base::BindOnce(&CaptureModeToastController::MaybeDismissCaptureToast,
+                       base::Unretained(this), capture_toast_type,
+                       /*animate=*/true));
+  }
+}
+
+void CaptureModeToastController::MaybeDismissCaptureToast(
+    CaptureToastType capture_toast_type,
+    bool animate) {
+  if (!current_toast_type_) {
+    DCHECK(!capture_toast_widget_ ||
+           !capture_mode_util::GetWidgetCurrentVisibility(
+               capture_toast_widget_.get()));
+    return;
+  }
+
+  if (!capture_toast_widget_) {
+    DCHECK(!current_toast_type_);
+    return;
+  }
+
+  if (capture_toast_type != current_toast_type_)
+    return;
+
+  capture_toast_dismiss_timer_.Stop();
+
+  current_toast_type_.reset();
+  if (animate) {
+    capture_mode_util::SetWidgetVisibility(
+        capture_toast_widget_.get(), /*target_visibility=*/false,
+        capture_mode_util::AnimationParams{
+            kCaptureToastVisibilityChangeDuration, gfx::Tween::FAST_OUT_SLOW_IN,
+            /*apply_scale_up_animation=*/false});
+    return;
+  }
+
+  capture_toast_widget_->Hide();
+}
+
+void CaptureModeToastController::DismissCurrentToastIfAny() {
+  if (current_toast_type_)
+    MaybeDismissCaptureToast(*current_toast_type_, /*animate=*/false);
+}
+
+void CaptureModeToastController::MaybeRepositionCaptureToast() {
+  if (!capture_toast_widget_)
+    return;
+
+  auto* parent_window = capture_session_->current_root()->GetChildById(
+      kShellWindowId_MenuContainer);
+
+  if (capture_toast_widget_->GetNativeWindow()->parent() != parent_window) {
+    parent_window->AddChild(capture_toast_widget_->GetNativeWindow());
+    auto* layer = capture_toast_widget_->GetLayer();
+    // Any ongoing opacity animation should be committed when we reparent the
+    // toast, otherwise it doesn't look good.
+    layer->SetOpacity(layer->GetTargetOpacity());
+  }
+
+  capture_toast_widget_->SetBounds(CalculateToastWidgetScreenBounds());
+}
+
+ui::Layer* CaptureModeToastController::MaybeGetToastLayer() {
+  return capture_toast_widget_ ? capture_toast_widget_->GetLayer() : nullptr;
+}
+
+void CaptureModeToastController::BuildCaptureToastWidget(
+    const std::u16string& label) {
+  capture_toast_widget_ = std::make_unique<views::Widget>(
+      CreateWidgetParams(capture_session_->current_root()->GetChildById(
+                             kShellWindowId_MenuContainer),
+                         CalculateToastWidgetScreenBounds()));
+
+  toast_label_view_ = capture_toast_widget_->SetContentsView(
+      std::make_unique<views::Label>(label));
+  toast_label_view_->SetMultiLine(true);
+  auto* color_provider = AshColorProvider::Get();
+  SkColor background_color = color_provider->GetBaseLayerColor(
+      AshColorProvider::BaseLayerType::kTransparent80);
+  toast_label_view_->SetBackground(
+      views::CreateSolidBackground(background_color));
+  toast_label_view_->SetBorder(views::CreateRoundedRectBorder(
+      kToastBorderThickness, kToastCornerRadius,
+      color_provider->GetControlsLayerColor(
+          AshColorProvider::ControlsLayerType::kHighlightColor1)));
+  toast_label_view_->SetAutoColorReadabilityEnabled(false);
+  const SkColor text_color = color_provider->GetContentLayerColor(
+      AshColorProvider::ContentLayerType::kTextColorPrimary);
+  toast_label_view_->SetEnabledColor(text_color);
+  toast_label_view_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
+  toast_label_view_->SetVerticalAlignment(gfx::ALIGN_MIDDLE);
+
+  toast_label_view_->SetPaintToLayer();
+  auto* label_layer = toast_label_view_->layer();
+  label_layer->SetFillsBoundsOpaquely(false);
+  label_layer->SetRoundedCornerRadius(kToastRoundedCorners);
+  label_layer->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
+  label_layer->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
+
+  // The widget is created initially with 0 opacity, and will animate to be
+  // fully visible when `ShowCaptureToast` is called.
+  capture_toast_widget_->Show();
+  auto* widget_layer = capture_toast_widget_->GetLayer();
+  widget_layer->SetOpacity(0);
+}
+
+gfx::Rect CaptureModeToastController::CalculateToastWidgetScreenBounds() const {
+  const auto bar_widget_bounds_in_screen =
+      capture_session_->capture_mode_bar_widget()->GetWindowBoundsInScreen();
+
+  auto bounds = bar_widget_bounds_in_screen;
+  if (toast_label_view_) {
+    const auto preferred_size = toast_label_view_->GetPreferredSize();
+    // We don't want the toast width to go beyond the capture bar width, but if
+    // it can use a smaller width, then we align the horizontal centers of the
+    // bar the toast together.
+    const int fitted_width =
+        preferred_size.width() + 2 * kToastHorizontalPadding;
+    if (fitted_width < bar_widget_bounds_in_screen.width()) {
+      bounds.set_width(fitted_width);
+      bounds.set_x(bar_widget_bounds_in_screen.CenterPoint().x() -
+                   fitted_width / 2);
+    }
+    // Note that the toast is allowed to have multiple lines if the width
+    // doesn't fit the contents.
+    bounds.set_height(toast_label_view_->GetHeightForWidth(bounds.width()) +
+                      2 * kToastVerticalPadding);
+  } else {
+    // The content view hasn't been created yet, so we use a default height.
+    // Calling Reposition() after the widget has been initialization will fix
+    // any wrong bounds.
+    bounds.set_height(kToastDefaultHeight);
+  }
+
+  bounds.set_y(bar_widget_bounds_in_screen.y() - bounds.height() -
+               kToastSpacingFromBar);
+
+  return bounds;
+}
+
+}  // namespace ash
diff --git a/ash/capture_mode/capture_mode_toast_controller.h b/ash/capture_mode/capture_mode_toast_controller.h
new file mode 100644
index 0000000..b4a786ed
--- /dev/null
+++ b/ash/capture_mode/capture_mode_toast_controller.h
@@ -0,0 +1,98 @@
+// Copyright 2022 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_CAPTURE_MODE_CAPTURE_MODE_TOAST_CONTROLLER_H_
+#define ASH_CAPTURE_MODE_CAPTURE_MODE_TOAST_CONTROLLER_H_
+
+#include "ash/ash_export.h"
+#include "base/timer/timer.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/views/widget/unique_widget_ptr.h"
+
+namespace gfx {
+class Rect;
+}  // namespace gfx
+
+namespace views {
+class Widget;
+class Label;
+}  // namespace views
+
+namespace ui {
+class Layer;
+}  // namespace ui
+
+namespace ash {
+
+class CaptureModeSession;
+
+// Defines the capture toast type that Capture Mode is currently using.
+enum class CaptureToastType {
+  kUserNudge,
+  kCameraPreview,
+};
+
+// Controls the capture mode toast shown in the capture session.
+class ASH_EXPORT CaptureModeToastController {
+ public:
+  explicit CaptureModeToastController(CaptureModeSession* session);
+  CaptureModeToastController(const CaptureModeToastController&) = delete;
+  CaptureModeToastController& operator=(const CaptureModeToastController&) =
+      delete;
+  ~CaptureModeToastController();
+
+  const CaptureToastType* current_toast_type() const {
+    return current_toast_type_ ? &(*current_toast_type_) : nullptr;
+  }
+  views::Widget* capture_toast_widget() { return capture_toast_widget_.get(); }
+
+  // Shows `capture_toast_widget_` with label text defined by the given
+  // `capture_toast_type`; if `capture_toast_widget_` doesn't exist, creates
+  // one. Otherwise, just updates the label text for it if needed.
+  void ShowCaptureToast(CaptureToastType capture_toast_type);
+
+  void MaybeDismissCaptureToast(CaptureToastType capture_toast_type,
+                                bool animate = true);
+
+  // Called when we need to dismiss the current toast in spite of the toast
+  // type. For example, when the settings menu is shown, the toast should be
+  // dismissed no matter what type it is.
+  void DismissCurrentToastIfAny();
+
+  void MaybeRepositionCaptureToast();
+
+  // Return the layer of `capture_toast_widget_` if it exists.
+  ui::Layer* MaybeGetToastLayer();
+
+  base::OneShotTimer* capture_toast_dismiss_timer_for_test() {
+    return &capture_toast_dismiss_timer_;
+  }
+
+ private:
+  // Initializes the toast widget and its contents.
+  void BuildCaptureToastWidget(const std::u16string& label);
+
+  gfx::Rect CalculateToastWidgetScreenBounds() const;
+
+  // The session that owns `this`. Guaranteed to be non null for the lifetime of
+  // `this`.
+  CaptureModeSession* const capture_session_;
+
+  // The capture toast widget and its contents view.
+  views::UniqueWidgetPtr capture_toast_widget_;
+  views::Label* toast_label_view_ = nullptr;
+
+  // Stores the toast type of the `capture_toast_widget_` after it's created.
+  absl::optional<CaptureToastType> current_toast_type_;
+
+  // Started when `capture_toast_widget_` is shown for `kCameraPreview`. Runs
+  // MaybeDismissCaptureToast() to fade out the capture toast if possible.
+  base::OneShotTimer capture_toast_dismiss_timer_;
+
+  base::WeakPtrFactory<CaptureModeToastController> weak_ptr_factory_{this};
+};
+
+}  // namespace ash
+
+#endif  // ASH_CAPTURE_MODE_CAPTURE_MODE_TOAST_CONTROLLER_H_
diff --git a/ash/capture_mode/capture_mode_type_view.cc b/ash/capture_mode/capture_mode_type_view.cc
index 263bceb..89a824b 100644
--- a/ash/capture_mode/capture_mode_type_view.cc
+++ b/ash/capture_mode/capture_mode_type_view.cc
@@ -83,8 +83,6 @@
     video_toggle_button_->SetEnabled(false);
   }
 
-  OnCaptureTypeChanged(controller->type());
-
   video_toggle_button_->SetTooltipText(
       l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_TOOLTIP_SCREENRECORD));
 }
diff --git a/ash/capture_mode/capture_mode_unittests.cc b/ash/capture_mode/capture_mode_unittests.cc
index 831afc4..f008ecf 100644
--- a/ash/capture_mode/capture_mode_unittests.cc
+++ b/ash/capture_mode/capture_mode_unittests.cc
@@ -5462,12 +5462,6 @@
     return CaptureModeSessionTestApi(session).GetCaptureModeSettingsView();
   }
 
-  UserNudgeController* GetUserNudgeController() const {
-    auto* session = CaptureModeController::Get()->capture_mode_session();
-    DCHECK(session);
-    return CaptureModeSessionTestApi(session).GetUserNudgeController();
-  }
-
   void WaitForSettingsMenuToBeRefreshed() {
     base::RunLoop run_loop;
     CaptureModeSettingsTestApi().SetOnSettingsMenuRefreshedCallback(
@@ -5535,10 +5529,15 @@
 
 TEST_P(CaptureModeNudgeDismissalTest, NudgeDismissedForever) {
   auto* controller = StartSession();
+  auto* capture_session = controller->capture_mode_session();
+  auto* capture_toast_controller = capture_session->capture_toast_controller();
   auto* nudge_controller = GetUserNudgeController();
   ASSERT_TRUE(nudge_controller);
   EXPECT_TRUE(nudge_controller->is_visible());
-  EXPECT_TRUE(nudge_controller->toast_widget());
+  EXPECT_TRUE(capture_toast_controller->capture_toast_widget());
+  ASSERT_TRUE(capture_toast_controller->current_toast_type());
+  EXPECT_EQ(*(capture_toast_controller->current_toast_type()),
+            CaptureToastType::kUserNudge);
 
   // Trigger the action that dismisses the nudge forever, it should be removed
   // in this session (if the action doesn't stop the session) and any future
@@ -5572,17 +5571,20 @@
   auto* controller = StartCaptureSession(CaptureModeSource::kFullscreen,
                                          CaptureModeType::kImage);
   auto* session = controller->capture_mode_session();
+  auto* capture_toast_controller = session->capture_toast_controller();
+
   EXPECT_EQ(Shell::GetAllRootWindows()[0], session->current_root());
-  auto* nudge_controller = GetUserNudgeController();
-  EXPECT_EQ(
-      nudge_controller->toast_widget()->GetNativeWindow()->GetRootWindow(),
-      session->current_root());
+  EXPECT_EQ(capture_toast_controller->capture_toast_widget()
+                ->GetNativeWindow()
+                ->GetRootWindow(),
+            session->current_root());
 
   MoveMouseToAndUpdateCursorDisplay(gfx::Point(1000, 500), event_generator);
   EXPECT_EQ(Shell::GetAllRootWindows()[1], session->current_root());
-  EXPECT_EQ(
-      nudge_controller->toast_widget()->GetNativeWindow()->GetRootWindow(),
-      session->current_root());
+  EXPECT_EQ(capture_toast_controller->capture_toast_widget()
+                ->GetNativeWindow()
+                ->GetRootWindow(),
+            session->current_root());
 }
 
 TEST_F(CaptureModeSettingsTest, NudgeBehaviorWhenSelectingRegion) {
@@ -5607,9 +5609,11 @@
 
   // The nudge shows again, and is on the second display.
   EXPECT_TRUE(nudge_controller->is_visible());
-  EXPECT_EQ(
-      nudge_controller->toast_widget()->GetNativeWindow()->GetRootWindow(),
-      session->current_root());
+  EXPECT_EQ(session->capture_toast_controller()
+                ->capture_toast_widget()
+                ->GetNativeWindow()
+                ->GetRootWindow(),
+            session->current_root());
 }
 
 TEST_F(CaptureModeSettingsTest, NudgeDoesNotShowForAllUserTypes) {
diff --git a/ash/capture_mode/capture_mode_util.cc b/ash/capture_mode/capture_mode_util.cc
index 566e4b1..b7ff611 100644
--- a/ash/capture_mode/capture_mode_util.cc
+++ b/ash/capture_mode/capture_mode_util.cc
@@ -30,8 +30,10 @@
 #include "ui/gfx/geometry/transform_util.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/message_center/views/notification_background_painter.h"
+#include "ui/views/animation/animation_builder.h"
 #include "ui/views/controls/image_view.h"
 #include "ui/views/layout/box_layout.h"
+#include "ui/views/widget/widget.h"
 
 namespace ash::capture_mode_util {
 
@@ -39,6 +41,7 @@
 
 constexpr int kBannerViewTopRadius = 0;
 constexpr int kBannerViewBottomRadius = 8;
+constexpr float kScaleUpFactor = 0.8f;
 
 // Returns the target visibility of the camera preview, given the
 // `confine_bounds_short_side_length`. The out parameter
@@ -72,6 +75,74 @@
   return gfx::Rect(layer->GetTargetBounds().size()).CenterPoint();
 }
 
+void FadeInWidget(views::Widget* widget,
+                  const AnimationParams& animation_params) {
+  DCHECK(widget);
+  auto* layer = widget->GetLayer();
+  DCHECK(!widget->GetNativeWindow()->TargetVisibility() ||
+         layer->GetTargetOpacity() < 1.f);
+
+  // Please notice the order matters here. When the opacity is set to 0.f, if
+  // there's any on-going fade out animation, the `OnEnded` in `FadeOutWidget`
+  // will be triggered, which will hide the widget and set its opacity to 1.f.
+  // So `Show` should be triggered after setting the opacity to 0 to undo the
+  // work done by the FadeOutWidget's OnEnded .
+  if (layer->opacity() == 1.f)
+    layer->SetOpacity(0.f);
+  if (!widget->GetNativeWindow()->TargetVisibility())
+    widget->Show();
+
+  if (animation_params.apply_scale_up_animation) {
+    layer->SetTransform(
+        capture_mode_util::GetScaleTransformAboutCenter(layer, kScaleUpFactor));
+  }
+
+  views::AnimationBuilder builder;
+  auto& animation_sequence_block =
+      builder
+          .SetPreemptionStrategy(
+              ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
+          .Once()
+          .SetDuration(animation_params.animation_duration)
+          .SetOpacity(layer, 1.f, animation_params.tween_type);
+
+  // We should only set transform here if `apply_scale_up_animation` is true,
+  // otherwise, it may mess up with the snap animation in
+  // `SetCameraPreviewBounds`.
+  if (animation_params.apply_scale_up_animation) {
+    animation_sequence_block.SetTransform(layer, gfx::Transform(),
+                                          gfx::Tween::ACCEL_20_DECEL_100);
+  }
+}
+
+void FadeOutWidget(views::Widget* widget,
+                   const AnimationParams& animation_params) {
+  DCHECK(widget);
+  DCHECK(widget->GetNativeWindow()->TargetVisibility());
+
+  auto* layer = widget->GetLayer();
+  DCHECK_EQ(layer->GetTargetOpacity(), 1.f);
+
+  views::AnimationBuilder()
+      .OnEnded(base::BindOnce(
+          [](base::WeakPtr<views::Widget> the_widget) {
+            if (!the_widget)
+              return;
+
+            // Please notice, the order matters here. If we set the layer's
+            // opacity back to 1.f before calling `Hide`, flickering can be
+            // seen.
+            the_widget->Hide();
+            the_widget->GetLayer()->SetOpacity(1.f);
+          },
+          widget->GetWeakPtr()))
+      .SetPreemptionStrategy(
+          ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET)
+      .Once()
+      .SetDuration(animation_params.animation_duration)
+      .SetOpacity(layer, 0.f, animation_params.tween_type);
+}
+
 }  // namespace
 
 bool IsCaptureModeActive() {
@@ -349,13 +420,52 @@
   }
 
   if (controller->IsActive()) {
-    if (auto* capture_label_widget =
-            controller->capture_mode_session()->capture_label_widget()) {
+    auto* capture_session = controller->capture_mode_session();
+
+    if (auto* capture_label_widget = capture_session->capture_label_widget())
       ignore_windows.insert(capture_label_widget->GetNativeWindow());
+
+    if (auto* capture_toast_widget = capture_session->capture_toast_controller()
+                                         ->capture_toast_widget()) {
+      ignore_windows.insert(capture_toast_widget->GetNativeWindow());
     }
   }
 
   return GetTopmostWindowAtPoint(screen_point, ignore_windows);
 }
 
+bool GetWidgetCurrentVisibility(views::Widget* widget) {
+  // Note that we use `aura::Window::TargetVisibility()` rather than
+  // `views::Widget::IsVisible()` (which in turn uses
+  // `aura::Window::IsVisible()`). The reason is because the latter takes into
+  // account whether window's layer is drawn or not. We want to calculate the
+  // current visibility only based on the actual visibility of the window
+  // itself, so that we can correctly compare it against `target_visibility`.
+  // Note that the widget may be a child of the unparented container (which is
+  // always hidden), yet the native window is shown.
+  return widget->GetNativeWindow()->TargetVisibility() &&
+         widget->GetLayer()->GetTargetOpacity() > 0.f;
+}
+
+bool SetWidgetVisibility(views::Widget* widget,
+                         bool target_visibility,
+                         absl::optional<AnimationParams> animation_params) {
+  DCHECK(widget);
+  if (target_visibility == GetWidgetCurrentVisibility(widget))
+    return false;
+
+  if (animation_params) {
+    if (target_visibility)
+      FadeInWidget(widget, *animation_params);
+    else
+      FadeOutWidget(widget, *animation_params);
+  } else {
+    if (target_visibility)
+      widget->Show();
+    else
+      widget->Hide();
+  }
+  return true;
+}
+
 }  // namespace ash::capture_mode_util
diff --git a/ash/capture_mode/capture_mode_util.h b/ash/capture_mode/capture_mode_util.h
index 61dbf04..f34bbde 100644
--- a/ash/capture_mode/capture_mode_util.h
+++ b/ash/capture_mode/capture_mode_util.h
@@ -9,6 +9,9 @@
 
 #include "ash/ash_export.h"
 #include "ash/capture_mode/capture_mode_types.h"
+#include "base/time/time.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+#include "ui/gfx/animation/tween.h"
 #include "ui/gfx/geometry/size.h"
 
 namespace aura {
@@ -27,6 +30,7 @@
 
 namespace views {
 class View;
+class Widget;
 }  // namespace views
 
 namespace ash {
@@ -137,6 +141,27 @@
 // since the snapshot code tries to snap a deleted window.
 aura::Window* GetTopMostCapturableWindowAtPoint(const gfx::Point& screen_point);
 
+bool GetWidgetCurrentVisibility(views::Widget* widget);
+
+// Defines an object to hold the animation params used for setting the widget's
+// visibility.
+struct AnimationParams {
+  const base::TimeDelta animation_duration;
+
+  const gfx::Tween::Type tween_type;
+
+  // When it's true, the scale up transform should be applied in the fade in
+  // animiation.
+  const bool apply_scale_up_animation;
+};
+
+// Sets the visibility of the given `widget` to the given `target_visibility`
+// with the given `animation_params`, returns true only if the
+// `target_visibility` is different than the current.
+bool SetWidgetVisibility(views::Widget* widget,
+                         bool target_visibility,
+                         absl::optional<AnimationParams> animation_params);
+
 }  // namespace capture_mode_util
 
 }  // namespace ash
diff --git a/ash/capture_mode/user_nudge_controller.cc b/ash/capture_mode/user_nudge_controller.cc
index b6e89ee..1b10b03 100644
--- a/ash/capture_mode/user_nudge_controller.cc
+++ b/ash/capture_mode/user_nudge_controller.cc
@@ -5,37 +5,23 @@
 #include "ash/capture_mode/user_nudge_controller.h"
 
 #include "ash/capture_mode/capture_mode_controller.h"
+#include "ash/capture_mode/capture_mode_session.h"
 #include "ash/capture_mode/capture_mode_util.h"
-#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/shell_window_ids.h"
-#include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
 #include "base/bind.h"
 #include "base/check.h"
 #include "base/time/time.h"
 #include "ui/aura/window.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/transform.h"
 #include "ui/gfx/geometry/transform_util.h"
 #include "ui/views/animation/animation_builder.h"
 #include "ui/views/animation/animation_sequence_block.h"
-#include "ui/views/background.h"
-#include "ui/views/border.h"
-#include "ui/views/controls/label.h"
 
 namespace ash {
 
 namespace {
 
-constexpr int kToastSpacingFromBar = 8;
-constexpr int kToastDefaultHeight = 36;
-constexpr int kToastVerticalPadding = 8;
-constexpr int kToastHorizontalPadding = 16;
-constexpr int kToastBorderThickness = 1;
-constexpr int kToastCornerRadius = 16;
-constexpr gfx::RoundedCornersF kToastRoundedCorners{kToastCornerRadius};
-
 constexpr float kBaseRingOpacity = 0.21f;
 constexpr float kRippleRingOpacity = 0.5f;
 
@@ -67,22 +53,12 @@
   return gfx::Rect(origin, view->layer()->size());
 }
 
-// Returns the init params that will be used for the toast widget.
-views::Widget::InitParams CreateWidgetParams(aura::Window* parent,
-                                             const gfx::Rect& bounds) {
-  views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
-  params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
-  params.parent = parent;
-  params.bounds = bounds;
-  params.name = "UserNudgeToastWidget";
-  params.accept_events = false;
-  return params;
-}
-
 }  // namespace
 
-UserNudgeController::UserNudgeController(views::View* view_to_be_highlighted)
-    : view_to_be_highlighted_(view_to_be_highlighted) {
+UserNudgeController::UserNudgeController(CaptureModeSession* session,
+                                         views::View* view_to_be_highlighted)
+    : capture_session_(session),
+      view_to_be_highlighted_(view_to_be_highlighted) {
   view_to_be_highlighted_->SetPaintToLayer();
   view_to_be_highlighted_->layer()->SetFillsBoundsOpaquely(false);
 
@@ -97,21 +73,19 @@
   ripple_ring_.SetFillsBoundsOpaquely(false);
   ripple_ring_.SetOpacity(0);
 
-  BuildToastWidget();
   Reposition();
 }
 
 UserNudgeController::~UserNudgeController() {
-  DCHECK(toast_widget_);
-  toast_widget_->CloseNow();
   if (should_dismiss_nudge_forever_)
     CaptureModeController::Get()->DisableUserNudgeForever();
+  capture_session_->capture_toast_controller()->MaybeDismissCaptureToast(
+      CaptureToastType::kUserNudge,
+      /*animate=*/false);
 }
 
 void UserNudgeController::Reposition() {
   auto* parent_window = GetParentWindow();
-  if (toast_widget_->GetNativeWindow()->parent() != parent_window)
-    parent_window->AddChild(toast_widget_->GetNativeWindow());
 
   auto* parent_layer = parent_window->layer();
   if (parent_layer != base_ring_.parent()) {
@@ -127,7 +101,6 @@
   ripple_ring_.SetBounds(view_bounds_in_root);
   ripple_ring_.SetRoundedCornerRadius(
       gfx::RoundedCornersF(view_bounds_in_root.width() / 2.f));
-  toast_widget_->SetBounds(CalculateToastWidgetScreenBounds());
 }
 
 void UserNudgeController::SetVisible(bool visible) {
@@ -135,6 +108,7 @@
     return;
 
   is_visible_ = visible;
+  auto* capture_toast_controller = capture_session_->capture_toast_controller();
 
   views::AnimationBuilder builder;
   builder.SetPreemptionStrategy(
@@ -152,8 +126,9 @@
     builder.Once()
         .SetDuration(kVisibilityChangeDuration)
         .SetOpacity(&base_ring_, 0, gfx::Tween::FAST_OUT_SLOW_IN)
-        .SetOpacity(&ripple_ring_, 0, gfx::Tween::FAST_OUT_SLOW_IN)
-        .SetOpacity(toast_widget_->GetLayer(), 0, gfx::Tween::FAST_OUT_SLOW_IN);
+        .SetOpacity(&ripple_ring_, 0, gfx::Tween::FAST_OUT_SLOW_IN);
+    capture_toast_controller->MaybeDismissCaptureToast(
+        CaptureToastType::kUserNudge);
     return;
   }
 
@@ -167,8 +142,8 @@
                               base::Unretained(this)))
       .Once()
       .SetDuration(kDelayToShowNudge)
-      .SetOpacity(&base_ring_, kBaseRingOpacity, gfx::Tween::FAST_OUT_SLOW_IN)
-      .SetOpacity(toast_widget_->GetLayer(), 1.f, gfx::Tween::FAST_OUT_SLOW_IN);
+      .SetOpacity(&base_ring_, kBaseRingOpacity, gfx::Tween::FAST_OUT_SLOW_IN);
+  capture_toast_controller->ShowCaptureToast(CaptureToastType::kUserNudge);
 }
 
 void UserNudgeController::PerformNudgeAnimations() {
@@ -250,79 +225,4 @@
   return root_window->GetChildById(kShellWindowId_OverlayContainer);
 }
 
-gfx::Rect UserNudgeController::CalculateToastWidgetScreenBounds() const {
-  const auto bar_widget_bounds_in_screen =
-      view_to_be_highlighted_->GetWidget()->GetWindowBoundsInScreen();
-
-  auto bounds = bar_widget_bounds_in_screen;
-  if (toast_label_view_) {
-    const auto preferred_size = toast_label_view_->GetPreferredSize();
-    // We don't want the toast width to go beyond the capture bar width, but if
-    // it can use a smaller width, then we align the horizontal centers of the
-    // bar the toast together.
-    const int fitted_width =
-        preferred_size.width() + 2 * kToastHorizontalPadding;
-    if (fitted_width < bar_widget_bounds_in_screen.width()) {
-      bounds.set_width(fitted_width);
-      bounds.set_x(bar_widget_bounds_in_screen.CenterPoint().x() -
-                   fitted_width / 2);
-    }
-    // Note that the toast is allowed to have multiple lines if the width
-    // doesn't fit the contents.
-    bounds.set_height(toast_label_view_->GetHeightForWidth(bounds.width()) +
-                      2 * kToastVerticalPadding);
-  } else {
-    // The content view hasn't been created yet, so we use a default height.
-    // Calling Reposition() after the widget has been initialization will fix
-    // any wrong bounds.
-    bounds.set_height(kToastDefaultHeight);
-  }
-
-  bounds.set_y(bar_widget_bounds_in_screen.y() - bounds.height() -
-               kToastSpacingFromBar);
-
-  return bounds;
-}
-
-void UserNudgeController::BuildToastWidget() {
-  toast_widget_->Init(CreateWidgetParams(GetParentWindow(),
-                                         CalculateToastWidgetScreenBounds()));
-
-  const int message_id =
-      features::IsCaptureModeSelfieCameraEnabled()
-          ? IDS_ASH_SCREEN_CAPTURE_SHOW_CAMERA_USER_NUDGE
-          : IDS_ASH_SCREEN_CAPTURE_FOLDER_SELECTION_USER_NUDGE;
-  toast_label_view_ = toast_widget_->SetContentsView(
-      std::make_unique<views::Label>(l10n_util::GetStringUTF16(message_id)));
-  toast_label_view_->SetMultiLine(true);
-  auto* color_provider = AshColorProvider::Get();
-  SkColor background_color = color_provider->GetBaseLayerColor(
-      AshColorProvider::BaseLayerType::kTransparent80);
-  toast_label_view_->SetBackground(
-      views::CreateSolidBackground(background_color));
-  toast_label_view_->SetBorder(views::CreateRoundedRectBorder(
-      kToastBorderThickness, kToastCornerRadius,
-      color_provider->GetControlsLayerColor(
-          AshColorProvider::ControlsLayerType::kHighlightColor1)));
-  toast_label_view_->SetAutoColorReadabilityEnabled(false);
-  const SkColor text_color = color_provider->GetContentLayerColor(
-      AshColorProvider::ContentLayerType::kTextColorPrimary);
-  toast_label_view_->SetEnabledColor(text_color);
-  toast_label_view_->SetHorizontalAlignment(gfx::ALIGN_CENTER);
-  toast_label_view_->SetVerticalAlignment(gfx::ALIGN_MIDDLE);
-
-  toast_label_view_->SetPaintToLayer();
-  auto* label_layer = toast_label_view_->layer();
-  label_layer->SetFillsBoundsOpaquely(false);
-  label_layer->SetRoundedCornerRadius(kToastRoundedCorners);
-  label_layer->SetBackgroundBlur(ColorProvider::kBackgroundBlurSigma);
-  label_layer->SetBackdropFilterQuality(ColorProvider::kBackgroundBlurQuality);
-
-  // The widget is created initially with 0 opacity, and will animate to be
-  // fully visible when SetVisible() is called.
-  toast_widget_->Show();
-  auto* widget_layer = toast_widget_->GetLayer();
-  widget_layer->SetOpacity(0);
-}
-
 }  // namespace ash
diff --git a/ash/capture_mode/user_nudge_controller.h b/ash/capture_mode/user_nudge_controller.h
index 61ed7f6..ab2576c 100644
--- a/ash/capture_mode/user_nudge_controller.h
+++ b/ash/capture_mode/user_nudge_controller.h
@@ -7,21 +7,19 @@
 
 #include "base/timer/timer.h"
 #include "ui/compositor/layer.h"
-#include "ui/views/widget/unique_widget_ptr.h"
-#include "ui/views/widget/widget.h"
 
 namespace aura {
 class Window;
 }  // namespace aura
 
 namespace views {
-class Label;
 class View;
-class Widget;
 }  // namespace views
 
 namespace ash {
 
+class CaptureModeSession;
+
 // Controls the user nudge animation and toast widget which are used to draw the
 // user's attention towards the given `view_to_be_highlighted`. In the current
 // iteration, this view is the settings button on the capture mode bar, and the
@@ -30,23 +28,25 @@
 // will be shown instead once the selfie camera feature is enabled.
 class UserNudgeController {
  public:
-  explicit UserNudgeController(views::View* view_to_be_highlighted);
+  explicit UserNudgeController(CaptureModeSession* session,
+                               views::View* view_to_be_highlighted);
   UserNudgeController(const UserNudgeController&) = delete;
   UserNudgeController& operator=(const UserNudgeController&) = delete;
   ~UserNudgeController();
 
-  views::Widget* toast_widget() { return toast_widget_.get(); }
   bool is_visible() const { return is_visible_; }
   void set_should_dismiss_nudge_forever(bool value) {
     should_dismiss_nudge_forever_ = value;
   }
+  bool should_dismiss_nudge_forever() const {
+    return should_dismiss_nudge_forever_;
+  }
 
-  // Repositions the animation layers and the toast widget such that they're
-  // correctly parented with the correct bounds on the correct display.
+  // Repositions the animation layers such that they're correctly parented with
+  // the correct bounds on the correct display.
   void Reposition();
 
-  // Animates the animation layers and the toast widget towards the given
-  // visibility state `visible`.
+  // Animates the animation layers towards the given visibility state `visible`.
   void SetVisible(bool visible);
 
  private:
@@ -69,18 +69,13 @@
   // a repeat of this animation after a certain delay using `timer_`.
   void OnBaseRingAnimationEnded();
 
-  // This is the window that will be used to as the parent of the toast widget,
-  // and its layer as the parent of our animation layers (`base_ring_` and
-  // `ripple_ring_`).
+  // This is the window that will be used to as the parent of our animation
+  // layers (`base_ring_` and `ripple_ring_`).
   aura::Window* GetParentWindow() const;
 
-  // Calculates and returns the current screen bounds that should be set on the
-  // `toast_widget_` based on where `view_to_be_highlighted_`'s widget (which is
-  // the capture bar) is.
-  gfx::Rect CalculateToastWidgetScreenBounds() const;
-
-  // Initializes the toast widget and its contents.
-  void BuildToastWidget();
+  // The session that owns `this`. Guaranteed to be non null for the lifetime of
+  // `this`.
+  CaptureModeSession* const capture_session_;
 
   // The view to which we're trying to grab the user's attention.
   views::View* const view_to_be_highlighted_;
@@ -90,10 +85,6 @@
   ui::Layer base_ring_{ui::LAYER_SOLID_COLOR};
   ui::Layer ripple_ring_{ui::LAYER_SOLID_COLOR};
 
-  // The toast widget and its contents view.
-  views::UniqueWidgetPtr toast_widget_ = std::make_unique<views::Widget>();
-  views::Label* toast_label_view_ = nullptr;
-
   // The timer used to repeat the nudge animation after a certain delay.
   base::OneShotTimer timer_;
 
diff --git a/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.cc b/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.cc
index fdb9cb4a..b120813 100644
--- a/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.cc
+++ b/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.cc
@@ -12,6 +12,8 @@
 #include "ash/components/arc/video_accelerator/protected_buffer_manager.h"
 #include "base/bind.h"
 #include "base/files/scoped_file.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/posix/eintr_wrapper.h"
@@ -526,13 +528,13 @@
   DCHECK(secure_mode_);
   DCHECK(!*secure_mode_);
   ContinueDecode(std::move(bitstream_buffer), std::move(handle_fd),
-                 base::subtle::PlatformSharedMemoryRegion());
+                 base::UnsafeSharedMemoryRegion());
 }
 
 void GpuArcVideoDecodeAccelerator::ContinueDecode(
     mojom::BitstreamBufferPtr bitstream_buffer,
     base::ScopedFD handle_fd,
-    base::subtle::PlatformSharedMemoryRegion shm_region) {
+    base::UnsafeSharedMemoryRegion shm_region) {
   DVLOGF(4);
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   if (!vda_) {
@@ -583,10 +585,11 @@
       return;
     }
     DCHECK(!shm_region.IsValid());
-    shm_region = base::subtle::PlatformSharedMemoryRegion::Take(
-        std::move(handle_fd),
-        base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe, handle_size,
-        base::UnguessableToken::Create());
+    shm_region = base::UnsafeSharedMemoryRegion::Deserialize(
+        base::subtle::PlatformSharedMemoryRegion::Take(
+            std::move(handle_fd),
+            base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
+            handle_size, base::UnguessableToken::Create()));
   }
 
   if (!shm_region.IsValid()) {
diff --git a/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.h b/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.h
index caa8576..031ed76 100644
--- a/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.h
+++ b/ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.h
@@ -13,6 +13,7 @@
 #include "ash/components/arc/mojom/video_decode_accelerator.mojom.h"
 #include "base/callback_forward.h"
 #include "base/files/scoped_file.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/threading/thread_checker.h"
 #include "gpu/config/gpu_driver_bug_workarounds.h"
 #include "gpu/config/gpu_preferences.h"
@@ -110,7 +111,7 @@
   // directly.
   void ContinueDecode(mojom::BitstreamBufferPtr bitstream_buffer,
                       base::ScopedFD handle_fd,
-                      base::subtle::PlatformSharedMemoryRegion shm_region);
+                      base::UnsafeSharedMemoryRegion shm_region);
 
   // Posted as a task after getting the result of the first query to the
   // |protected_buffer_manager_| in order to resume decode tasks that were
diff --git a/ash/components/arc/video_accelerator/gpu_arc_video_decoder.cc b/ash/components/arc/video_accelerator/gpu_arc_video_decoder.cc
index 3c08883..6170f1f 100644
--- a/ash/components/arc/video_accelerator/gpu_arc_video_decoder.cc
+++ b/ash/components/arc/video_accelerator/gpu_arc_video_decoder.cc
@@ -12,6 +12,8 @@
 #include "ash/components/arc/video_accelerator/protected_buffer_manager.h"
 #include "base/bind.h"
 #include "base/files/scoped_file.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/posix/eintr_wrapper.h"
 #include "base/threading/sequenced_task_runner_handle.h"
@@ -395,7 +397,7 @@
     uint32_t bytes_used) {
   // TODO(b/189278506) integrate additional memory buffer verification for
   // protected buffers (see crrev.com/3306795).
-  base::subtle::PlatformSharedMemoryRegion shm_region;
+  base::UnsafeSharedMemoryRegion shm_region;
   if (*secure_mode_) {
     // Use protected shared memory associated with the given file descriptor.
     shm_region = protected_buffer_manager_->GetProtectedSharedMemoryRegionFor(
@@ -410,9 +412,11 @@
       VLOGF(1) << "Failed to get size for fd";
       return nullptr;
     }
-    shm_region = base::subtle::PlatformSharedMemoryRegion::Take(
-        std::move(fd), base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
-        size, base::UnguessableToken::Create());
+    shm_region = base::UnsafeSharedMemoryRegion::Deserialize(
+        base::subtle::PlatformSharedMemoryRegion::Take(
+            std::move(fd),
+            base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe, size,
+            base::UnguessableToken::Create()));
     if (!shm_region.IsValid()) {
       VLOGF(1) << "Cannot take file descriptor based shared memory";
       return nullptr;
diff --git a/ash/components/arc/video_accelerator/gpu_arc_video_encode_accelerator.cc b/ash/components/arc/video_accelerator/gpu_arc_video_encode_accelerator.cc
index 456d089..2745a97 100644
--- a/ash/components/arc/video_accelerator/gpu_arc_video_encode_accelerator.cc
+++ b/ash/components/arc/video_accelerator/gpu_arc_video_encode_accelerator.cc
@@ -245,9 +245,11 @@
   // rather than pulling out the fd. https://crbug.com/713763.
   // TODO(rockot): Pass through a real size rather than |0|.
   base::UnguessableToken guid = base::UnguessableToken::Create();
-  auto shm_region = base::subtle::PlatformSharedMemoryRegion::Take(
-      std::move(fd), base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
-      shmem_size, guid);
+  auto shm_region = base::UnsafeSharedMemoryRegion::Deserialize(
+      base::subtle::PlatformSharedMemoryRegion::Take(
+          std::move(fd),
+          base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe, shmem_size,
+          guid));
   if (!shm_region.IsValid()) {
     client_->NotifyError(Error::kInvalidArgumentError);
     return;
diff --git a/ash/components/arc/video_accelerator/oop_arc_video_accelerator_factory.cc b/ash/components/arc/video_accelerator/oop_arc_video_accelerator_factory.cc
index c0e0df13..9ba4a71 100644
--- a/ash/components/arc/video_accelerator/oop_arc_video_accelerator_factory.cc
+++ b/ash/components/arc/video_accelerator/oop_arc_video_accelerator_factory.cc
@@ -8,12 +8,14 @@
 #include "ash/components/arc/video_accelerator/gpu_arc_video_decode_accelerator.h"
 #include "ash/components/arc/video_accelerator/gpu_arc_video_decoder.h"
 #include "ash/components/arc/video_accelerator/protected_buffer_manager.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "gpu/config/gpu_driver_bug_workarounds.h"
 #include "gpu/config/gpu_preferences.h"
 #include "media/base/bind_to_current_loop.h"
 #include "media/gpu/macros.h"
 #include "mojo/public/cpp/bindings/callback_helpers.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
+#include "mojo/public/cpp/system/platform_handle.h"
 
 namespace arc {
 
@@ -67,13 +69,12 @@
       GetProtectedSharedMemoryRegionForResponseCB response_cb,
       mojo::ScopedSharedBufferHandle shared_memory_mojo_handle) {
     if (!shared_memory_mojo_handle.is_valid()) {
-      return std::move(response_cb)
-          .Run(base::subtle::PlatformSharedMemoryRegion());
+      return std::move(response_cb).Run(base::UnsafeSharedMemoryRegion());
     }
 
     // TODO(b/195769334): does anything need to be validated here?
     std::move(response_cb)
-        .Run(mojo::UnwrapPlatformSharedMemoryRegion(
+        .Run(mojo::UnwrapUnsafeSharedMemoryRegion(
             std::move(shared_memory_mojo_handle)));
   }
 
diff --git a/ash/components/arc/video_accelerator/protected_buffer_manager.cc b/ash/components/arc/video_accelerator/protected_buffer_manager.cc
index d317d06..4854fb3 100644
--- a/ash/components/arc/video_accelerator/protected_buffer_manager.cc
+++ b/ash/components/arc/video_accelerator/protected_buffer_manager.cc
@@ -10,7 +10,6 @@
 #include "base/bind.h"
 #include "base/bits.h"
 #include "base/logging.h"
-#include "base/memory/platform_shared_memory_region.h"
 #include "base/memory/unsafe_shared_memory_region.h"
 #include "base/posix/eintr_wrapper.h"
 #include "base/system/sys_info.h"
@@ -47,8 +46,7 @@
   // Downcasting methods to return duplicated handles to the underlying
   // protected buffers for each buffer type, or empty/null handles if not
   // applicable.
-  virtual base::subtle::PlatformSharedMemoryRegion
-  DuplicatePlatformSharedMemoryRegion() const {
+  virtual base::UnsafeSharedMemoryRegion DuplicateSharedMemoryRegion() const {
     return {};
   }
   virtual gfx::NativePixmapHandle DuplicateNativePixmapHandle() const {
@@ -79,15 +77,14 @@
       scoped_refptr<gfx::NativePixmap> dummy_handle,
       size_t size);
 
-  base::subtle::PlatformSharedMemoryRegion DuplicatePlatformSharedMemoryRegion()
-      const override {
+  base::UnsafeSharedMemoryRegion DuplicateSharedMemoryRegion() const override {
     return region_.Duplicate();
   }
 
  private:
   explicit ProtectedSharedMemory(scoped_refptr<gfx::NativePixmap> dummy_handle);
 
-  base::subtle::PlatformSharedMemoryRegion region_;
+  base::UnsafeSharedMemoryRegion region_;
 };
 
 ProtectedBufferManager::ProtectedSharedMemory::ProtectedSharedMemory(
@@ -113,9 +110,7 @@
     return nullptr;
   }
 
-  protected_shmem->region_ =
-      base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-          std::move(shmem_region));
+  protected_shmem->region_ = std::move(shmem_region);
   return protected_shmem;
 }
 
@@ -399,7 +394,7 @@
   allocator_to_buffers_map_.erase(allocator_id);
 }
 
-base::subtle::PlatformSharedMemoryRegion
+base::UnsafeSharedMemoryRegion
 ProtectedBufferManager::GetProtectedSharedMemoryRegionFor(
     base::ScopedFD dummy_fd) {
   uint32_t id = 0;
@@ -412,7 +407,7 @@
   if (iter == buffer_map_.end())
     return {};
 
-  return iter->second->DuplicatePlatformSharedMemoryRegion();
+  return iter->second->DuplicateSharedMemoryRegion();
 }
 
 gfx::NativePixmapHandle
diff --git a/ash/components/arc/video_accelerator/protected_buffer_manager.h b/ash/components/arc/video_accelerator/protected_buffer_manager.h
index 298e061..7e89ca52 100644
--- a/ash/components/arc/video_accelerator/protected_buffer_manager.h
+++ b/ash/components/arc/video_accelerator/protected_buffer_manager.h
@@ -10,8 +10,8 @@
 #include <set>
 
 #include "base/files/scoped_file.h"
-#include "base/memory/platform_shared_memory_region.h"
 #include "base/memory/ref_counted.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/memory/weak_ptr.h"
 #include "base/synchronization/lock.h"
 #include "base/thread_annotations.h"
@@ -34,7 +34,7 @@
   // after use. |response_cb| is called on the calling sequence and may be
   // called before this method returns.
   using GetProtectedSharedMemoryRegionForResponseCB =
-      base::OnceCallback<void(base::subtle::PlatformSharedMemoryRegion)>;
+      base::OnceCallback<void(base::UnsafeSharedMemoryRegion)>;
   virtual void GetProtectedSharedMemoryRegionFor(
       base::ScopedFD dummy_fd,
       GetProtectedSharedMemoryRegionForResponseCB response_cb) = 0;
@@ -70,10 +70,10 @@
   CreateProtectedBufferAllocator(
       scoped_refptr<ProtectedBufferManager> protected_buffer_manager);
 
-  // Return a duplicated PlatformSharedMemoryRegion associated with the
+  // Return a duplicated UnsafeSharedMemoryRegion associated with the
   // |dummy_fd|, if one exists, or an invalid handle otherwise. The client is
   // responsible for closing the handle after use.
-  base::subtle::PlatformSharedMemoryRegion GetProtectedSharedMemoryRegionFor(
+  base::UnsafeSharedMemoryRegion GetProtectedSharedMemoryRegionFor(
       base::ScopedFD dummy_fd);
 
   // Return a duplicated NativePixmapHandle associated with the |dummy_fd|,
diff --git a/ash/components/arc/video_accelerator/protected_buffer_manager_proxy.cc b/ash/components/arc/video_accelerator/protected_buffer_manager_proxy.cc
index 8e21f53..9aa840be 100644
--- a/ash/components/arc/video_accelerator/protected_buffer_manager_proxy.cc
+++ b/ash/components/arc/video_accelerator/protected_buffer_manager_proxy.cc
@@ -6,6 +6,8 @@
 
 #include "ash/components/arc/video_accelerator/arc_video_accelerator_util.h"
 #include "ash/components/arc/video_accelerator/protected_buffer_manager.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "media/gpu/macros.h"
 #include "mojo/public/cpp/system/platform_handle.h"
 
@@ -35,8 +37,14 @@
     return;
   }
 
-  // This ScopedFDPair dance is chromeos-specific.
-  base::subtle::ScopedFDPair fd_pair = region.PassPlatformHandle();
+  // Due to POSIX limitations, the shmem platform handle consists of a pair of
+  // a writable FD and a read-only FD. Since GetProtectedSharedMemoryRegionFor()
+  // returns a base::UnsafeSharedMemoryRegion, only the writable FD should be
+  // present.
+  base::subtle::PlatformSharedMemoryRegion platform_region =
+      base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
+          std::move(region));
+  base::subtle::ScopedFDPair fd_pair = platform_region.PassPlatformHandle();
   std::move(callback).Run(mojo::WrapPlatformFile(std::move(fd_pair.fd)));
 }
 
@@ -45,16 +53,12 @@
     GetProtectedSharedMemoryFromHandleCallback callback) {
   base::ScopedFD unwrapped_fd = UnwrapFdFromMojoHandle(std::move(dummy_handle));
 
-  auto region_platform_handle =
+  base::UnsafeSharedMemoryRegion unsafe_shared_memory_region =
       protected_buffer_manager_->GetProtectedSharedMemoryRegionFor(
           std::move(unwrapped_fd));
-  if (!region_platform_handle.IsValid())
+  if (!unsafe_shared_memory_region.IsValid())
     return std::move(callback).Run(mojo::ScopedSharedBufferHandle());
 
-  base::UnsafeSharedMemoryRegion unsafe_shared_memory_region =
-      base::UnsafeSharedMemoryRegion::Deserialize(
-          std::move(region_platform_handle));
-  CHECK(unsafe_shared_memory_region.IsValid());
   std::move(callback).Run(mojo::WrapUnsafeSharedMemoryRegion(
       std::move(unsafe_shared_memory_region)));
 }
diff --git a/ash/components/device_activity/BUILD.gn b/ash/components/device_activity/BUILD.gn
index 5b125cb..b0d63ec 100644
--- a/ash/components/device_activity/BUILD.gn
+++ b/ash/components/device_activity/BUILD.gn
@@ -20,6 +20,7 @@
     "//chromeos/dbus/session_manager",
     "//chromeos/network",
     "//chromeos/system",
+    "//components/policy/core/common",
     "//components/prefs:prefs",
     "//components/version_info",
     "//crypto:crypto",
@@ -70,6 +71,7 @@
     "//chromeos/network",
     "//chromeos/network:test_support",
     "//chromeos/system:system",
+    "//components/policy/core/common:test_support",
     "//components/prefs:test_support",
     "//components/version_info:channel",
     "//dbus",
diff --git a/ash/components/device_activity/DEPS b/ash/components/device_activity/DEPS
index 056565a..e0d395b 100644
--- a/ash/components/device_activity/DEPS
+++ b/ash/components/device_activity/DEPS
@@ -1,6 +1,7 @@
 include_rules = [
   "+chromeos/dbus/session_manager",
   "+chromeos/ash/components/dbus/system_clock",
+  "+components/policy",
   "+components/version_info",
   "+crypto",
   "+dbus",
diff --git a/ash/components/device_activity/daily_use_case_impl.cc b/ash/components/device_activity/daily_use_case_impl.cc
index 1791ba0e..b364992 100644
--- a/ash/components/device_activity/daily_use_case_impl.cc
+++ b/ash/components/device_activity/daily_use_case_impl.cc
@@ -51,6 +51,7 @@
   DeviceMetadata* device_metadata = import_request.mutable_device_metadata();
   device_metadata->set_chromeos_version(GetChromeOSVersion());
   device_metadata->set_chromeos_channel(GetChromeOSChannel());
+  device_metadata->set_market_segment(GetMarketSegment());
 
   // TODO(hirthanan): This is used for debugging purposes until crbug/1289722
   // has launched.
diff --git a/ash/components/device_activity/daily_use_case_impl_unittest.cc b/ash/components/device_activity/daily_use_case_impl_unittest.cc
index 801f27f8..6427520 100644
--- a/ash/components/device_activity/daily_use_case_impl_unittest.cc
+++ b/ash/components/device_activity/daily_use_case_impl_unittest.cc
@@ -25,7 +25,8 @@
 constexpr char kFakePsmDeviceActiveSecret[] = "FAKE_PSM_DEVICE_ACTIVE_SECRET";
 
 constexpr ChromeDeviceMetadataParameters kFakeChromeParameters = {
-    version_info::Channel::STABLE /* chromeos_channel */
+    version_info::Channel::STABLE /* chromeos_channel */,
+    MarketSegment::MARKET_SEGMENT_UNKNOWN /* market_segment */,
 };
 
 }  // namespace
@@ -54,12 +55,11 @@
   TestingPrefServiceSimple local_state_;
 };
 
-TEST_F(DailyUseCaseImplTest, GetLastKnownPingTimestampReturnsEpochOnNoPrefs) {
-  EXPECT_EQ(daily_use_case_impl_->GetLastKnownPingTimestamp(),
-            base::Time::UnixEpoch());
+TEST_F(DailyUseCaseImplTest, CheckIfLastKnownPingTimestampNotSet) {
+  EXPECT_FALSE(daily_use_case_impl_->IsLastKnownPingTimestampSet());
 }
 
-TEST_F(DailyUseCaseImplTest, CheckLocalStateUpdatesCorrectly) {
+TEST_F(DailyUseCaseImplTest, CheckIfLastKnownPingTimestampSet) {
   // Create fixed timestamp to see if local state updates value correctly.
   base::Time new_daily_ts;
   EXPECT_TRUE(
@@ -69,6 +69,7 @@
   daily_use_case_impl_->SetLastKnownPingTimestamp(new_daily_ts);
 
   EXPECT_EQ(daily_use_case_impl_->GetLastKnownPingTimestamp(), new_daily_ts);
+  EXPECT_TRUE(daily_use_case_impl_->IsLastKnownPingTimestampSet());
 }
 
 TEST_F(DailyUseCaseImplTest, CheckGenerateUTCWindowIdentifierHasValidFormat) {
diff --git a/ash/components/device_activity/device_active_use_case.cc b/ash/components/device_activity/device_active_use_case.cc
index 11e8ab7..762d024 100644
--- a/ash/components/device_activity/device_active_use_case.cc
+++ b/ash/components/device_activity/device_active_use_case.cc
@@ -44,16 +44,18 @@
   return local_state_;
 }
 
-// Return the last known ping timestamp from local state pref.
 base::Time DeviceActiveUseCase::GetLastKnownPingTimestamp() const {
   return GetLocalState()->GetTime(use_case_pref_key_);
 }
 
-// Set the last known ping timestamp in local state pref.
 void DeviceActiveUseCase::SetLastKnownPingTimestamp(base::Time new_ts) {
   GetLocalState()->SetTime(use_case_pref_key_, new_ts);
 }
 
+bool DeviceActiveUseCase::IsLastKnownPingTimestampSet() const {
+  return GetLastKnownPingTimestamp() != base::Time::UnixEpoch();
+}
+
 psm_rlwe::RlweUseCase DeviceActiveUseCase::GetPsmUseCase() const {
   return psm_use_case_;
 }
@@ -155,6 +157,10 @@
   }
 }
 
+MarketSegment DeviceActiveUseCase::GetMarketSegment() const {
+  return chrome_passed_device_params_.market_segment;
+}
+
 absl::optional<psm_rlwe::RlwePlaintextId>
 DeviceActiveUseCase::GeneratePsmIdentifier() const {
   const std::string psm_use_case = psm_rlwe::RlweUseCase_Name(GetPsmUseCase());
diff --git a/ash/components/device_activity/device_active_use_case.h b/ash/components/device_activity/device_active_use_case.h
index af2f34b..6078be5 100644
--- a/ash/components/device_activity/device_active_use_case.h
+++ b/ash/components/device_activity/device_active_use_case.h
@@ -30,6 +30,7 @@
 // on chrome browser.
 struct COMPONENT_EXPORT(ASH_DEVICE_ACTIVITY) ChromeDeviceMetadataParameters {
   version_info::Channel chromeos_channel;
+  MarketSegment market_segment;
 };
 
 // Base class for device active use cases.
@@ -63,8 +64,12 @@
   // timestamp from the local state pref.
   base::Time GetLastKnownPingTimestamp() const;
 
+  // Set the last known ping timestamp in local state pref.
   void SetLastKnownPingTimestamp(base::Time new_ts);
 
+  // Return true if the |use_case_pref_key_| is not Unix Epoch (default value).
+  bool IsLastKnownPingTimestampSet() const;
+
   // Retrieve the PSM use case.
   // The PSM dataset on the serverside is segmented by the PSM use case.
   private_membership::rlwe::RlweUseCase GetPsmUseCase() const;
@@ -112,6 +117,9 @@
   // Retrieve the ChromeOS release channel.
   Channel GetChromeOSChannel() const;
 
+  // Retrieve the ChromeOS device market segment.
+  MarketSegment GetMarketSegment() const;
+
  private:
   // Field is used to identify a fixed window of time for device active
   // counting. Privacy compliance is guaranteed by retrieving the
diff --git a/ash/components/device_activity/device_activity_client.cc b/ash/components/device_activity/device_activity_client.cc
index ca97456..f797a8c 100644
--- a/ash/components/device_activity/device_activity_client.cc
+++ b/ash/components/device_activity/device_activity_client.cc
@@ -445,8 +445,7 @@
         // new, powerwashed, recovered, RMA, or the local state was corrupted.
         if (base::FeatureList::IsEnabled(
                 features::kDeviceActiveClientDailyCheckMembership) &&
-            (current_use_case->GetLastKnownPingTimestamp() ==
-             base::Time::UnixEpoch())) {
+            !current_use_case->IsLastKnownPingTimestampSet()) {
           TransitionToCheckMembershipOprf(current_use_case);
           return;
         } else {
@@ -460,8 +459,7 @@
         // new, powerwashed, recovered, RMA, or the local state was corrupted.
         if (base::FeatureList::IsEnabled(
                 features::kDeviceActiveClientMonthlyCheckMembership) &&
-            (current_use_case->GetLastKnownPingTimestamp() ==
-             base::Time::UnixEpoch())) {
+            !current_use_case->IsLastKnownPingTimestampSet()) {
           TransitionToCheckMembershipOprf(current_use_case);
           return;
         }
diff --git a/ash/components/device_activity/device_activity_client_unittest.cc b/ash/components/device_activity/device_activity_client_unittest.cc
index 82536d7..2ca1f8b 100644
--- a/ash/components/device_activity/device_activity_client_unittest.cc
+++ b/ash/components/device_activity/device_activity_client_unittest.cc
@@ -73,7 +73,8 @@
 constexpr char kFakeFresnelApiKey[] = "FAKE_FRESNEL_API_KEY";
 
 constexpr ChromeDeviceMetadataParameters kFakeChromeParameters = {
-    version_info::Channel::STABLE  // chromeos_channel
+    version_info::Channel::STABLE /* chromeos_channel */,
+    MarketSegment::MARKET_SEGMENT_UNKNOWN /* market_segment */,
 };
 
 // Number of test cases exist in cros_test_data.binarypb file, which is part of
@@ -252,7 +253,7 @@
 
   // Initialize well formed OPRF response body used to deterministically fake
   // PSM network responses.
-  std::string GetFresnelOprfResponse(
+  const std::string GetFresnelOprfResponse(
       const psm_rlwe::PrivateMembershipRlweClientRegressionTestData::TestCase&
           test_case) {
     FresnelPsmRlweOprfResponse psm_oprf_response;
@@ -262,7 +263,7 @@
 
   // Initialize well formed Query response body used to deterministically fake
   // PSM network responses.
-  std::string GetFresnelQueryResponse(
+  const std::string GetFresnelQueryResponse(
       const psm_rlwe::PrivateMembershipRlweClientRegressionTestData::TestCase&
           test_case) {
     FresnelPsmRlweQueryResponse psm_query_response;
@@ -341,6 +342,27 @@
         prefs::kDeviceActiveLastKnownMonthlyPingTimestamp);
   }
 
+  void SimulateOprfResponse(const std::string& serialized_response_body,
+                            net::HttpStatusCode response_code) {
+    test_url_loader_factory_.SimulateResponseForPendingRequest(
+        GetFresnelTestEndpoint(kPsmOprfRequestEndpoint),
+        serialized_response_body, response_code);
+  }
+
+  void SimulateQueryResponse(const std::string& serialized_response_body,
+                             net::HttpStatusCode response_code) {
+    test_url_loader_factory_.SimulateResponseForPendingRequest(
+        GetFresnelTestEndpoint(kPsmQueryRequestEndpoint),
+        serialized_response_body, response_code);
+  }
+
+  void SimulateImportResponse(const std::string& serialized_response_body,
+                              net::HttpStatusCode response_code) {
+    test_url_loader_factory_.SimulateResponseForPendingRequest(
+        GetFresnelTestEndpoint(kPsmImportRequestEndpoint),
+        serialized_response_body, response_code);
+  }
+
   void CreateWifiNetworkConfig() {
     ASSERT_TRUE(wifi_network_service_path_.empty());
 
@@ -559,7 +581,7 @@
                  << "PSM use case: "
                  << psm_rlwe::RlweUseCase_Name(use_case->GetPsmUseCase()));
 
-    EXPECT_EQ(use_case->GetLastKnownPingTimestamp(), base::Time::UnixEpoch());
+    EXPECT_FALSE(use_case->IsLastKnownPingTimestampSet());
   }
 
   EXPECT_TRUE(device_activity_client_->GetReportTimer()->IsRunning());
@@ -608,15 +630,11 @@
     EXPECT_EQ(device_activity_client_->GetState(),
               DeviceActivityClient::State::kCheckingMembershipOprf);
 
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmOprfRequestEndpoint),
-        GetFresnelOprfResponse(nonmember_test_case), net::HTTP_OK);
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmQueryRequestEndpoint),
-        GetFresnelQueryResponse(nonmember_test_case), net::HTTP_OK);
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(),
-        net::HTTP_OK);
+    SimulateOprfResponse(GetFresnelOprfResponse(nonmember_test_case),
+                         net::HTTP_OK);
+    SimulateQueryResponse(GetFresnelQueryResponse(nonmember_test_case),
+                          net::HTTP_OK);
+    SimulateImportResponse(std::string(), net::HTTP_OK);
     task_environment_.RunUntilIdle();
   }
 
@@ -647,22 +665,18 @@
     // On first ever ping, we begin the check membership protocol
     // since the local state pref for that use case is by default unix
     // epoch.
-    EXPECT_EQ(use_case->GetLastKnownPingTimestamp(), base::Time::UnixEpoch());
+    EXPECT_FALSE(use_case->IsLastKnownPingTimestampSet());
     EXPECT_EQ(device_activity_client_->GetState(),
               DeviceActivityClient::State::kCheckingMembershipOprf);
 
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmOprfRequestEndpoint),
-        GetFresnelOprfResponse(nonmember_test_case), net::HTTP_OK);
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmQueryRequestEndpoint),
-        GetFresnelQueryResponse(nonmember_test_case), net::HTTP_OK);
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(),
-        net::HTTP_OK);
+    SimulateOprfResponse(GetFresnelOprfResponse(nonmember_test_case),
+                         net::HTTP_OK);
+    SimulateQueryResponse(GetFresnelQueryResponse(nonmember_test_case),
+                          net::HTTP_OK);
+    SimulateImportResponse(std::string(), net::HTTP_OK);
     task_environment_.RunUntilIdle();
 
-    EXPECT_NE(use_case->GetLastKnownPingTimestamp(), base::Time::UnixEpoch());
+    EXPECT_TRUE(use_case->IsLastKnownPingTimestampSet());
   }
 
   EXPECT_EQ(test_url_loader_factory_.NumPending(), 0);
@@ -689,13 +703,11 @@
                  << "PSM use case: "
                  << psm_rlwe::RlweUseCase_Name(use_case->GetPsmUseCase()));
 
-    EXPECT_NE(use_case->GetLastKnownPingTimestamp(), base::Time::UnixEpoch());
+    EXPECT_TRUE(use_case->IsLastKnownPingTimestampSet());
     EXPECT_EQ(device_activity_client_->GetState(),
               DeviceActivityClient::State::kCheckingIn);
 
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(),
-        net::HTTP_OK);
+    SimulateImportResponse(std::string(), net::HTTP_OK);
     task_environment_.RunUntilIdle();
 
     // base::Time::Now() is updated in |DeviceActivityClientTest| constructor.
@@ -719,9 +731,7 @@
               DeviceActivityClient::State::kCheckingMembershipOprf);
 
     // Return an invalid Fresnel OPRF response.
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmOprfRequestEndpoint),
-        /*fresnel_oprf_response*/ std::string(), net::HTTP_OK);
+    SimulateOprfResponse(/*fresnel_oprf_response*/ std::string(), net::HTTP_OK);
     task_environment_.RunUntilIdle();
   }
 
@@ -748,14 +758,11 @@
               DeviceActivityClient::State::kCheckingMembershipOprf);
 
     // Return a valid OPRF response.
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmOprfRequestEndpoint),
-        GetFresnelOprfResponse(nonmember_test_case), net::HTTP_OK);
+    SimulateOprfResponse(GetFresnelOprfResponse(nonmember_test_case),
+                         net::HTTP_OK);
 
     // Return an invalid Query response.
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmQueryRequestEndpoint), std::string(),
-        net::HTTP_OK);
+    SimulateQueryResponse(std::string(), net::HTTP_OK);
     task_environment_.RunUntilIdle();
   }
 
@@ -781,7 +788,7 @@
     // On first ever ping, we begin the check membership protocol
     // since the local state pref for that use case is by default unix
     // epoch.
-    EXPECT_EQ(use_case->GetLastKnownPingTimestamp(), base::Time::UnixEpoch());
+    EXPECT_FALSE(use_case->IsLastKnownPingTimestampSet());
     EXPECT_EQ(device_activity_client_->GetState(),
               DeviceActivityClient::State::kCheckingMembershipOprf);
 
@@ -789,26 +796,20 @@
         psm_rlwe::RlweUseCase::CROS_FRESNEL_DAILY) {
       // Daily use case will terminate while failing to parse
       // this invalid OPRF response.
-      test_url_loader_factory_.SimulateResponseForPendingRequest(
-          GetFresnelTestEndpoint(kPsmOprfRequestEndpoint), std::string(),
-          net::HTTP_OK);
+      SimulateOprfResponse(std::string(), net::HTTP_OK);
 
       task_environment_.RunUntilIdle();
 
       // Failed to update the local state since the OPRF response was invalid.
-      EXPECT_EQ(use_case->GetLastKnownPingTimestamp(), base::Time::UnixEpoch());
+      EXPECT_FALSE(use_case->IsLastKnownPingTimestampSet());
     } else if (use_case->GetPsmUseCase() ==
                psm_rlwe::RlweUseCase::CROS_FRESNEL_MONTHLY) {
       // Monthly use case will return valid psm network request responses.
-      test_url_loader_factory_.SimulateResponseForPendingRequest(
-          GetFresnelTestEndpoint(kPsmOprfRequestEndpoint),
-          GetFresnelOprfResponse(nonmember_test_case), net::HTTP_OK);
-      test_url_loader_factory_.SimulateResponseForPendingRequest(
-          GetFresnelTestEndpoint(kPsmQueryRequestEndpoint),
-          GetFresnelQueryResponse(nonmember_test_case), net::HTTP_OK);
-      test_url_loader_factory_.SimulateResponseForPendingRequest(
-          GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(),
-          net::HTTP_OK);
+      SimulateOprfResponse(GetFresnelOprfResponse(nonmember_test_case),
+                           net::HTTP_OK);
+      SimulateQueryResponse(GetFresnelQueryResponse(nonmember_test_case),
+                            net::HTTP_OK);
+      SimulateImportResponse(std::string(), net::HTTP_OK);
 
       task_environment_.RunUntilIdle();
 
@@ -843,22 +844,18 @@
     // On first ever ping, we begin the check membership protocol
     // since the local state pref for that use case is by default unix
     // epoch.
-    EXPECT_EQ(use_case->GetLastKnownPingTimestamp(), base::Time::UnixEpoch());
+    EXPECT_FALSE(use_case->IsLastKnownPingTimestampSet());
     EXPECT_EQ(device_activity_client_->GetState(),
               DeviceActivityClient::State::kCheckingMembershipOprf);
 
     if (use_case->GetPsmUseCase() ==
         psm_rlwe::RlweUseCase::CROS_FRESNEL_DAILY) {
       // Daily use case will return valid psm network request responses.
-      test_url_loader_factory_.SimulateResponseForPendingRequest(
-          GetFresnelTestEndpoint(kPsmOprfRequestEndpoint),
-          GetFresnelOprfResponse(nonmember_test_case), net::HTTP_OK);
-      test_url_loader_factory_.SimulateResponseForPendingRequest(
-          GetFresnelTestEndpoint(kPsmQueryRequestEndpoint),
-          GetFresnelQueryResponse(nonmember_test_case), net::HTTP_OK);
-      test_url_loader_factory_.SimulateResponseForPendingRequest(
-          GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(),
-          net::HTTP_OK);
+      SimulateOprfResponse(GetFresnelOprfResponse(nonmember_test_case),
+                           net::HTTP_OK);
+      SimulateQueryResponse(GetFresnelQueryResponse(nonmember_test_case),
+                            net::HTTP_OK);
+      SimulateImportResponse(std::string(), net::HTTP_OK);
 
       task_environment_.RunUntilIdle();
 
@@ -869,14 +866,12 @@
                psm_rlwe::RlweUseCase::CROS_FRESNEL_MONTHLY) {
       // Monthly use case will terminate while failing to parse
       // this invalid OPRF response.
-      test_url_loader_factory_.SimulateResponseForPendingRequest(
-          GetFresnelTestEndpoint(kPsmOprfRequestEndpoint), std::string(),
-          net::HTTP_OK);
+      SimulateOprfResponse(std::string(), net::HTTP_OK);
 
       task_environment_.RunUntilIdle();
 
       // Failed to update the local state since the OPRF response was invalid.
-      EXPECT_EQ(use_case->GetLastKnownPingTimestamp(), base::Time::UnixEpoch());
+      EXPECT_FALSE(use_case->IsLastKnownPingTimestampSet());
     } else {
       // Currently we only support daily, and monthly use cases.
       NOTREACHED() << "Invalid Use Case.";
@@ -936,15 +931,11 @@
               DeviceActivityClient::State::kCheckingMembershipOprf);
     base::Time prev_time = use_case->GetLastKnownPingTimestamp();
 
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmOprfRequestEndpoint),
-        GetFresnelOprfResponse(nonmember_test_case), net::HTTP_OK);
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmQueryRequestEndpoint),
-        GetFresnelQueryResponse(nonmember_test_case), net::HTTP_OK);
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(),
-        net::HTTP_OK);
+    SimulateOprfResponse(GetFresnelOprfResponse(nonmember_test_case),
+                         net::HTTP_OK);
+    SimulateQueryResponse(GetFresnelQueryResponse(nonmember_test_case),
+                          net::HTTP_OK);
+    SimulateImportResponse(std::string(), net::HTTP_OK);
     task_environment_.RunUntilIdle();
 
     // After a PSM identifier is checked in, local state prefs is updated.
@@ -997,15 +988,11 @@
     EXPECT_EQ(device_activity_client_->GetState(),
               DeviceActivityClient::State::kCheckingMembershipOprf);
 
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmOprfRequestEndpoint),
-        GetFresnelOprfResponse(nonmember_test_case), net::HTTP_OK);
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmQueryRequestEndpoint),
-        GetFresnelQueryResponse(nonmember_test_case), net::HTTP_OK);
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(),
-        net::HTTP_OK);
+    SimulateOprfResponse(GetFresnelOprfResponse(nonmember_test_case),
+                         net::HTTP_OK);
+    SimulateQueryResponse(GetFresnelQueryResponse(nonmember_test_case),
+                          net::HTTP_OK);
+    SimulateImportResponse(std::string(), net::HTTP_OK);
     task_environment_.RunUntilIdle();
   }
 
@@ -1030,9 +1017,7 @@
   // Return well formed Import response body for the DAILY use case.
   // The time was forwarded by 1 day, which means only the daily use case will
   // report actives again.
-  test_url_loader_factory_.SimulateResponseForPendingRequest(
-      GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(),
-      net::HTTP_OK);
+  SimulateImportResponse(std::string(), net::HTTP_OK);
   task_environment_.RunUntilIdle();
 
   // Return back to |kIdle| state after successful check-in of daily use case.
@@ -1057,15 +1042,11 @@
     EXPECT_EQ(device_activity_client_->GetState(),
               DeviceActivityClient::State::kCheckingMembershipOprf);
 
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmOprfRequestEndpoint),
-        GetFresnelOprfResponse(nonmember_test_case), net::HTTP_OK);
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmQueryRequestEndpoint),
-        GetFresnelQueryResponse(nonmember_test_case), net::HTTP_OK);
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(),
-        net::HTTP_OK);
+    SimulateOprfResponse(GetFresnelOprfResponse(nonmember_test_case),
+                         net::HTTP_OK);
+    SimulateQueryResponse(GetFresnelQueryResponse(nonmember_test_case),
+                          net::HTTP_OK);
+    SimulateImportResponse(std::string(), net::HTTP_OK);
     task_environment_.RunUntilIdle();
   }
 
@@ -1107,15 +1088,11 @@
     EXPECT_EQ(device_activity_client_->GetState(),
               DeviceActivityClient::State::kCheckingMembershipOprf);
 
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmOprfRequestEndpoint),
-        GetFresnelOprfResponse(nonmember_test_case), net::HTTP_OK);
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmQueryRequestEndpoint),
-        GetFresnelQueryResponse(nonmember_test_case), net::HTTP_OK);
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(),
-        net::HTTP_OK);
+    SimulateOprfResponse(GetFresnelOprfResponse(nonmember_test_case),
+                         net::HTTP_OK);
+    SimulateQueryResponse(GetFresnelQueryResponse(nonmember_test_case),
+                          net::HTTP_OK);
+    SimulateImportResponse(std::string(), net::HTTP_OK);
     task_environment_.RunUntilIdle();
   }
 
@@ -1151,9 +1128,7 @@
       EXPECT_EQ(device_activity_client_->GetState(),
                 DeviceActivityClient::State::kCheckingIn);
 
-      test_url_loader_factory_.SimulateResponseForPendingRequest(
-          GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(),
-          net::HTTP_OK);
+      SimulateImportResponse(std::string(), net::HTTP_OK);
       task_environment_.RunUntilIdle();
     }
   }
@@ -1182,19 +1157,15 @@
     base::Time prev_time = use_case->GetLastKnownPingTimestamp();
 
     // Mock Successful |kCheckingMembershipOprf|.
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmOprfRequestEndpoint),
-        GetFresnelOprfResponse(nonmember_test_case), net::HTTP_OK);
+    SimulateOprfResponse(GetFresnelOprfResponse(nonmember_test_case),
+                         net::HTTP_OK);
 
     // Mock Successful |kCheckingMembershipQuery|.
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmQueryRequestEndpoint),
-        GetFresnelQueryResponse(nonmember_test_case), net::HTTP_OK);
+    SimulateQueryResponse(GetFresnelQueryResponse(nonmember_test_case),
+                          net::HTTP_OK);
 
     // Mock Successful |kCheckingIn|.
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(),
-        net::HTTP_OK);
+    SimulateImportResponse(std::string(), net::HTTP_OK);
     task_environment_.RunUntilIdle();
 
     base::Time new_time = use_case->GetLastKnownPingTimestamp();
@@ -1226,19 +1197,15 @@
     base::Time prev_time = use_case->GetLastKnownPingTimestamp();
 
     // Mock Successful |kCheckingMembershipOprf|.
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmOprfRequestEndpoint),
-        GetFresnelOprfResponse(nonmember_test_case), net::HTTP_OK);
+    SimulateOprfResponse(GetFresnelOprfResponse(nonmember_test_case),
+                         net::HTTP_OK);
 
     // Mock Successful |kCheckingMembershipQuery|.
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmQueryRequestEndpoint),
-        GetFresnelQueryResponse(nonmember_test_case), net::HTTP_OK);
+    SimulateQueryResponse(GetFresnelQueryResponse(nonmember_test_case),
+                          net::HTTP_OK);
 
     // Mock Successful |kCheckingIn|.
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(),
-        net::HTTP_OK);
+    SimulateImportResponse(std::string(), net::HTTP_OK);
     task_environment_.RunUntilIdle();
 
     base::Time new_time = use_case->GetLastKnownPingTimestamp();
@@ -1282,19 +1249,15 @@
                  << psm_rlwe::RlweUseCase_Name(use_case->GetPsmUseCase()));
 
     // Mock Successful |kCheckingMembershipOprf|.
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmOprfRequestEndpoint),
-        GetFresnelOprfResponse(nonmember_test_case), net::HTTP_OK);
+    SimulateOprfResponse(GetFresnelOprfResponse(nonmember_test_case),
+                         net::HTTP_OK);
 
     // Mock Successful |kCheckingMembershipQuery|.
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmQueryRequestEndpoint),
-        GetFresnelQueryResponse(nonmember_test_case), net::HTTP_OK);
+    SimulateQueryResponse(GetFresnelQueryResponse(nonmember_test_case),
+                          net::HTTP_OK);
 
     // Mock Successful |kCheckingIn|.
-    test_url_loader_factory_.SimulateResponseForPendingRequest(
-        GetFresnelTestEndpoint(kPsmImportRequestEndpoint), std::string(),
-        net::HTTP_OK);
+    SimulateImportResponse(std::string(), net::HTTP_OK);
     task_environment_.RunUntilIdle();
   }
 
diff --git a/ash/components/device_activity/device_activity_controller.cc b/ash/components/device_activity/device_activity_controller.cc
index 1ffaa78..6e1a8541 100644
--- a/ash/components/device_activity/device_activity_controller.cc
+++ b/ash/components/device_activity/device_activity_controller.cc
@@ -30,6 +30,31 @@
 namespace {
 DeviceActivityController* g_ash_device_activity_controller = nullptr;
 
+// Policy device modes that should be classified as not being set.
+static const std::unordered_set<policy::DeviceMode>& DeviceModeNotSet() {
+  static const std::unordered_set<policy::DeviceMode> kModeNotSet(
+      {policy::DeviceMode::DEVICE_MODE_PENDING,
+       policy::DeviceMode::DEVICE_MODE_NOT_SET});
+  return kModeNotSet;
+}
+
+// Policy device modes that should be classified as consumer devices.
+static const std::unordered_set<policy::DeviceMode>& DeviceModeConsumer() {
+  static const std::unordered_set<policy::DeviceMode> kModeConsumer(
+      {policy::DeviceMode::DEVICE_MODE_CONSUMER,
+       policy::DeviceMode::DEVICE_MODE_CONSUMER_KIOSK_AUTOLAUNCH});
+  return kModeConsumer;
+}
+
+// Policy device modes that should be classified as enterprise devices.
+static const std::unordered_set<policy::DeviceMode>& DeviceModeEnterprise() {
+  static const std::unordered_set<policy::DeviceMode> kModeEnterprise(
+      {policy::DeviceMode::DEVICE_MODE_ENTERPRISE,
+       policy::DeviceMode::DEVICE_MODE_ENTERPRISE_AD,
+       policy::DeviceMode::DEVICE_MODE_DEMO});
+  return kModeEnterprise;
+}
+
 // Production edge server for reporting device actives.
 // TODO(https://crbug.com/1267432): Enable passing base url as a runtime flag.
 const char kFresnelBaseUrl[] = "https://crosfresnel-pa.googleapis.com";
@@ -98,6 +123,35 @@
   return delay_on_first_chrome_run;
 }
 
+// static
+MarketSegment DeviceActivityController::GetMarketSegment(
+    policy::DeviceMode device_mode,
+    policy::MarketSegment device_market_segment) {
+  // Determine Fresnel market segment using the retrieved device policy
+  // |device_mode| and |device_market_segment|.
+  if (DeviceModeNotSet().count(device_mode)) {
+    return MARKET_SEGMENT_UNKNOWN;
+  }
+
+  if (DeviceModeConsumer().count(device_mode)) {
+    return MARKET_SEGMENT_CONSUMER;
+  }
+
+  if (DeviceModeEnterprise().count(device_mode)) {
+    if (device_market_segment == policy::MarketSegment::ENTERPRISE) {
+      return MARKET_SEGMENT_ENTERPRISE;
+    }
+
+    if (device_market_segment == policy::MarketSegment::EDUCATION) {
+      return MARKET_SEGMENT_EDUCATION;
+    }
+
+    return MARKET_SEGMENT_ENTERPRISE_ENROLLED_BUT_UNKNOWN;
+  }
+
+  return MARKET_SEGMENT_UNKNOWN;
+}
+
 DeviceActivityController::DeviceActivityController(
     const ChromeDeviceMetadataParameters& chrome_passed_device_params,
     PrefService* local_state,
diff --git a/ash/components/device_activity/device_activity_controller.h b/ash/components/device_activity/device_activity_controller.h
index 4d6894fd..7d373ba4 100644
--- a/ash/components/device_activity/device_activity_controller.h
+++ b/ash/components/device_activity/device_activity_controller.h
@@ -11,6 +11,7 @@
 #include "base/component_export.h"
 #include "base/time/time.h"
 #include "chromeos/system/statistics_provider.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
 class PrefRegistrySimple;
@@ -40,6 +41,11 @@
   // reporting.
   static base::TimeDelta DetermineStartUpDelay(base::Time chrome_first_run_ts);
 
+  // Determines the market segment from the loaded ChromeOS device policies.
+  static MarketSegment GetMarketSegment(
+      policy::DeviceMode device_mode,
+      policy::MarketSegment device_market_segment);
+
   DeviceActivityController(
       const ChromeDeviceMetadataParameters& chrome_passed_device_params,
       PrefService* local_state,
diff --git a/ash/components/device_activity/device_activity_controller_unittest.cc b/ash/components/device_activity/device_activity_controller_unittest.cc
index f09392a5e..f7454cb 100644
--- a/ash/components/device_activity/device_activity_controller_unittest.cc
+++ b/ash/components/device_activity/device_activity_controller_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/timer/mock_timer.h"
 #include "chromeos/dbus/session_manager/session_manager_client.h"
 #include "chromeos/system/fake_statistics_provider.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
 #include "components/prefs/testing_pref_service.h"
 #include "components/version_info/channel.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
@@ -24,7 +25,8 @@
 namespace {
 
 constexpr ChromeDeviceMetadataParameters kFakeChromeParameters = {
-    version_info::Channel::STABLE  // chromeos_channel
+    version_info::Channel::STABLE /* chromeos_channel */,
+    MarketSegment::MARKET_SEGMENT_UNKNOWN /* market_segment */,
 };
 
 }  // namespace
diff --git a/ash/components/device_activity/fresnel_service.proto b/ash/components/device_activity/fresnel_service.proto
index 85ca5daa..97b2ae2 100644
--- a/ash/components/device_activity/fresnel_service.proto
+++ b/ash/components/device_activity/fresnel_service.proto
@@ -23,6 +23,21 @@
   CHANNEL_STABLE = 4;
 }
 
+enum MarketSegment {
+  // Default segment, should not be in this state.
+  MARKET_SEGMENT_UNSPECIFIED = 0;
+  // Device is not yet enrolled, owned or have market segment available.
+  MARKET_SEGMENT_UNKNOWN = 1;
+  // Device is locally owned as a consumer device.
+  MARKET_SEGMENT_CONSUMER = 2;
+  // Device is enterprise enrolled but does not have enterprise market segment.
+  MARKET_SEGMENT_ENTERPRISE_ENROLLED_BUT_UNKNOWN = 3;
+  // Device is enterprise enrolled and has enterprise market segment.
+  MARKET_SEGMENT_ENTERPRISE = 4;
+  // Device is enterprise enrolled and has education market segment.
+  MARKET_SEGMENT_EDUCATION = 5;
+};
+
 // The Chrome OS device metadata which is sent in PSM import requests.
 // Each new field to this message requires privacy approval.
 // Next ID: 6
@@ -41,8 +56,9 @@
   // i.e. US for United States of America, CA for Canada.
   optional string country_code = 3;
 
-  // Reserved for market segment.
-  reserved 4;
+  // Device market segment is one of the northstar breakout dimensions for
+  // active device counting.
+  optional MarketSegment market_segment = 4;
 
   // ChromeOS channel is used to determine the breakdown of devices that are
   // coming from canary, dev, beta, stable, or unknown channels.
diff --git a/ash/components/device_activity/monthly_use_case_impl.cc b/ash/components/device_activity/monthly_use_case_impl.cc
index dd27199..02fa4c0 100644
--- a/ash/components/device_activity/monthly_use_case_impl.cc
+++ b/ash/components/device_activity/monthly_use_case_impl.cc
@@ -51,6 +51,7 @@
   DeviceMetadata* device_metadata = import_request.mutable_device_metadata();
   device_metadata->set_chromeos_version(GetChromeOSVersion());
   device_metadata->set_chromeos_channel(GetChromeOSChannel());
+  device_metadata->set_market_segment(GetMarketSegment());
 
   // TODO(hirthanan): This is used for debugging purposes until crbug/1289722
   // has launched.
diff --git a/ash/components/device_activity/monthly_use_case_impl_unittest.cc b/ash/components/device_activity/monthly_use_case_impl_unittest.cc
index 074790d..31b6bd06 100644
--- a/ash/components/device_activity/monthly_use_case_impl_unittest.cc
+++ b/ash/components/device_activity/monthly_use_case_impl_unittest.cc
@@ -25,11 +25,13 @@
 constexpr char kFakePsmDeviceActiveSecret[] = "FAKE_PSM_DEVICE_ACTIVE_SECRET";
 
 constexpr ChromeDeviceMetadataParameters kFakeChromeParameters = {
-    version_info::Channel::STABLE /* chromeos_channel */
+    version_info::Channel::STABLE /* chromeos_channel */,
+    MarketSegment::MARKET_SEGMENT_UNKNOWN /* market_segment */,
 };
 
 }  // namespace
 
+// TODO(hirthanan): Move shared tests to DeviceActiveUseCase base class.
 class MonthlyUseCaseImplTest : public testing::Test {
  public:
   MonthlyUseCaseImplTest() = default;
@@ -54,12 +56,11 @@
   TestingPrefServiceSimple local_state_;
 };
 
-TEST_F(MonthlyUseCaseImplTest, GetLastKnownPingTimestampReturnsEpochOnNoPrefs) {
-  EXPECT_EQ(monthly_use_case_impl_->GetLastKnownPingTimestamp(),
-            base::Time::UnixEpoch());
+TEST_F(MonthlyUseCaseImplTest, CheckIfLastKnownPingTimestampNotSet) {
+  EXPECT_FALSE(monthly_use_case_impl_->IsLastKnownPingTimestampSet());
 }
 
-TEST_F(MonthlyUseCaseImplTest, CheckLocalStateUpdatesCorrectly) {
+TEST_F(MonthlyUseCaseImplTest, CheckIfLastKnownPingTimestampSet) {
   // Create fixed timestamp to see if local state updates value correctly.
   base::Time new_monthly_ts;
   EXPECT_TRUE(
@@ -70,6 +71,7 @@
 
   EXPECT_EQ(monthly_use_case_impl_->GetLastKnownPingTimestamp(),
             new_monthly_ts);
+  EXPECT_TRUE(monthly_use_case_impl_->IsLastKnownPingTimestampSet());
 }
 
 TEST_F(MonthlyUseCaseImplTest, CheckGenerateUTCWindowIdentifierHasValidFormat) {
diff --git a/ash/components/hid_detection/BUILD.gn b/ash/components/hid_detection/BUILD.gn
index 81b3883..ff38a765 100644
--- a/ash/components/hid_detection/BUILD.gn
+++ b/ash/components/hid_detection/BUILD.gn
@@ -12,6 +12,8 @@
     "bluetooth_hid_detector.h",
     "bluetooth_hid_detector_impl.cc",
     "bluetooth_hid_detector_impl.h",
+    "hid_detection_utils.cc",
+    "hid_detection_utils.h",
   ]
 
   deps = [
@@ -22,6 +24,7 @@
     "//components/device_event_log",
     "//device/bluetooth",
     "//mojo/public/cpp/bindings",
+    "//services/device/public/mojom",
   ]
 }
 
diff --git a/ash/components/hid_detection/DEPS b/ash/components/hid_detection/DEPS
index c9cd343..72649ad 100644
--- a/ash/components/hid_detection/DEPS
+++ b/ash/components/hid_detection/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
   "+chromeos/services/bluetooth_config",
   "+components/device_event_log",
+  "+services/device/public",
 ]
\ No newline at end of file
diff --git a/ash/components/hid_detection/hid_detection_utils.cc b/ash/components/hid_detection/hid_detection_utils.cc
new file mode 100644
index 0000000..0064511
--- /dev/null
+++ b/ash/components/hid_detection/hid_detection_utils.cc
@@ -0,0 +1,67 @@
+// Copyright 2022 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/components/hid_detection/hid_detection_utils.h"
+
+#include "base/metrics/histogram_functions.h"
+#include "components/device_event_log/device_event_log.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace ash::hid_detection {
+namespace {
+
+using InputDeviceType = device::mojom::InputDeviceType;
+
+absl::optional<HidType> GetHidType(
+    const device::mojom::InputDeviceInfo& device) {
+  if (device.is_touchscreen || device.is_tablet)
+    return HidType::kTouchscreen;
+
+  if (device.is_mouse || device.is_touchpad) {
+    switch (device.type) {
+      case InputDeviceType::TYPE_BLUETOOTH:
+        return HidType::kBluetoothPointer;
+      case InputDeviceType::TYPE_USB:
+        return HidType::kUsbPointer;
+      case InputDeviceType::TYPE_SERIO:
+        return HidType::kSerialPointer;
+      case InputDeviceType::TYPE_UNKNOWN:
+        return HidType::kUnknownPointer;
+    }
+  }
+
+  if (device.is_keyboard) {
+    switch (device.type) {
+      case InputDeviceType::TYPE_BLUETOOTH:
+        return HidType::kBluetoothKeyboard;
+      case InputDeviceType::TYPE_USB:
+        return HidType::kUsbKeyboard;
+      case InputDeviceType::TYPE_SERIO:
+        return HidType::kSerialKeyboard;
+      case InputDeviceType::TYPE_UNKNOWN:
+        return HidType::kUnknownKeyboard;
+    }
+  }
+
+  return absl::nullopt;
+}
+
+}  // namespace
+
+void RecordHidConnected(const device::mojom::InputDeviceInfo& device) {
+  absl::optional<HidType> hid_type = GetHidType(device);
+
+  // If |device| is not relevant (i.e. an accelerometer, joystick, etc), don't
+  // emit metric.
+  if (!hid_type.has_value()) {
+    HID_LOG(DEBUG) << "HidConnected not logged for device " << device.id
+                   << " because it doesn't have a relevant device type.";
+    return;
+  }
+
+  base::UmaHistogramEnumeration("OOBE.HidDetectionScreen.HidConnected",
+                                hid_type.value());
+}
+
+}  // namespace ash::hid_detection
diff --git a/ash/components/hid_detection/hid_detection_utils.h b/ash/components/hid_detection/hid_detection_utils.h
new file mode 100644
index 0000000..0726fce
--- /dev/null
+++ b/ash/components/hid_detection/hid_detection_utils.h
@@ -0,0 +1,33 @@
+// Copyright 2022 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_COMPONENTS_HID_DETECTION_HID_DETECTION_UTILS_H_
+#define ASH_COMPONENTS_HID_DETECTION_HID_DETECTION_UTILS_H_
+
+#include "services/device/public/mojom/input_service.mojom.h"
+
+namespace ash::hid_detection {
+
+// This enum is tied directly to the HidType UMA enum defined in
+// //tools/metrics/histograms/enums.xml, and should always reflect it (do not
+// change one without changing the other).
+enum class HidType {
+  kTouchscreen = 0,
+  kUsbKeyboard = 1,
+  kUsbPointer = 2,
+  kSerialKeyboard = 3,
+  kSerialPointer = 4,
+  kBluetoothKeyboard = 5,
+  kBluetoothPointer = 6,
+  kUnknownKeyboard = 7,
+  kUnknownPointer = 8,
+  kMaxValue = kUnknownPointer
+};
+
+// Record each HID that is connected while the HID detection screen is shown.
+void RecordHidConnected(const device::mojom::InputDeviceInfo& device);
+
+}  // namespace ash::hid_detection
+
+#endif  // ASH_COMPONENTS_HID_DETECTION_HID_DETECTION_UTILS_H_
diff --git a/ash/constants/ash_features.cc b/ash/constants/ash_features.cc
index 229da58..668fa7a 100644
--- a/ash/constants/ash_features.cc
+++ b/ash/constants/ash_features.cc
@@ -252,6 +252,10 @@
 const base::Feature kCalendarView{"CalendarView",
                                   base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enable or disable debug mode for CalendarModel.
+const base::Feature kCalendarModelDebugMode{"CalendarModelDebugMode",
+                                            base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Controls whether the camera privacy switch toasts and notification should be
 // displayed.
 const base::Feature kCameraPrivacySwitchNotifications{
@@ -539,7 +543,7 @@
 // This will only take effect when running a compatible kernel, see
 // crbug/1275421.
 const base::Feature kEnableIkev2Vpn{"EnableIkev2Vpn",
-                                    base::FEATURE_DISABLED_BY_DEFAULT};
+                                    base::FEATURE_ENABLED_BY_DEFAULT};
 
 // If enabled, the input device cards will be shown in the diagnostics app.
 const base::Feature kEnableInputInDiagnosticsApp{
@@ -989,7 +993,7 @@
 
 // Whether PDF files are opened by default in the ChromeOS media app.
 const base::Feature kMediaAppHandlesPdf{"MediaAppHandlesPdf",
-                                        base::FEATURE_ENABLED_BY_DEFAULT};
+                                        base::FEATURE_DISABLED_BY_DEFAULT};
 
 // Feature to continuously log PSI memory pressure data to UMA.
 const base::Feature kMemoryPressureMetricsDetail{
@@ -1199,6 +1203,12 @@
     "ProjectorShowShortPseudoTranscript",
     base::FEATURE_ENABLED_BY_DEFAULT);
 
+// Controls whether to update the indexable text when metadata file gets
+// uploaded.
+const base::Feature kProjectorUpdateIndexableText(
+    "ProjectorUpdateIndexableText",
+    base::FEATURE_ENABLED_BY_DEFAULT);
+
 // Controls whether the quick dim prototype is enabled.
 const base::Feature kQuickDim{"QuickDim", base::FEATURE_ENABLED_BY_DEFAULT};
 
@@ -1287,7 +1297,7 @@
 // Enables launcher nudge that animates the home button to guide users to open
 // the launcher.
 const base::Feature kShelfLauncherNudge{"ShelfLauncherNudge",
-                                        base::FEATURE_DISABLED_BY_DEFAULT};
+                                        base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enables the shelf party.
 const base::Feature kShelfParty{"ShelfParty",
@@ -1295,7 +1305,7 @@
 
 // Enables or disables the new shimless rma flow.
 const base::Feature kShimlessRMAFlow{"ShimlessRMAFlow",
-                                     base::FEATURE_ENABLED_BY_DEFAULT};
+                                     base::FEATURE_DISABLED_BY_DEFAULT};
 
 // Enables or disables launching Shimless RMA as a standalone app.
 const base::Feature kShimlessRMAEnableStandalone{
@@ -1639,6 +1649,10 @@
   return base::FeatureList::IsEnabled(kCalendarView);
 }
 
+bool IsCalendarModelDebugModeEnabled() {
+  return base::FeatureList::IsEnabled(kCalendarModelDebugMode);
+}
+
 bool IsCaptureModeSelfieCameraEnabled() {
   return base::FeatureList::IsEnabled(kCaptureModeSelfieCamera);
 }
@@ -2098,6 +2112,10 @@
   return base::FeatureList::IsEnabled(kProjectorShowShortPseudoTranscript);
 }
 
+bool IsProjectorUpdateIndexableTextEnabled() {
+  return base::FeatureList::IsEnabled(kProjectorUpdateIndexableText);
+}
+
 bool IsQuickDimEnabled() {
   return base::FeatureList::IsEnabled(kQuickDim) && ash::switches::HasHps();
 }
@@ -2149,7 +2167,8 @@
 }
 
 bool IsShelfLauncherNudgeEnabled() {
-  return base::FeatureList::IsEnabled(kShelfLauncherNudge);
+  return IsProductivityLauncherEnabled() &&
+         base::FeatureList::IsEnabled(kShelfLauncherNudge);
 }
 
 bool IsShimlessRMAFlowEnabled() {
diff --git a/ash/constants/ash_features.h b/ash/constants/ash_features.h
index c049c7ba7..8ce0dcb 100644
--- a/ash/constants/ash_features.h
+++ b/ash/constants/ash_features.h
@@ -109,6 +109,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kCalendarView;
 COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const base::Feature kCalendarModelDebugMode;
+COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kCameraPrivacySwitchNotifications;
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kCaptureModeSelfieCamera;
@@ -468,6 +470,8 @@
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kProjectorShowShortPseudoTranscript;
 COMPONENT_EXPORT(ASH_CONSTANTS)
+extern const base::Feature kProjectorUpdateIndexableText;
+COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kQuickDim;
 COMPONENT_EXPORT(ASH_CONSTANTS)
 extern const base::Feature kQuickSettingsNetworkRevamp;
@@ -610,6 +614,7 @@
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsBentoBarEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsBluetoothRevampEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsCalendarViewEnabled();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsCalendarModelDebugModeEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsCaptureModeSelfieCameraEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsCheckPasswordsAgainstCryptohomeHelperEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsLauncherItemColorSyncEnabled();
@@ -734,6 +739,7 @@
 bool IsProjectorManagedUserIgnorePolicyEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool IsProjectorShowShortPseudoTranscript();
+COMPONENT_EXPORT(ASH_CONSTANTS) bool IsProjectorUpdateIndexableTextEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS) bool IsQuickDimEnabled();
 COMPONENT_EXPORT(ASH_CONSTANTS)
 bool IsQuickSettingsNetworkRevampEnabled();
diff --git a/ash/projector/projector_controller_impl.cc b/ash/projector/projector_controller_impl.cc
index 8ec0f4db..55df2a4 100644
--- a/ash/projector/projector_controller_impl.cc
+++ b/ash/projector/projector_controller_impl.cc
@@ -33,7 +33,7 @@
 
 // String format of the screencast name.
 constexpr char kScreencastPathFmtStr[] =
-    "Recording %d-%02d-%02d %02d.%02d.%02d";
+    "Screencast %d-%02d-%02d %02d.%02d.%02d";
 
 constexpr char kScreencastDefaultThumbnailFileName[] = "thumbnail.png";
 
diff --git a/ash/projector/projector_controller_unittest.cc b/ash/projector/projector_controller_unittest.cc
index bffe9980..ce19fab65 100644
--- a/ash/projector/projector_controller_unittest.cc
+++ b/ash/projector/projector_controller_unittest.cc
@@ -360,7 +360,7 @@
           // Verify that |SaveMetadata| in |ProjectorMetadataController| is
           // called with the expected path.
           const std::string expected_screencast_name =
-              "Recording 2021-01-02 20.02.10";
+              "Screencast 2021-01-02 20.02.10";
           const base::FilePath expected_path =
               screencast_container_path.Append("root")
                   .Append("projector_data")
diff --git a/ash/public/cpp/BUILD.gn b/ash/public/cpp/BUILD.gn
index 546b882..8de4beb 100644
--- a/ash/public/cpp/BUILD.gn
+++ b/ash/public/cpp/BUILD.gn
@@ -402,7 +402,6 @@
     "//ash/services/cellular_setup/public/mojom",
     "//ash/webui/personalization_app/proto",
     "//base",
-    "//chromeos/crosapi/cpp",
     "//chromeos/crosapi/mojom",
     "//chromeos/services/assistant/public/mojom",
     "//chromeos/services/bluetooth_config/public/mojom",
diff --git a/ash/public/cpp/accelerators.h b/ash/public/cpp/accelerators.h
index d78a5a9..892d4e84 100644
--- a/ash/public/cpp/accelerators.h
+++ b/ash/public/cpp/accelerators.h
@@ -144,6 +144,7 @@
 
   // Debug accelerators are intentionally at the end, so that if you remove one
   // you don't need to update tests which check hashes of the ids.
+  DEBUG_DUMP_CALENDAR_MODEL,
   DEBUG_KEYBOARD_BACKLIGHT_TOGGLE,
   DEBUG_MICROPHONE_MUTE_TOGGLE,
   DEBUG_PRINT_LAYER_HIERARCHY,
diff --git a/ash/public/cpp/desk_template.cc b/ash/public/cpp/desk_template.cc
index e5418d8..6d0db86 100644
--- a/ash/public/cpp/desk_template.cc
+++ b/ash/public/cpp/desk_template.cc
@@ -7,7 +7,6 @@
 #include "ash/constants/app_types.h"
 #include "ash/constants/ash_features.h"
 #include "base/strings/utf_string_conversions.h"
-#include "chromeos/crosapi/cpp/lacros_startup_state.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
 
@@ -28,9 +27,7 @@
 
 // static
 bool DeskTemplate::IsAppTypeSupported(aura::Window* window) {
-  // For now we'll ignore crostini windows in desk template. We'll also ignore
-  // lacros windows if it is not the primary browser. We'll also ignore ARC apps
-  // unless the flag is turned on.
+  // For now we'll ignore crostini windows in desk templates.
   const AppType app_type =
       static_cast<AppType>(window->GetProperty(aura::client::kAppType));
   switch (app_type) {
@@ -38,7 +35,6 @@
     case AppType::CROSTINI_APP:
       return false;
     case AppType::LACROS:
-      return crosapi::lacros_startup_state::IsLacrosPrimaryEnabled();
     case AppType::ARC_APP:
     case AppType::BROWSER:
     case AppType::CHROME_APP:
diff --git a/ash/public/cpp/pagination/pagination_controller.cc b/ash/public/cpp/pagination/pagination_controller.cc
index 7e302a2..09a90dc 100644
--- a/ash/public/cpp/pagination/pagination_controller.cc
+++ b/ash/public/cpp/pagination/pagination_controller.cc
@@ -84,11 +84,18 @@
       float velocity = scroll_axis_ == SCROLL_AXIS_HORIZONTAL
                            ? details.velocity_x()
                            : details.velocity_y();
-      pagination_model_->EndScroll(true);
 
       if (fabs(velocity) > kMinHorizVelocityToSwitchPage) {
+        pagination_model_->EndScroll(true);
+
         const int delta = velocity < 0 ? 1 : -1;
         SelectPageAndRecordMetric(delta, event.type());
+      } else {
+        // If the gesture ends in a fling below page switch velocity threshold,
+        // decide whether to switch page depending on the scroll progress (if
+        // gesture ends with a slow fling after the user has dragged the page
+        // beyond page switch drag threshold, switch the page).
+        EndDrag(event);
       }
       return true;
     }
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn
index ac480c27..843335a 100644
--- a/ash/resources/vector_icons/BUILD.gn
+++ b/ash/resources/vector_icons/BUILD.gn
@@ -224,6 +224,7 @@
     "persistent_desks_bar_visible.icon",
     "phone_hub_battery_saver.icon",
     "phone_hub_battery_saver_outline.icon",
+    "phone_hub_battery_saver_outline_mask.icon",
     "phone_hub_camera_roll_item_video.icon",
     "phone_hub_camera_roll_menu_download.icon",
     "phone_hub_default_favicon.icon",
@@ -419,12 +420,16 @@
     "unified_menu_accessibility.icon",
     "unified_menu_battery_alert.icon",
     "unified_menu_battery_alert_outline.icon",
+    "unified_menu_battery_alert_outline_mask.icon",
     "unified_menu_battery_bolt.icon",
     "unified_menu_battery_bolt_outline.icon",
+    "unified_menu_battery_bolt_outline_mask.icon",
     "unified_menu_battery_unreliable.icon",
     "unified_menu_battery_unreliable_outline.icon",
+    "unified_menu_battery_unreliable_outline_mask.icon",
     "unified_menu_battery_x.icon",
     "unified_menu_battery_x_outline.icon",
+    "unified_menu_battery_x_outline_mask.icon",
     "unified_menu_bluetooth.icon",
     "unified_menu_bluetooth_connected.icon",
     "unified_menu_bluetooth_connected_legacy.icon",
diff --git a/ash/resources/vector_icons/phone_hub_battery_saver_outline_mask.icon b/ash/resources/vector_icons/phone_hub_battery_saver_outline_mask.icon
new file mode 100644
index 0000000..33dfd3ceb
--- /dev/null
+++ b/ash/resources/vector_icons/phone_hub_battery_saver_outline_mask.icon
@@ -0,0 +1,47 @@
+// Copyright 2022 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.
+
+CANVAS_DIMENSIONS, 20,
+PATH_MODE_CLEAR,
+MOVE_TO, 10.59f, 9.85f,
+R_LINE_TO, 2.36f, 0,
+R_V_LINE_TO, 1.18f,
+R_H_LINE_TO, -2.36f,
+R_V_LINE_TO, 2.36f,
+H_LINE_TO, 9.41f,
+R_LINE_TO, 0, -2.36f,
+H_LINE_TO, 7.05f,
+R_LINE_TO, 0, -1.18f,
+R_LINE_TO, 2.36f, 0,
+R_LINE_TO, 0, -2.36f,
+R_H_LINE_TO, 1.18f,
+V_LINE_TO, 9.85f,
+CLOSE,
+R_MOVE_TO, -1.78f, -0.6f,
+R_LINE_TO, 0, -1.76f,
+R_CUBIC_TO, 0, -0.16f, 0.06f, -0.31f, 0.18f, -0.42f,
+R_CUBIC_TO, 0.11f, -0.11f, 0.27f, -0.18f, 0.42f, -0.18f,
+R_H_LINE_TO, 1.18f,
+R_CUBIC_TO, 0.16f, 0, 0.31f, 0.06f, 0.42f, 0.18f,
+R_CUBIC_TO, 0.11f, 0.11f, 0.18f, 0.27f, 0.18f, 0.42f,
+V_LINE_TO, 9.25f,
+R_H_LINE_TO, 1.76f,
+R_CUBIC_TO, 0.33f, 0, 0.6f, 0.27f, 0.6f, 0.6f,
+R_V_LINE_TO, 1.18f,
+R_CUBIC_TO, 0, 0.16f, -0.06f, 0.31f, -0.18f, 0.42f,
+R_CUBIC_TO, -0.11f, 0.11f, -0.27f, 0.18f, -0.42f, 0.18f,
+R_H_LINE_TO, -1.76f,
+R_V_LINE_TO, 1.76f,
+R_CUBIC_TO, 0, 0.16f, -0.06f, 0.31f, -0.18f, 0.42f,
+R_CUBIC_TO, -0.11f, 0.11f, -0.27f, 0.18f, -0.42f, 0.18f,
+R_LINE_TO, -1.18f, 0,
+R_CUBIC_TO, -0.33f, 0, -0.6f, -0.27f, -0.6f, -0.6f,
+R_V_LINE_TO, -1.76f,
+H_LINE_TO, 7.05f,
+R_CUBIC_TO, -0.33f, 0, -0.6f, -0.27f, -0.6f, -0.6f,
+R_LINE_TO, 0, -1.18f,
+R_CUBIC_TO, 0, -0.16f, 0.06f, -0.31f, 0.18f, -0.42f,
+R_CUBIC_TO, 0.11f, -0.11f, 0.27f, -0.18f, 0.42f, -0.18f,
+R_H_LINE_TO, 1.76f,
+CLOSE
diff --git a/ash/resources/vector_icons/unified_menu_battery_alert_outline_mask.icon b/ash/resources/vector_icons/unified_menu_battery_alert_outline_mask.icon
new file mode 100644
index 0000000..8e1f6c7
--- /dev/null
+++ b/ash/resources/vector_icons/unified_menu_battery_alert_outline_mask.icon
@@ -0,0 +1,34 @@
+// Copyright 2022 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.
+
+CANVAS_DIMENSIONS, 20,
+PATH_MODE_CLEAR,
+MOVE_TO, 11.55f, 6.5f,
+CUBIC_TO, 11.55f, 6.06f, 11.19f, 5.7f, 10.75f, 5.7f,
+H_LINE_TO, 9.25f,
+CUBIC_TO, 8.81f, 5.7f, 8.45f, 6.06f, 8.45f, 6.5f,
+V_LINE_TO, 12,
+CUBIC_TO, 8.45f, 12.19f, 8.52f, 12.36f, 8.63f, 12.5f,
+CUBIC_TO, 8.52f, 12.64f, 8.45f, 12.81f, 8.45f, 13,
+V_LINE_TO, 14.5f,
+CUBIC_TO, 8.45f, 14.94f, 8.81f, 15.3f, 9.25f, 15.3f,
+H_LINE_TO, 10.75f,
+CUBIC_TO, 11.19f, 15.3f, 11.55f, 14.94f, 11.55f, 14.5f,
+V_LINE_TO, 13,
+CUBIC_TO, 11.55f, 12.81f, 11.48f, 12.64f, 11.37f, 12.5f,
+CUBIC_TO, 11.48f, 12.36f, 11.55f, 12.19f, 11.55f, 12,
+V_LINE_TO, 6.5f,
+CLOSE,
+MOVE_TO, 10.75f, 6.5f,
+V_LINE_TO, 12,
+H_LINE_TO, 9.25f,
+V_LINE_TO, 6.5f,
+H_LINE_TO, 10.75f,
+CLOSE,
+MOVE_TO, 10.75f, 13,
+V_LINE_TO, 14.5f,
+H_LINE_TO, 9.25f,
+V_LINE_TO, 13,
+H_LINE_TO, 10.75f,
+CLOSE
diff --git a/ash/resources/vector_icons/unified_menu_battery_bolt_outline_mask.icon b/ash/resources/vector_icons/unified_menu_battery_bolt_outline_mask.icon
new file mode 100644
index 0000000..430301d
--- /dev/null
+++ b/ash/resources/vector_icons/unified_menu_battery_bolt_outline_mask.icon
@@ -0,0 +1,30 @@
+// Copyright 2022 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.
+
+CANVAS_DIMENSIONS, 20,
+PATH_MODE_CLEAR,
+MOVE_TO, 7.25f, 11.44f,
+CUBIC_TO, 7.1f, 11.21f, 7.09f, 10.91f, 7.21f, 10.67f,
+LINE_TO, 9.98f, 5.14f,
+CUBIC_TO, 10.14f, 4.81f, 10.51f, 4.64f, 10.87f, 4.72f,
+CUBIC_TO, 11.24f, 4.81f, 11.49f, 5.13f, 11.49f, 5.5f,
+V_LINE_TO, 8.84f,
+H_LINE_TO, 12.07f,
+CUBIC_TO, 12.35f, 8.84f, 12.61f, 8.99f, 12.75f, 9.22f,
+CUBIC_TO, 12.9f, 9.46f, 12.91f, 9.75f, 12.79f, 10,
+LINE_TO, 10.03f, 15.52f,
+CUBIC_TO, 9.86f, 15.86f, 9.49f, 16.03f, 9.13f, 15.95f,
+CUBIC_TO, 8.76f, 15.86f, 8.51f, 15.54f, 8.51f, 15.17f,
+V_LINE_TO, 11.82f,
+H_LINE_TO, 7.93f,
+CUBIC_TO, 7.65f, 11.82f, 7.39f, 11.68f, 7.25f, 11.44f,
+CLOSE,
+MOVE_TO, 9.31f, 11.02f,
+V_LINE_TO, 15.17f,
+LINE_TO, 12.07f, 9.64f,
+H_LINE_TO, 10.69f,
+V_LINE_TO, 5.5f,
+LINE_TO, 7.93f, 11.02f,
+H_LINE_TO, 9.31f,
+CLOSE
diff --git a/ash/resources/vector_icons/unified_menu_battery_unreliable_outline_mask.icon b/ash/resources/vector_icons/unified_menu_battery_unreliable_outline_mask.icon
new file mode 100644
index 0000000..dfddfda
--- /dev/null
+++ b/ash/resources/vector_icons/unified_menu_battery_unreliable_outline_mask.icon
@@ -0,0 +1,68 @@
+// Copyright 2022 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.
+
+CANVAS_DIMENSIONS, 20,
+PATH_MODE_CLEAR,
+MOVE_TO, 12.5f, 8.97f,
+CUBIC_TO, 12.94f, 8.97f, 13.3f, 9.32f, 13.3f, 9.77f,
+V_LINE_TO, 11.27f,
+H_LINE_TO, 12.5f,
+V_LINE_TO, 9.77f,
+CUBIC_TO, 12.5f, 9.77f, 12.5f, 10.38f, 11.7f, 10.48f,
+CUBIC_TO, 11.62f, 10.49f, 11.54f, 10.5f, 11.44f, 10.5f,
+CUBIC_TO, 10.92f, 10.5f, 10.49f, 10.13f, 10.05f, 9.76f,
+CUBIC_TO, 9.6f, 9.38f, 9.15f, 9, 8.6f, 9,
+CUBIC_TO, 7.5f, 9, 7.5f, 9.77f, 7.5f, 9.77f,
+CUBIC_TO, 6.7f, 9.77f, 6.7f, 9.77f, 6.7f, 9.77f,
+LINE_TO, 6.7f, 9.76f,
+LINE_TO, 6.7f, 9.76f,
+LINE_TO, 6.7f, 9.76f,
+LINE_TO, 6.7f, 9.75f,
+CUBIC_TO, 6.7f, 9.74f, 6.7f, 9.73f, 6.7f, 9.72f,
+CUBIC_TO, 6.7f, 9.7f, 6.7f, 9.68f, 6.71f, 9.65f,
+CUBIC_TO, 6.71f, 9.6f, 6.72f, 9.54f, 6.74f, 9.46f,
+CUBIC_TO, 6.77f, 9.31f, 6.84f, 9.12f, 6.99f, 8.92f,
+CUBIC_TO, 7.3f, 8.48f, 7.84f, 8.2f, 8.6f, 8.2f,
+CUBIC_TO, 9.45f, 8.2f, 10.1f, 8.76f, 10.5f, 9.09f,
+CUBIC_TO, 10.52f, 9.11f, 10.53f, 9.12f, 10.55f, 9.14f,
+CUBIC_TO, 10.79f, 9.34f, 10.96f, 9.48f, 11.12f, 9.58f,
+CUBIC_TO, 11.28f, 9.68f, 11.38f, 9.7f, 11.44f, 9.7f,
+CUBIC_TO, 11.59f, 9.7f, 11.67f, 9.68f, 11.71f, 9.66f,
+CUBIC_TO, 11.76f, 9.27f, 12.09f, 8.97f, 12.5f, 8.97f,
+CLOSE,
+MOVE_TO, 11.73f, 9.65f,
+CUBIC_TO, 11.74f, 9.65f, 11.74f, 9.64f, 11.74f, 9.64f,
+CUBIC_TO, 11.74f, 9.65f, 11.74f, 9.65f, 11.73f, 9.65f,
+CLOSE,
+MOVE_TO, 7.5f, 9.77f,
+H_LINE_TO, 6.7f,
+V_LINE_TO, 11.27f,
+CUBIC_TO, 6.7f, 11.71f, 7.06f, 12.07f, 7.5f, 12.07f,
+CUBIC_TO, 7.91f, 12.07f, 8.26f, 11.75f, 8.3f, 11.35f,
+CUBIC_TO, 8.33f, 11.33f, 8.42f, 11.3f, 8.6f, 11.3f,
+CUBIC_TO, 8.8f, 11.3f, 9.02f, 11.44f, 9.52f, 11.86f,
+CUBIC_TO, 9.53f, 11.87f, 9.54f, 11.88f, 9.55f, 11.89f,
+CUBIC_TO, 9.75f, 12.06f, 10.01f, 12.28f, 10.29f, 12.45f,
+CUBIC_TO, 10.6f, 12.63f, 10.98f, 12.8f, 11.44f, 12.8f,
+CUBIC_TO, 12.18f, 12.8f, 12.71f, 12.53f, 13.02f, 12.1f,
+CUBIC_TO, 13.16f, 11.91f, 13.23f, 11.71f, 13.26f, 11.57f,
+CUBIC_TO, 13.28f, 11.49f, 13.29f, 11.43f, 13.29f, 11.38f,
+CUBIC_TO, 13.3f, 11.35f, 13.3f, 11.33f, 13.3f, 11.31f,
+CUBIC_TO, 13.3f, 11.3f, 13.3f, 11.3f, 13.3f, 11.29f,
+LINE_TO, 13.3f, 11.28f,
+LINE_TO, 13.3f, 11.27f,
+LINE_TO, 13.3f, 11.27f,
+LINE_TO, 13.3f, 11.27f,
+CUBIC_TO, 13.3f, 11.27f, 13.3f, 11.27f, 12.5f, 11.27f,
+CUBIC_TO, 12.5f, 11.27f, 12.5f, 12, 11.44f, 12,
+CUBIC_TO, 10.92f, 12, 10.49f, 11.63f, 10.05f, 11.26f,
+CUBIC_TO, 9.6f, 10.88f, 9.15f, 10.5f, 8.6f, 10.5f,
+CUBIC_TO, 8.49f, 10.5f, 8.39f, 10.51f, 8.3f, 10.52f,
+CUBIC_TO, 7.5f, 10.64f, 7.5f, 11.27f, 7.5f, 11.27f,
+V_LINE_TO, 9.77f,
+CLOSE,
+MOVE_TO, 8.28f, 11.35f,
+CUBIC_TO, 8.28f, 11.36f, 8.27f, 11.36f, 8.27f, 11.36f,
+CUBIC_TO, 8.27f, 11.36f, 8.27f, 11.36f, 8.28f, 11.35f,
+CLOSE
diff --git a/ash/resources/vector_icons/unified_menu_battery_x_outline_mask.icon b/ash/resources/vector_icons/unified_menu_battery_x_outline_mask.icon
new file mode 100644
index 0000000..3a4fa8a
--- /dev/null
+++ b/ash/resources/vector_icons/unified_menu_battery_x_outline_mask.icon
@@ -0,0 +1,47 @@
+// Copyright 2022 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.
+
+CANVAS_DIMENSIONS, 20,
+PATH_MODE_CLEAR,
+MOVE_TO, 10, 9.77f,
+LINE_TO, 11.67f, 8.1f,
+LINE_TO, 12.5f, 8.93f,
+LINE_TO, 10.83f, 10.6f,
+LINE_TO, 12.5f, 12.27f,
+LINE_TO, 11.67f, 13.1f,
+LINE_TO, 10, 11.43f,
+LINE_TO, 8.33f, 13.1f,
+LINE_TO, 7.5f, 12.27f,
+LINE_TO, 9.17f, 10.6f,
+LINE_TO, 7.5f, 8.93f,
+LINE_TO, 8.33f, 8.1f,
+LINE_TO, 10, 9.77f,
+CLOSE,
+MOVE_TO, 8.32f, 10.6f,
+LINE_TO, 7.08f, 9.36f,
+CUBIC_TO, 6.96f, 9.25f, 6.9f, 9.09f, 6.9f, 8.93f,
+CUBIC_TO, 6.9f, 8.77f, 6.96f, 8.62f, 7.08f, 8.51f,
+LINE_TO, 7.91f, 7.68f,
+CUBIC_TO, 8.02f, 7.56f, 8.17f, 7.5f, 8.33f, 7.5f,
+CUBIC_TO, 8.49f, 7.5f, 8.64f, 7.56f, 8.76f, 7.68f,
+LINE_TO, 10, 8.92f,
+LINE_TO, 11.24f, 7.68f,
+CUBIC_TO, 11.48f, 7.44f, 11.86f, 7.44f, 12.09f, 7.68f,
+LINE_TO, 12.92f, 8.51f,
+CUBIC_TO, 13.04f, 8.62f, 13.1f, 8.77f, 13.1f, 8.93f,
+CUBIC_TO, 13.1f, 9.09f, 13.04f, 9.25f, 12.92f, 9.36f,
+LINE_TO, 11.68f, 10.6f,
+LINE_TO, 12.92f, 11.84f,
+CUBIC_TO, 13.04f, 11.95f, 13.1f, 12.11f, 13.1f, 12.27f,
+CUBIC_TO, 13.1f, 12.43f, 13.04f, 12.58f, 12.92f, 12.69f,
+LINE_TO, 12.09f, 13.52f,
+CUBIC_TO, 11.86f, 13.76f, 11.48f, 13.76f, 11.24f, 13.52f,
+LINE_TO, 10, 12.28f,
+LINE_TO, 8.76f, 13.52f,
+CUBIC_TO, 8.52f, 13.76f, 8.14f, 13.76f, 7.91f, 13.52f,
+LINE_TO, 7.08f, 12.69f,
+CUBIC_TO, 6.96f, 12.58f, 6.9f, 12.43f, 6.9f, 12.27f,
+CUBIC_TO, 6.9f, 12.11f, 6.96f, 11.95f, 7.08f, 11.84f,
+LINE_TO, 8.32f, 10.6f,
+CLOSE
diff --git a/ash/rgb_keyboard/rgb_keyboard_manager.cc b/ash/rgb_keyboard/rgb_keyboard_manager.cc
index aa2eae7..a51076f 100644
--- a/ash/rgb_keyboard/rgb_keyboard_manager.cc
+++ b/ash/rgb_keyboard/rgb_keyboard_manager.cc
@@ -30,6 +30,7 @@
 
   ime_controller_ptr_->AddObserver(this);
 
+  VLOG(1) << "Initializing RGB Keyboard support";
   FetchRgbKeyboardSupport();
 }
 
@@ -56,28 +57,42 @@
                                                   uint8_t g,
                                                   uint8_t b) {
   DCHECK(RgbkbdClient::Get());
-  // TODO(michaelcheco): Check RGB capabilities before proceeding.
+  if (!IsRgbKeyboardSupported()) {
+    LOG(ERROR) << "Attempted to set RGB keyboard color, but flag is disabled.";
+    return;
+  }
+
+  VLOG(1) << "Setting RGB keyboard color to R:" << static_cast<int>(r)
+          << " G:" << static_cast<int>(g) << " B:" << static_cast<int>(b);
   RgbkbdClient::Get()->SetStaticBackgroundColor(r, g, b);
 }
 
 void RgbKeyboardManager::SetRainbowMode() {
   DCHECK(RgbkbdClient::Get());
-  // TODO(michaelcheco): Check RGB capabilities before proceeding.
+  if (!IsRgbKeyboardSupported()) {
+    LOG(ERROR) << "Attempted to set RGB rainbow mode, but flag is disabled.";
+    return;
+  }
+
+  VLOG(1) << "Setting RGB keyboard to rainbow mode";
   RgbkbdClient::Get()->SetRainbowMode();
 }
 
 void RgbKeyboardManager::SetAnimationMode(rgbkbd::RgbAnimationMode mode) {
   if (!features::IsExperimentalRgbKeyboardPatternsEnabled()) {
-    LOG(ERROR) << "Attempted to set animation mode, but flag is disabled.";
+    LOG(ERROR) << "Attempted to set RGB animation mode, but flag is disabled.";
     return;
   }
 
   DCHECK(RgbkbdClient::Get());
+  VLOG(1) << "Setting RGB keyboard animation mode to "
+          << static_cast<uint32_t>(mode);
   RgbkbdClient::Get()->SetAnimationMode(mode);
 }
 
 void RgbKeyboardManager::OnCapsLockChanged(bool enabled) {
   if (IsRgbKeyboardSupported()) {
+    VLOG(1) << "Setting RGB keyboard caps lock state to " << enabled;
     RgbkbdClient::Get()->SetCapsLockState(enabled);
   }
 }
@@ -90,13 +105,18 @@
 void RgbKeyboardManager::OnGetRgbKeyboardCapabilities(
     absl::optional<rgbkbd::RgbKeyboardCapabilities> reply) {
   if (!reply.has_value()) {
-    LOG(ERROR) << "rgbkbd: No response received for GetRgbKeyboardCapabilities";
+    LOG(ERROR) << "No response received for GetRgbKeyboardCapabilities";
     return;
   }
+
   capabilities_ = reply.value();
+  VLOG(1) << "RGB Keyboard capabilities="
+          << static_cast<uint32_t>(capabilities_);
 
   // Upon login, CapsLock may already be enabled.
   if (IsRgbKeyboardSupported()) {
+    VLOG(1) << "Setting initial RGB keyboard caps lock state to "
+            << ime_controller_ptr_->IsCapsLockEnabled();
     RgbkbdClient::Get()->SetCapsLockState(
         ime_controller_ptr_->IsCapsLockEnabled());
   }
diff --git a/ash/rgb_keyboard/rgb_keyboard_manager_unittest.cc b/ash/rgb_keyboard/rgb_keyboard_manager_unittest.cc
index fa368bd9..c833a03 100644
--- a/ash/rgb_keyboard/rgb_keyboard_manager_unittest.cc
+++ b/ash/rgb_keyboard/rgb_keyboard_manager_unittest.cc
@@ -20,7 +20,6 @@
 class RgbKeyboardManagerTest : public testing::Test {
  public:
   RgbKeyboardManagerTest() {
-    // scoped_feature_list_.InitAndEnableFeature(features::kRgbKeyboard);
     scoped_feature_list_.InitWithFeatures(
         /*enabled_features=*/{features::kRgbKeyboard,
                               features::kExperimentalRgbKeyboardPatterns},
diff --git a/ash/shelf/launcher_nudge_controller.cc b/ash/shelf/launcher_nudge_controller.cc
index 4ac3a44..d6a1e765 100644
--- a/ash/shelf/launcher_nudge_controller.cc
+++ b/ash/shelf/launcher_nudge_controller.cc
@@ -148,6 +148,12 @@
   if (!prefs)
     return false;
 
+  if (GetFirstLoginTime(prefs).is_null()) {
+    // Don't show the nudge to existing users. See
+    // `OnActiveUserPrefServiceChanged()` for details.
+    return false;
+  }
+
   // Only show the launcher nudge in clamshell mode.
   if (Shell::Get()->IsInTabletMode())
     return false;
diff --git a/ash/system/palette/palette_tray.cc b/ash/system/palette/palette_tray.cc
index e9ed7af2..972f35f 100644
--- a/ash/system/palette/palette_tray.cc
+++ b/ash/system/palette/palette_tray.cc
@@ -298,8 +298,9 @@
 
 bool PaletteTray::ShouldShowOnDisplay() {
   if (stylus_utils::IsPaletteEnabledOnEveryDisplay() ||
-      display_has_stylus_for_testing_)
+      display_has_stylus_for_testing_) {
     return true;
+  }
 
   // |widget| is null when this function is called from PaletteTray constructor
   // before it is added to a widget.
@@ -322,6 +323,7 @@
     ids.insert(ids.end(), mirrors.begin(), mirrors.end());
     ids.push_back(display_manager->mirroring_source_id());
   }
+
   for (const ui::TouchscreenDevice& device :
        ui::DeviceDataManager::GetInstance()->GetTouchscreenDevices()) {
     if (device.has_stylus && std::find(ids.begin(), ids.end(),
diff --git a/ash/system/palette/stylus_battery_delegate.cc b/ash/system/palette/stylus_battery_delegate.cc
index 52f4360..119e770 100644
--- a/ash/system/palette/stylus_battery_delegate.cc
+++ b/ash/system/palette/stylus_battery_delegate.cc
@@ -6,6 +6,7 @@
 
 #include <string>
 
+#include "ash/constants/ash_features.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -51,7 +52,11 @@
 
   if (IsBatteryCharging()) {
     info.icon_badge = &kUnifiedMenuBatteryBoltIcon;
-    info.badge_outline = &kUnifiedMenuBatteryBoltOutlineIcon;
+    if (features::IsDarkLightModeEnabled()) {
+      info.badge_outline = &kUnifiedMenuBatteryBoltOutlineMaskIcon;
+    } else {
+      info.badge_outline = &kUnifiedMenuBatteryBoltOutlineIcon;
+    }
   }
 
   const SkColor icon_fg_color = GetColorForBatteryLevel();
diff --git a/ash/system/phonehub/phone_status_view.cc b/ash/system/phonehub/phone_status_view.cc
index e4e725ad..bbe235d 100644
--- a/ash/system/phonehub/phone_status_view.cc
+++ b/ash/system/phonehub/phone_status_view.cc
@@ -6,6 +6,7 @@
 
 #include <string>
 
+#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/network_icon_image_source.h"
 #include "ash/public/cpp/shelf_config.h"
 #include "ash/resources/vector_icons/vector_icons.h"
@@ -116,6 +117,13 @@
   AddView(TriView::Container::START, phone_name_label_);
 
   AddView(TriView::Container::CENTER, signal_icon_);
+
+  if (features::IsDarkLightModeEnabled()) {
+    // The battery icon requires its own layer to properly render the masked
+    // outline of the badge within the battery icon.
+    battery_icon_->SetPaintToLayer();
+    battery_icon_->layer()->SetFillsBoundsOpaquely(false);
+  }
   AddView(TriView::Container::CENTER, battery_icon_);
 
   battery_label_->SetAutoColorReadabilityEnabled(false);
@@ -254,7 +262,11 @@
 
   if (IsBatterySaverModeOn(phone_status)) {
     info.icon_badge = &kPhoneHubBatterySaverIcon;
-    info.badge_outline = &kPhoneHubBatterySaverOutlineIcon;
+    if (features::IsDarkLightModeEnabled()) {
+      info.badge_outline = &kPhoneHubBatterySaverOutlineMaskIcon;
+    } else {
+      info.badge_outline = &kPhoneHubBatterySaverOutlineIcon;
+    }
     return info;
   }
 
@@ -263,16 +275,28 @@
       info.alert_if_low = true;
       if (info.charge_percent < PowerStatus::kCriticalBatteryChargePercentage) {
         info.icon_badge = &kUnifiedMenuBatteryAlertIcon;
-        info.badge_outline = &kUnifiedMenuBatteryAlertOutlineIcon;
+        if (features::IsDarkLightModeEnabled()) {
+          info.badge_outline = &kUnifiedMenuBatteryAlertOutlineMaskIcon;
+        } else {
+          info.badge_outline = &kUnifiedMenuBatteryAlertOutlineIcon;
+        }
       }
       break;
     case PhoneStatusModel::ChargingState::kChargingAc:
       info.icon_badge = &kUnifiedMenuBatteryBoltIcon;
-      info.badge_outline = &kUnifiedMenuBatteryBoltOutlineIcon;
+      if (features::IsDarkLightModeEnabled()) {
+        info.badge_outline = &kUnifiedMenuBatteryBoltOutlineMaskIcon;
+      } else {
+        info.badge_outline = &kUnifiedMenuBatteryBoltOutlineIcon;
+      }
       break;
     case PhoneStatusModel::ChargingState::kChargingUsb:
       info.icon_badge = &kUnifiedMenuBatteryUnreliableIcon;
-      info.badge_outline = &kUnifiedMenuBatteryUnreliableOutlineIcon;
+      if (features::IsDarkLightModeEnabled()) {
+        info.badge_outline = &kUnifiedMenuBatteryUnreliableOutlineMaskIcon;
+      } else {
+        info.badge_outline = &kUnifiedMenuBatteryUnreliableOutlineIcon;
+      }
       break;
   }
 
diff --git a/ash/system/power/adaptive_charging_controller.cc b/ash/system/power/adaptive_charging_controller.cc
index d367df55..4aba183 100644
--- a/ash/system/power/adaptive_charging_controller.cc
+++ b/ash/system/power/adaptive_charging_controller.cc
@@ -39,8 +39,13 @@
 
   is_adaptive_delaying_charge_ = proto.adaptive_delaying_charge();
 
-  if (!is_adaptive_delaying_charge_)
+  // Nudge and notification should be shown only if heuristic is enabled for
+  // this user and the adaptive charging is actually active.
+  if (!proto.has_adaptive_charging_heuristic_enabled() ||
+      !proto.adaptive_charging_heuristic_enabled() ||
+      !is_adaptive_delaying_charge_) {
     return;
+  }
 
   // The nudge will only be shown alongside the notification once.
   nudge_controller_->ShowNudge();
diff --git a/ash/system/power/battery_image_source.cc b/ash/system/power/battery_image_source.cc
index f907012..f12a3d3b 100644
--- a/ash/system/power/battery_image_source.cc
+++ b/ash/system/power/battery_image_source.cc
@@ -4,6 +4,7 @@
 
 #include "ash/system/power/battery_image_source.h"
 
+#include "ash/constants/ash_features.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/style/ash_color_provider.h"
 #include "base/cxx17_backports.h"
@@ -99,15 +100,30 @@
   canvas->Restore();
 
   if (info_.badge_outline) {
-    const SkColor outline_color =
-        info_.charge_percent > 50 ? fg_color_ : bg_color_;
-    PaintVectorIcon(canvas, *info_.badge_outline, size().height(),
-                    outline_color);
+    if (ash::features::IsDarkLightModeEnabled()) {
+      // The outline is always a vector icon with PATH_MODE_CLEAR. This means it
+      // masks out anything previously drawn to the canvas. Give it any opaque
+      // color so it will properly mask the rest of the battery icon. NOTE: The
+      // view which renders this canvas must paint to its own non-opaque layer,
+      // otherwise this outline will show up SK_ColorBlue instead of
+      // transparent.
+      PaintVectorIcon(canvas, *info_.badge_outline, size().height(),
+                      SK_ColorBLUE);
+    } else {
+      // The outline is a colored outline, so give it a color meant to be seen
+      // by the user.
+      const SkColor outline_color =
+          info_.charge_percent > 50 ? fg_color_ : bg_color_;
+      PaintVectorIcon(canvas, *info_.badge_outline, size().height(),
+                      outline_color);
+    }
   }
 
   // Paint the badge over top of the battery, if applicable.
   if (info_.icon_badge) {
-    const SkColor badge_color = use_alert_color ? alert_color : badge_color_;
+    const SkColor default_color =
+        ash::features::IsDarkLightModeEnabled() ? fg_color_ : badge_color_;
+    const SkColor badge_color = use_alert_color ? alert_color : default_color;
     PaintVectorIcon(canvas, *info_.icon_badge, size().height(), badge_color);
   }
 }
diff --git a/ash/system/power/power_status.cc b/ash/system/power/power_status.cc
index bffdad8..5e11b63 100644
--- a/ash/system/power/power_status.cc
+++ b/ash/system/power/power_status.cc
@@ -7,6 +7,7 @@
 #include <algorithm>
 #include <cmath>
 
+#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/power_utils.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -247,17 +248,29 @@
 
   if (!IsUsbChargerConnected() && !IsBatteryPresent()) {
     info->icon_badge = &kUnifiedMenuBatteryXIcon;
-    info->badge_outline = &kUnifiedMenuBatteryXOutlineIcon;
+    if (features::IsDarkLightModeEnabled()) {
+      info->badge_outline = &kUnifiedMenuBatteryXOutlineMaskIcon;
+    } else {
+      info->badge_outline = &kUnifiedMenuBatteryXOutlineIcon;
+    }
     info->charge_percent = 0;
     return;
   }
 
   if (IsUsbChargerConnected()) {
     info->icon_badge = &kUnifiedMenuBatteryUnreliableIcon;
-    info->badge_outline = &kUnifiedMenuBatteryUnreliableOutlineIcon;
+    if (features::IsDarkLightModeEnabled()) {
+      info->badge_outline = &kUnifiedMenuBatteryUnreliableOutlineMaskIcon;
+    } else {
+      info->badge_outline = &kUnifiedMenuBatteryUnreliableOutlineIcon;
+    }
   } else if (IsLinePowerConnected()) {
     info->icon_badge = &kUnifiedMenuBatteryBoltIcon;
-    info->badge_outline = &kUnifiedMenuBatteryBoltOutlineIcon;
+    if (features::IsDarkLightModeEnabled()) {
+      info->badge_outline = &kUnifiedMenuBatteryBoltOutlineMaskIcon;
+    } else {
+      info->badge_outline = &kUnifiedMenuBatteryBoltOutlineIcon;
+    }
   } else {
     info->icon_badge = nullptr;
     info->badge_outline = nullptr;
@@ -270,7 +283,11 @@
   if (GetBatteryPercent() < kCriticalBatteryChargePercentage &&
       !info->icon_badge) {
     info->icon_badge = &kUnifiedMenuBatteryAlertIcon;
-    info->badge_outline = &kUnifiedMenuBatteryAlertOutlineIcon;
+    if (features::IsDarkLightModeEnabled()) {
+      info->badge_outline = &kUnifiedMenuBatteryAlertOutlineMaskIcon;
+    } else {
+      info->badge_outline = &kUnifiedMenuBatteryAlertOutlineIcon;
+    }
   }
 }
 
diff --git a/ash/system/time/calendar_event_fetch.cc b/ash/system/time/calendar_event_fetch.cc
index 24761d2..74d862c 100644
--- a/ash/system/time/calendar_event_fetch.cc
+++ b/ash/system/time/calendar_event_fetch.cc
@@ -14,6 +14,9 @@
 #include "base/time/tick_clock.h"
 #include "base/time/time.h"
 
+#undef ENABLED_VLOG_LEVEL
+#define ENABLED_VLOG_LEVEL 1
+
 namespace ash {
 
 CalendarEventFetch::CalendarEventFetch(
@@ -31,6 +34,10 @@
 
   const base::Time start_of_next_month =
       calendar_utils::GetStartOfNextMonthUTC(start_of_month);
+
+  if (ash::features::IsCalendarModelDebugModeEnabled())
+    VLOG(1) << "Fetching: " << start_of_month << " => " << start_of_next_month;
+
   client->GetEventList(base::BindOnce(&CalendarEventFetch::OnResultReceived,
                                       weak_factory_.GetWeakPtr()),
                        start_of_month, start_of_next_month);
diff --git a/ash/system/time/calendar_model.cc b/ash/system/time/calendar_model.cc
index 0a149689..5ef4a9a7 100644
--- a/ash/system/time/calendar_model.cc
+++ b/ash/system/time/calendar_model.cc
@@ -10,6 +10,7 @@
 
 #include "ash/calendar/calendar_client.h"
 #include "ash/calendar/calendar_controller.h"
+#include "ash/constants/ash_features.h"
 #include "ash/shell.h"
 #include "ash/system/time/calendar_event_fetch.h"
 #include "ash/system/time/calendar_utils.h"
@@ -23,6 +24,9 @@
 #include "google_apis/calendar/calendar_api_response_types.h"
 #include "google_apis/common/api_error_codes.h"
 
+#undef ENABLED_VLOG_LEVEL
+#define ENABLED_VLOG_LEVEL 1
+
 namespace {
 
 using ::google_apis::calendar::CalendarEvent;
@@ -255,6 +259,37 @@
     return;
   }
 
+  if (ash::features::IsCalendarModelDebugModeEnabled() && events) {
+    VLOG(1) << __FUNCTION__ << " month " << start_of_month << " num events "
+            << events->items().size();
+
+    // It is possible for incoming events to have a start date (adjusted for
+    // timezone differences) that's not in `start_of_month`. The code below
+    // outputs a breakdown of the events by month.
+    if (events->items().size() > 0) {
+      std::map<base::Time, int> included_months;
+      for (auto& event : events->items()) {
+        base::Time adjusted_start = GetStartTimeAdjusted(event.get());
+        base::Time adjusted_start_of_month =
+            calendar_utils::GetStartOfMonthUTC(adjusted_start);
+        if (included_months.find(adjusted_start_of_month) ==
+            included_months.end()) {
+          included_months[adjusted_start_of_month] = 1;
+        } else {
+          included_months[adjusted_start_of_month]++;
+        }
+      }
+
+      if (included_months.size() > 1) {
+        VLOG(1) << __FUNCTION__ << " breakdown:";
+        for (auto& included_month : included_months) {
+          VLOG(1) << __FUNCTION__ << "   " << included_month.first << " ("
+                  << included_month.second << ")";
+        }
+      }
+    }
+  }
+
   // Keep us within storage limits.
   PruneEventCache();
 
@@ -326,6 +361,11 @@
   base::Time start_of_month =
       calendar_utils::GetStartOfMonthUTC(GetStartTimeMidnightAdjusted(event));
 
+  if (ash::features::IsCalendarModelDebugModeEnabled()) {
+    VLOG(1) << __FUNCTION__ << " start_of_month " << start_of_month;
+    DebugDumpEventLarge(__FUNCTION__, event);
+  }
+
   auto it = event_months_.find(start_of_month);
   if (it == event_months_.end()) {
     // No events for this month, so add a map for it and insert.
@@ -364,14 +404,27 @@
   }
 }
 
-base::Time CalendarModel::GetStartTimeMidnightAdjusted(
+base::Time CalendarModel::GetStartTimeAdjusted(
     const google_apis::calendar::CalendarEvent* event) const {
   if (time_difference_minutes_.has_value()) {
-    return (event->start_time().date_time() +
-            base::Minutes(time_difference_minutes_.value()))
-        .UTCMidnight();
+    return event->start_time().date_time() +
+           base::Minutes(time_difference_minutes_.value());
   }
-  return event->start_time().date_time().UTCMidnight();
+  return event->start_time().date_time();
+}
+
+base::Time CalendarModel::GetEndTimeAdjusted(
+    const google_apis::calendar::CalendarEvent* event) const {
+  if (time_difference_minutes_.has_value()) {
+    return event->end_time().date_time() +
+           base::Minutes(time_difference_minutes_.value());
+  }
+  return event->start_time().date_time();
+}
+
+base::Time CalendarModel::GetStartTimeMidnightAdjusted(
+    const google_apis::calendar::CalendarEvent* event) const {
+  return GetStartTimeAdjusted(event).UTCMidnight();
 }
 
 void CalendarModel::InsertEvents(
@@ -431,6 +484,102 @@
   return kNever;
 }
 
+void CalendarModel::DebugDumpEventSmall(
+    std::ostringstream* out,
+    const char* prefix,
+    const google_apis::calendar::CalendarEvent* event) {
+  if (!event)
+    return;
+
+  *out << prefix << "      "
+       << calendar_utils::GetTwelveHourClockTime(
+              event->start_time().date_time())
+       << " -> "
+       << calendar_utils::GetTwelveHourClockTime(event->end_time().date_time())
+       << " (" << event->summary().substr(0, 6) << "...)"
+       << "\n";
+}
+
+void CalendarModel::DebugDumpEventLarge(
+    const char* prefix,
+    const google_apis::calendar::CalendarEvent* event) {
+  if (!event)
+    return;
+
+  VLOG(1) << prefix << " ID: " << event->id();
+  VLOG(1) << prefix << "  summary: \"" << event->summary().substr(0, 6)
+          << "...\"";
+  VLOG(1) << prefix << "  st/et: " << event->start_time().date_time() << " => "
+          << event->end_time().date_time();
+  VLOG(1) << prefix << "  (adj): " << GetStartTimeAdjusted(event) << " => "
+          << GetEndTimeAdjusted(event);
+}
+
+void CalendarModel::DebugDumpEvents(std::ostringstream* out,
+                                    const char* prefix) {
+  *out << prefix << " event_months_ START size: " << event_months_.size()
+       << "\n";
+  for (auto& month : event_months_) {
+    *out << prefix << " month: " << month.first << "\n";
+    for (auto& day : month.second) {
+      *out << prefix << "   day: " << day.first << "\n";
+      for (auto it = day.second.begin(); it != day.second.end(); ++it) {
+        google_apis::calendar::CalendarEvent event = *it;
+        DebugDumpEventSmall(out, prefix, &event);
+      }
+    }
+  }
+  *out << prefix << " event_months_ END"
+       << "\n";
+}
+
+void CalendarModel::DebugDumpMruMonths(std::ostringstream* out,
+                                       const char* prefix) {
+  *out << prefix << " mru_months_ START size: " << mru_months_.size() << "\n";
+  for (auto& month : mru_months_) {
+    *out << prefix << "   " << month << "\n";
+  }
+  *out << prefix << " mru_months_ END"
+       << "\n";
+}
+
+void CalendarModel::DebugDumpNonPrunableMonths(std::ostringstream* out,
+                                               const char* prefix) {
+  *out << prefix
+       << " non_prunable_months_ START size: " << non_prunable_months_.size()
+       << "\n";
+  for (auto& month : non_prunable_months_) {
+    *out << prefix << "   " << month << "\n";
+  }
+  *out << prefix << " non_prunable_months_ END"
+       << "\n";
+}
+
+void CalendarModel::DebugDumpMonthsFetched(std::ostringstream* out,
+                                           const char* prefix) {
+  *out << prefix << " months_fetched_ START size: " << months_fetched_.size()
+       << "\n";
+  for (auto& month : months_fetched_) {
+    *out << prefix << "   " << month << "\n";
+  }
+  *out << prefix << " months_fetched_ END"
+       << "\n";
+}
+
+void CalendarModel::DebugDump() {
+  std::ostringstream out;
+  const char* kDebugDumpPrefix = "CalendarModelDump: ";
+  out << __FUNCTION__ << " START"
+      << "\n";
+  DebugDumpEvents(&out, kDebugDumpPrefix);
+  DebugDumpMruMonths(&out, kDebugDumpPrefix);
+  DebugDumpNonPrunableMonths(&out, kDebugDumpPrefix);
+  DebugDumpMonthsFetched(&out, kDebugDumpPrefix);
+  out << __FUNCTION__ << " END"
+      << "\n";
+  VLOG(1) << out.str();
+}
+
 void CalendarModel::RedistributeEvents(int time_difference_minutes) {
   // Early returns if the time difference is not changed.
   if (time_difference_minutes_.has_value() &&
diff --git a/ash/system/time/calendar_model.h b/ash/system/time/calendar_model.h
index 236a2d9..39a7cf3f 100644
--- a/ash/system/time/calendar_model.h
+++ b/ash/system/time/calendar_model.h
@@ -107,6 +107,9 @@
   // Checks the `FetchingStatus` of a given start time.
   FetchingStatus FindFetchingStatus(base::Time start_time) const;
 
+  // Dumps our internal state to logs.
+  void DebugDump();
+
   // Redistributes all the fetched events to the date map with the
   // `time_difference_minutes_`. This only happens once per calendar view's life
   // cycle.
@@ -129,6 +132,17 @@
   friend class CalendarMonthViewTest;
   friend class CalendarModelFunctionTest;
 
+  // Methods for dumping various event containers/representations to logs.
+  void DebugDumpEventLarge(const char* prefix,
+                           const google_apis::calendar::CalendarEvent* event);
+  void DebugDumpEventSmall(std::ostringstream* out,
+                           const char* prefix,
+                           const google_apis::calendar::CalendarEvent* event);
+  void DebugDumpEvents(std::ostringstream* out, const char* prefix);
+  void DebugDumpMruMonths(std::ostringstream* out, const char* prefix);
+  void DebugDumpNonPrunableMonths(std::ostringstream* out, const char* prefix);
+  void DebugDumpMonthsFetched(std::ostringstream* out, const char* prefix);
+
   // Checks if the event has allowed statuses and is eligible for insertion.
   bool ShouldInsertEvent(
       const google_apis::calendar::CalendarEvent* event) const;
@@ -141,9 +155,18 @@
   void InsertEventInMonth(SingleMonthEventMap& month,
                           const google_apis::calendar::CalendarEvent* event);
 
-  // Returns the event's `start_time` midnight adjusted by the
-  // `time_difference_minutes_`. So the each event will be mapped to the date
-  // map by the local device/set time.
+  // Returns the `start_time` of `event` adjusted by
+  // `time_difference_minutes_`, to ensure that each event is stored by its
+  // local time, e.g. an event that starts at 2022-05-31 22:00:00.000 PST
+  // (2022-06-01 05:00:00.000 UTC) is stored in the map for 05-2022.
+  base::Time GetStartTimeAdjusted(
+      const google_apis::calendar::CalendarEvent* event) const;
+
+  // Returns the `end_time` of `event` adjusted by `time_difference_minutes_`.
+  base::Time GetEndTimeAdjusted(
+      const google_apis::calendar::CalendarEvent* event) const;
+
+  // Returns midnight on the day of the state time of `event`.
   base::Time GetStartTimeMidnightAdjusted(
       const google_apis::calendar::CalendarEvent* event) const;
 
diff --git a/ash/system/unified/unified_system_info_view.cc b/ash/system/unified/unified_system_info_view.cc
index 12e19c45..3389ad9 100644
--- a/ash/system/unified/unified_system_info_view.cc
+++ b/ash/system/unified/unified_system_info_view.cc
@@ -33,6 +33,7 @@
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/chromeos/devicetype_utils.h"
+#include "ui/compositor/layer.h"
 #include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/animation/ink_drop.h"
@@ -320,6 +321,12 @@
     SetLayoutManager(std::move(layout));
 
     battery_image_ = AddChildView(std::make_unique<views::ImageView>());
+    if (features::IsDarkLightModeEnabled()) {
+      // The battery icon requires its own layer to properly render the masked
+      // outline of the badge within the battery icon.
+      battery_image_->SetPaintToLayer();
+      battery_image_->layer()->SetFillsBoundsOpaquely(false);
+    }
     ConfigureIcon();
 
     percentage_ = AddChildView(std::make_unique<views::Label>());
@@ -594,9 +601,8 @@
     separator_->SetPreferredHeight(kUnifiedSystemInfoHeight);
 
     const bool use_smart_charging_ui = UseSmartChargingUI();
-    if (use_smart_charging_ui) {
+    if (use_smart_charging_ui)
       AddChildView(std::make_unique<BatteryIconView>(controller));
-    }
     AddChildView(
         std::make_unique<BatteryLabelView>(controller, use_smart_charging_ui));
   }
diff --git a/ash/webui/OWNERS b/ash/webui/OWNERS
index a7791a94..40e42be 100644
--- a/ash/webui/OWNERS
+++ b/ash/webui/OWNERS
@@ -2,4 +2,8 @@
 # See go/lacros-directory-migration
 file://chromeos/components/OWNERS
 
-file://ash/webui/PLATFORM_OWNERS
\ No newline at end of file
+file://ash/webui/PLATFORM_OWNERS
+
+# For horizontal changes, usually related to the WebUI toolchain. For any
+# changes that have to do with business logic direct to CrOS OWNERS above.
+file://ui/webui/PLATFORM_OWNERS
diff --git a/ash/webui/media_app_ui/media_app_guest_ui.cc b/ash/webui/media_app_ui/media_app_guest_ui.cc
index 60ec67c..8786aad 100644
--- a/ash/webui/media_app_ui/media_app_guest_ui.cc
+++ b/ash/webui/media_app_ui/media_app_guest_ui.cc
@@ -9,6 +9,7 @@
 #include "ash/webui/web_applications/webui_test_prod_util.h"
 #include "base/files/file_util.h"
 #include "base/memory/ref_counted_memory.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/task/task_runner_util.h"
 #include "base/task/thread_pool.h"
 #include "chromeos/grit/chromeos_media_app_bundle_resources.h"
@@ -29,6 +30,8 @@
     FILE_PATH_LITERAL("/usr/share/fonts");
 constexpr char kFontRequestPrefix[] = "fonts/";
 
+int g_media_app_window_count = 0;
+
 bool IsFontRequest(const std::string& path) {
   return base::StartsWith(path, kFontRequestPrefix);
 }
@@ -142,7 +145,10 @@
   content::WebUIDataSource::Add(browser_context, untrusted_source);
 }
 
-MediaAppGuestUI::~MediaAppGuestUI() = default;
+MediaAppGuestUI::~MediaAppGuestUI() {
+  if (app_navigation_committed_)
+    --g_media_app_window_count;
+}
 
 void MediaAppGuestUI::ReadyToCommitNavigation(
     content::NavigationHandle* handle) {
@@ -151,6 +157,19 @@
   if (handle->GetURL() != GURL(kChromeUIMediaAppGuestURL + allowed_resource))
     return;
 
+  if (!app_navigation_committed_) {
+    // Record the number of other media app windows that currently exist when a
+    // new one is created. Counts windows open with any supported file type, or
+    // in the "zero state" (with no open file). Pick 50 as a sensible maximum
+    // (additional windows will be recorded in the 51 bucket).
+    constexpr int kMaxExpectedWindowCount = 50;
+    UMA_HISTOGRAM_EXACT_LINEAR("Apps.MediaApp.Load.OtherOpenWindowCount",
+                               g_media_app_window_count,
+                               kMaxExpectedWindowCount);
+    app_navigation_committed_ = true;
+    ++g_media_app_window_count;
+  }
+
   mojo::AssociatedRemote<blink::mojom::AutoplayConfigurationClient> client;
   handle->GetRenderFrameHost()->GetRemoteAssociatedInterfaces()->GetInterface(
       &client);
diff --git a/ash/webui/media_app_ui/media_app_guest_ui.h b/ash/webui/media_app_ui/media_app_guest_ui.h
index 053b3d2..057b5b7c 100644
--- a/ash/webui/media_app_ui/media_app_guest_ui.h
+++ b/ash/webui/media_app_ui/media_app_guest_ui.h
@@ -48,6 +48,9 @@
   // The background task runner on which file I/O is performed.
   scoped_refptr<base::SequencedTaskRunner> task_runner_;
 
+  // Whether ReadyToCommitNavigation has occurred for the main `app.html`.
+  bool app_navigation_committed_ = false;
+
   base::WeakPtrFactory<MediaAppGuestUI> weak_factory_{this};
 };
 
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.html b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.html
index 8a095b9f..762abf33 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.html
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.html
@@ -12,10 +12,13 @@
 <iron-scroll-threshold id="gridScrollThreshold"
   on-lower-threshold="onGridScrollThresholdReached_">
   <iron-list id="grid" items="[[albumsForDisplay_]]" as="album" grid
-    scroll-target="gridScrollThreshold">
+    scroll-target="gridScrollThreshold"
+    aria-setsize$="[[albumsForDisplay_.length]]"
+    role="list">
     <template>
       <wallpaper-grid-item
         aria-label$="[[getAlbumAriaLabel_(album)]]"
+        aria-posinset$="[[getAlbumAriaIndex_(index)]]"
         class="album"
         image-src="[[album.preview.url]]"
         index="[[index]]"
@@ -23,6 +26,7 @@
         on-keypress="onAlbumSelected_"
         placeholder$="[[isAlbumPlaceholder_(album)]]"
         primary-text="[[album.title]]"
+        role="listitem"
         secondary-text="[[getSecondaryText_(album)]]"
         tabindex$="[[tabIndex]]">
       </wallpaper-grid-item>
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.ts
index 7d8fc72..56e96bc 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/google_photos_albums_element.ts
@@ -207,6 +207,11 @@
     return undefined;
   }
 
+  /** Returns the aria posinset index for the album at index |i|. */
+  private getAlbumAriaIndex_(i: number): number {
+    return i + 1;
+  }
+
   /** Returns the secondary text to display for the specified |album|. */
   private getSecondaryText_(album: GooglePhotosAlbum): string {
     return getCountText(album.photoCount);
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.html b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.html
index 319a2800d..88048c44 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.html
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.html
@@ -140,11 +140,9 @@
 </style>
 <div class="item" aria-selected$="[[selected]]"
      style$="[[getItemPlaceholderAnimationDelay_(index)]]">
-  <template is="dom-if" if="[[isImageVisible_(imageSrc)]]">
-    <img is="cr-auto-img" aria-hidden="true" auto-src="[[imageSrc]]" clear-src
-      is-google-photos>
-    </img>
-  </template>
+  <img id="image" is="cr-auto-img" aria-hidden="true" auto-src="[[imageSrc]]"
+    clear-src hidden is-google-photos>
+  </img>
   <template is="dom-if" if="[[isTextVisible_(primaryText, secondaryText)]]">
     <div class="text">
       <template is="dom-if" if="[[isPrimaryTextVisible_(primaryText)]]">
diff --git a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.ts b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.ts
index 8626165..da9bcae 100644
--- a/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.ts
+++ b/ash/webui/personalization_app/resources/trusted/wallpaper/wallpaper_grid_item_element.ts
@@ -13,6 +13,10 @@
 import {getLoadingPlaceholderAnimationDelay} from '../../common/utils.js';
 import {getTemplate} from './wallpaper_grid_item_element.html.js';
 
+export interface WallpaperGridItem {
+  $: {image: HTMLImageElement};
+}
+
 export class WallpaperGridItem extends PolymerElement {
   static get is() {
     return 'wallpaper-grid-item';
@@ -24,7 +28,11 @@
 
   static get properties() {
     return {
-      imageSrc: String,
+      imageSrc: {
+        type: String,
+        observer: 'onImageSrcChanged_',
+      },
+
       index: Number,
       primaryText: String,
       secondaryText: String,
@@ -51,17 +59,22 @@
   /** Whether the grid item is currently selected. */
   selected: boolean;
 
+  // Invoked on changes to |imageSrc|.
+  private onImageSrcChanged_(imageSrc: WallpaperGridItem['imageSrc']) {
+    // Hide the |image| element until it has successfully loaded. Note that it
+    // is intentional that the |image| element remain hidden on failure.
+    this.$.image.setAttribute('hidden', '');
+    this.$.image.onload = (imageSrc && imageSrc.length) ?
+        () => this.$.image.removeAttribute('hidden') :
+        null;
+  }
+
   /** Returns the delay to use for the grid item's placeholder animation. */
   private getItemPlaceholderAnimationDelay_(index: WallpaperGridItem['index']):
       string {
     return getLoadingPlaceholderAnimationDelay(index);
   }
 
-  /** Whether the image is currently visible. */
-  private isImageVisible_() {
-    return !!this.imageSrc && !!this.imageSrc.length;
-  }
-
   /** Whether the primary text is currently visible. */
   private isPrimaryTextVisible_() {
     return !!this.primaryText && !!this.primaryText.length;
diff --git a/ash/webui/shimless_rma/resources/reimaging_device_information_page.html b/ash/webui/shimless_rma/resources/reimaging_device_information_page.html
index e32962d..532ba2a 100644
--- a/ash/webui/shimless_rma/resources/reimaging_device_information_page.html
+++ b/ash/webui/shimless_rma/resources/reimaging_device_information_page.html
@@ -76,8 +76,8 @@
           [[i18n('confirmDeviceInfoSerialNumberLabel')]]
         </label>
         <div class="input-holder">
-          <cr-input id="serialNumber" placeholder="Enter Serial Number"
-              value="{{serialNumber_}}" aria-labelledby="serialNumberLabel"
+          <cr-input id="serialNumber" value="{{serialNumber_}}" 
+              aria-labelledby="serialNumberLabel"
               disabled="[[allButtonsDisabled]]">
           </cr-input>
           <cr-button id="resetSerialNumber"
@@ -94,8 +94,6 @@
         <div class="input-holder">
           <cr-input
               id="dramPartNumber"
-              placeholder=
-                  "[[i18n('confirmDeviceInfoDramPartNumberPlaceholderLabel')]]"
               value="{{dramPartNumber_}}"
               aria-labelledby="dramPartNumberLabel"
               disabled="[[allButtonsDisabled]]">
diff --git a/ash/webui/shimless_rma/resources/reimaging_firmware_update_page.js b/ash/webui/shimless_rma/resources/reimaging_firmware_update_page.js
index d1db25504..2d7b119 100644
--- a/ash/webui/shimless_rma/resources/reimaging_firmware_update_page.js
+++ b/ash/webui/shimless_rma/resources/reimaging_firmware_update_page.js
@@ -62,10 +62,10 @@
 
   static get properties() {
     return {
-      /** @protected {!UpdateRoFirmwareStatus} */
+      /** @protected {?UpdateRoFirmwareStatus} */
       status_: {
         type: Object,
-        value: UpdateRoFirmwareStatus.kWaitUsb,
+        value: null,
       },
 
       /** @protected {string} */
@@ -137,7 +137,8 @@
    * @protected
    */
   getStatusString_() {
-    return this.i18n(STATUS_TEXT_KEY_MAP[this.status_]);
+    return this.status_ === null ? '' :
+                                   this.i18n(STATUS_TEXT_KEY_MAP[this.status_]);
   }
 
   /**
@@ -145,7 +146,9 @@
    * @protected
    */
   getImgSrc_() {
-    return `illustrations/${STATUS_IMG_MAP[this.status_]}.svg`;
+    return `illustrations/${
+        this.status_ === null ? 'downloading' :
+                                STATUS_IMG_MAP[this.status_]}.svg`;
   }
 }
 
diff --git a/ash/webui/shimless_rma/resources/reimaging_provisioning_page.html b/ash/webui/shimless_rma/resources/reimaging_provisioning_page.html
index 610472ea242..ecdcd49 100644
--- a/ash/webui/shimless_rma/resources/reimaging_provisioning_page.html
+++ b/ash/webui/shimless_rma/resources/reimaging_provisioning_page.html
@@ -4,17 +4,6 @@
 <base-page>
   <div slot="left-pane">
     <h1>[[i18n('provisioningPageTitleText')]]</h1>
-    <div id="provisioningDeviceStatus" class="instructions">
-      [[statusString_]]
-    </div>
-    <div>
-      <cr-button id="retryProvisioningButton" class="cancel-button"
-          on-click="onRetryProvsioningButtonClicked_"
-          hidden$="[[!shouldShowRetryButton_]]"
-          disabled="[[allButtonsDisabled]]">
-        [[i18n('provisioningPageFailedRetryButtonLabel')]]
-      </cr-button>
-    </div>
   </div>
   <div slot="right-pane">
     <div class="illustration-wrapper">
diff --git a/ash/webui/shimless_rma/resources/reimaging_provisioning_page.js b/ash/webui/shimless_rma/resources/reimaging_provisioning_page.js
index 53c00739..c541dbe 100644
--- a/ash/webui/shimless_rma/resources/reimaging_provisioning_page.js
+++ b/ash/webui/shimless_rma/resources/reimaging_provisioning_page.js
@@ -16,15 +16,6 @@
 import {ProvisioningObserverInterface, ProvisioningObserverReceiver, ProvisioningStatus, ShimlessRmaServiceInterface, StateResult} from './shimless_rma_types.js';
 import {disableNextButton, enableNextButton, executeThenTransitionState} from './shimless_rma_util.js';
 
-/** @type {!Object<!ProvisioningStatus, string>} */
-const provisioningStatusTextKeys = {
-  [ProvisioningStatus.kInProgress]: 'provisioningPageInProgressText',
-  [ProvisioningStatus.kComplete]: 'provisioningPageCompleteText',
-  [ProvisioningStatus.kFailedBlocking]: 'provisioningPageFailedBlockingText',
-  [ProvisioningStatus.kFailedNonBlocking]:
-      'provisioningPageFailedNonBlockingText',
-};
-
 /**
  * @fileoverview
  * 'reimaging-provisioning-page' provisions the device then auto-transitions to
@@ -62,23 +53,11 @@
         type: Object,
       },
 
-      /** @protected */
-      statusString_: {
-        type: String,
-        computed: 'getStatusString_(status_)',
-      },
-
       /** @protected {boolean} */
       shouldShowSpinner_: {
         type: Boolean,
         value: false,
       },
-
-      /** @protected {boolean} */
-      shouldShowRetryButton_: {
-        type: Boolean,
-        value: false,
-      },
     };
   }
 
@@ -98,18 +77,6 @@
   }
 
   /**
-   * @return {string}
-   * @protected
-   */
-  getStatusString_() {
-    if (!this.status_) {
-      return '';
-    }
-
-    return this.i18n(provisioningStatusTextKeys[this.status_]);
-  }
-
-  /**
    * Implements ProvisioningObserver.onProvisioningUpdated()
    * TODO(joonbug): Add error handling and display failure using cr-dialog.
    * @param {!ProvisioningStatus} status
@@ -128,21 +95,6 @@
     }
 
     this.shouldShowSpinner_ = this.status_ === ProvisioningStatus.kInProgress;
-    this.shouldShowRetryButton_ =
-        this.status_ === ProvisioningStatus.kFailedBlocking ||
-        this.status_ === ProvisioningStatus.kFailedNonBlocking;
-  }
-
-  /** @private */
-  onRetryProvsioningButtonClicked_() {
-    if (this.status_ !== ProvisioningStatus.kFailedBlocking &&
-        this.status_ !== ProvisioningStatus.kFailedNonBlocking) {
-      console.error('Provisioning has not failed.');
-      return;
-    }
-
-    executeThenTransitionState(
-        this, () => this.shimlessRmaService_.retryProvisioning());
   }
 }
 
diff --git a/ash/webui/shimless_rma/shimless_rma.cc b/ash/webui/shimless_rma/shimless_rma.cc
index fb4d13c0..322c4d2 100644
--- a/ash/webui/shimless_rma/shimless_rma.cc
+++ b/ash/webui/shimless_rma/shimless_rma.cc
@@ -185,15 +185,6 @@
        IDS_SHIMLESS_RMA_RUN_CALIBRATION_COMPLETE_TITLE},
       // Device provisioning page
       {"provisioningPageTitleText", IDS_SHIMLESS_RMA_PROVISIONING_TITLE},
-      {"provisioningPageInProgressText",
-       IDS_SHIMLESS_RMA_PROVISIONING_IN_PROGRESS},
-      {"provisioningPageCompleteText", IDS_SHIMLESS_RMA_PROVISIONING_COMPLETE},
-      {"provisioningPageFailedBlockingText",
-       IDS_SHIMLESS_RMA_PROVISIONING_FAILED_BLOCKING},
-      {"provisioningPageFailedNonBlockingText",
-       IDS_SHIMLESS_RMA_PROVISIONING_FAILED_NON_BLOCKING},
-      {"provisioningPageFailedRetryButtonLabel",
-       IDS_SHIMLESS_RMA_PROVISIONING_FAILED_RETRY_BUTTON_LABEL},
       // Repair complete page
       {"repairCompletedTitleText", IDS_SHIMLESS_RMA_REPAIR_COMPLETED},
       {"repairCompletedDescriptionText",
@@ -272,8 +263,6 @@
        IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_EMPTY_WHITE_LABEL_LABEL},
       {"confirmDeviceInfoDramPartNumberLabel",
        IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_DRAM_PART_NUMBER_LABEL},
-      {"confirmDeviceInfoDramPartNumberPlaceholderLabel",
-       IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_DRAM_PART_NUMBER_PLACEHOLDER_LABEL},
       {"confirmDeviceInfoSkuLabel",
        IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_SKU_LABEL},
       {"confirmDeviceInfoResetButtonLabel",
diff --git a/ash/wm/desks/desk_action_context_menu.cc b/ash/wm/desks/desk_action_context_menu.cc
index 81fffa45..511f6d3 100644
--- a/ash/wm/desks/desk_action_context_menu.cc
+++ b/ash/wm/desks/desk_action_context_menu.cc
@@ -50,6 +50,11 @@
   context_menu_model_.SetVisibleAt(CommandId::kCombineDesks, visible);
 }
 
+void DeskActionContextMenu::MaybeCloseMenu() {
+  if (context_menu_runner_)
+    context_menu_runner_->Cancel();
+}
+
 void DeskActionContextMenu::ExecuteCommand(int command_id, int event_flags) {
   switch (command_id) {
     case CommandId::kCombineDesks:
@@ -74,7 +79,8 @@
     ui::MenuSourceType source_type) {
   const int run_types = views::MenuRunner::USE_ASH_SYS_UI_LAYOUT |
                         views::MenuRunner::CONTEXT_MENU |
-                        views::MenuRunner::FIXED_ANCHOR;
+                        views::MenuRunner::FIXED_ANCHOR |
+                        views::MenuRunner::SEND_GESTURE_EVENTS_TO_OWNER;
 
   context_menu_runner_ =
       std::make_unique<views::MenuRunner>(&context_menu_model_, run_types);
diff --git a/ash/wm/desks/desk_action_context_menu.h b/ash/wm/desks/desk_action_context_menu.h
index cd1703b..afc3f29 100644
--- a/ash/wm/desks/desk_action_context_menu.h
+++ b/ash/wm/desks/desk_action_context_menu.h
@@ -48,6 +48,9 @@
   // can reflect whether there are windows on the desk.
   void SetCombineDesksMenuItemVisibility(bool visible);
 
+  // Closes the context menu if one is running.
+  void MaybeCloseMenu();
+
   // ui::SimpleMenuModel::Delegate:
   void ExecuteCommand(int command_id, int event_flags) override;
   void MenuClosed(ui::SimpleMenuModel* menu) override;
diff --git a/ash/wm/desks/desk_mini_view.cc b/ash/wm/desks/desk_mini_view.cc
index 148790c..4d3ad79 100644
--- a/ash/wm/desks/desk_mini_view.cc
+++ b/ash/wm/desks/desk_mini_view.cc
@@ -242,7 +242,7 @@
   return GetWidget() && HitTestPoint(point_in_view);
 }
 
-void DeskMiniView::OpenContextMenu() {
+void DeskMiniView::OpenContextMenu(ui::MenuSourceType source) {
   is_context_menu_open_ = true;
   UpdateDeskButtonVisibility();
 
@@ -255,13 +255,19 @@
   // visible on all desks.
   context_menu_->SetCombineDesksMenuItemVisibility(ContainsAppWindows(desk_));
 
-  // TODO(crbug.com/1308780): Source will need to be different when opening with
-  // long press and possibly keyboard.
+  // Only show the combine desks context menu option if there are app windows in
+  // the desk.
+  context_menu_->SetCombineDesksMenuItemVisibility(desk_->ContainsAppWindows());
   context_menu_->ShowContextMenuForView(
       this,
       base::i18n::IsRTL() ? desk_preview_->GetBoundsInScreen().bottom_right()
                           : desk_preview_->GetBoundsInScreen().bottom_left(),
-      ui::MENU_SOURCE_MOUSE);
+      source);
+}
+
+void DeskMiniView::MaybeCloseContextMenu() {
+  if (context_menu_)
+    context_menu_->MaybeCloseMenu();
 }
 
 const char* DeskMiniView::GetClassName() const {
diff --git a/ash/wm/desks/desk_mini_view.h b/ash/wm/desks/desk_mini_view.h
index 9d62be6..2ebd50c 100644
--- a/ash/wm/desks/desk_mini_view.h
+++ b/ash/wm/desks/desk_mini_view.h
@@ -100,8 +100,19 @@
   bool IsPointOnMiniView(const gfx::Point& screen_location) const;
 
   // Hides the `desk_action_view_` and opens `context_menu_`. Called when
-  // `desk_preview_` is right-clicked or long-pressed.
-  void OpenContextMenu();
+  // `desk_preview_` is right-clicked or long-pressed. `source` is the type of
+  // action that caused the context menu to be opened (e.g. long press versus
+  // mouse click), and is provided to the context menu runner when the menu is
+  // open in `DeskActionContextMenu::ShowContextMenuForViewImpl` so that it can
+  // further evaluate menu positioning. This ends up doing nothing in particular
+  // in the case of the `DeskActionContextMenu` because we use a
+  // `views::MenuRunner::FIXED_ANCHOR` run type parameter, but the
+  // `MenuRunner::RunMenuAt` function still requires this parameter, so we pass
+  // it down to the function through this parameter.
+  void OpenContextMenu(ui::MenuSourceType source);
+
+  // Closes context menu on this mini view if one exists.
+  void MaybeCloseContextMenu();
 
   // views::View:
   const char* GetClassName() const override;
diff --git a/ash/wm/desks/desk_preview_view.cc b/ash/wm/desks/desk_preview_view.cc
index 6ade218..554fbd83 100644
--- a/ash/wm/desks/desk_preview_view.cc
+++ b/ash/wm/desks/desk_preview_view.cc
@@ -417,7 +417,7 @@
   // should open the context menu.
   if (features::IsDesksCloseAllEnabled() && event.IsRightMouseButton()) {
     DeskNameView::CommitChanges(GetWidget());
-    mini_view_->OpenContextMenu();
+    mini_view_->OpenContextMenu(ui::MENU_SOURCE_MOUSE);
   } else {
     mini_view_->owner_bar()->HandlePressEvent(mini_view_, event);
   }
@@ -441,8 +441,6 @@
   switch (event->type()) {
     // Only long press can trigger drag & drop.
     case ui::ET_GESTURE_LONG_PRESS:
-      // TODO(crbug.com/1308780): Need to figure out how we can still maintain
-      // drag functionality while allowing long press to open the context menu.
       owner_bar->HandleLongPressEvent(mini_view_, *event);
       event->SetHandled();
       break;
diff --git a/ash/wm/desks/desks_bar_view.cc b/ash/wm/desks/desks_bar_view.cc
index 6a98996..cee9984 100644
--- a/ash/wm/desks/desks_bar_view.cc
+++ b/ash/wm/desks/desks_bar_view.cc
@@ -29,7 +29,7 @@
 #include "ash/wm/desks/persistent_desks_bar_button.h"
 #include "ash/wm/desks/persistent_desks_bar_controller.h"
 #include "ash/wm/desks/scroll_arrow_button.h"
-#include "ash/wm/desks/templates/desks_templates_metrics_util.h"
+#include "ash/wm/desks/templates/saved_desk_metrics_util.h"
 #include "ash/wm/desks/templates/saved_desk_presenter.h"
 #include "ash/wm/desks/templates/saved_desk_util.h"
 #include "ash/wm/desks/zero_state_button.h"
@@ -547,6 +547,9 @@
   gfx::PointF location = event.target()->GetScreenLocationF(event);
   InitDragDesk(mini_view, location);
   StartDragDesk(mini_view, location, event.IsMouseEvent());
+
+  if (features::IsDesksCloseAllEnabled())
+    mini_view->OpenContextMenu(ui::MENU_SOURCE_LONG_PRESS);
 }
 
 void DesksBarView::HandleDragEvent(DeskMiniView* mini_view,
@@ -556,6 +559,9 @@
   if (!drag_proxy_ || mini_view->is_animating_to_remove())
     return;
 
+  if (features::IsDesksCloseAllEnabled())
+    mini_view->MaybeCloseContextMenu();
+
   gfx::PointF location = event.target()->GetScreenLocationF(event);
 
   // If the drag proxy is initialized, start the drag. If the drag started,
diff --git a/ash/wm/desks/desks_bar_view.h b/ash/wm/desks/desks_bar_view.h
index 4c8f628..8521a133 100644
--- a/ash/wm/desks/desks_bar_view.h
+++ b/ash/wm/desks/desks_bar_view.h
@@ -10,7 +10,7 @@
 
 #include "ash/ash_export.h"
 #include "ash/wm/desks/desks_controller.h"
-#include "ash/wm/desks/templates/desks_templates_metrics_util.h"
+#include "ash/wm/desks/templates/saved_desk_metrics_util.h"
 #include "base/callback_list.h"
 #include "ui/views/controls/scroll_view.h"
 #include "ui/views/view.h"
diff --git a/ash/wm/desks/desks_test_api.cc b/ash/wm/desks/desks_test_api.cc
index 6e36d9e5..405efcf 100644
--- a/ash/wm/desks/desks_test_api.cc
+++ b/ash/wm/desks/desks_test_api.cc
@@ -20,6 +20,7 @@
 #include "ash/wm/desks/persistent_desks_bar_view.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_grid.h"
+#include "ui/views/controls/menu/menu_runner.h"
 
 namespace ash {
 
@@ -106,6 +107,7 @@
   return GetContextMenuForDesk(index)->context_menu_model_;
 }
 
+// static
 views::View* DesksTestApi::GetHighlightOverlayForDeskPreview(int index) {
   return GetDesksBarView()
       ->mini_views()[index]
@@ -129,6 +131,11 @@
 }
 
 // static
+bool DesksTestApi::IsContextMenuRunningForDesk(int index) {
+  return GetContextMenuForDesk(index)->context_menu_runner_->IsRunning();
+}
+
+// static
 bool DesksTestApi::IsDesksBarLeftGradientVisible() {
   return !GetDesksBarView()
               ->gradient_layer_delegate_->start_fade_zone_bounds()
diff --git a/ash/wm/desks/desks_test_api.h b/ash/wm/desks/desks_test_api.h
index e117256..9b7d89f 100644
--- a/ash/wm/desks/desks_test_api.h
+++ b/ash/wm/desks/desks_test_api.h
@@ -51,6 +51,7 @@
   static bool HasVerticalDotsButton();
   static bool DesksControllerHasDesk(Desk* desk);
   static bool DesksControllerCanUndoDeskRemoval();
+  static bool IsContextMenuRunningForDesk(int index);
 
   static bool IsDesksBarLeftGradientVisible();
   static bool IsDesksBarRightGradientVisible();
diff --git a/ash/wm/desks/desks_unittests.cc b/ash/wm/desks/desks_unittests.cc
index e0f2289..64e80d6 100644
--- a/ash/wm/desks/desks_unittests.cc
+++ b/ash/wm/desks/desks_unittests.cc
@@ -7776,6 +7776,24 @@
   }
 }
 
+// Checks that a `DeskActionContextMenu` opens when the user long-presses a
+// desk's mini view.
+TEST_F(DesksCloseAllTest, ContextMenuOpensOnLongPress) {
+  NewDesk();
+  EnterOverview();
+  ASSERT_TRUE(Shell::Get()->overview_controller()->InOverviewSession());
+
+  // Long press on the first desk preview view.
+  const DeskPreviewView* desk_preview_view =
+      GetPrimaryRootDesksBarView()->mini_views()[0]->desk_preview();
+  const gfx::Point desk_preview_view_center =
+      desk_preview_view->GetBoundsInScreen().CenterPoint();
+  auto* event_generator = GetEventGenerator();
+  LongGestureTap(desk_preview_view_center, event_generator);
+
+  EXPECT_TRUE(DesksTestApi::IsContextMenuRunningForDesk(0));
+}
+
 // TODO(afakhry): Add more tests:
 // - Always on top windows are not tracked by any desk.
 // - Reusing containers when desks are removed and created.
diff --git a/ash/wm/desks/templates/saved_desk_dialog_controller.cc b/ash/wm/desks/templates/saved_desk_dialog_controller.cc
index 85dcdc45..dd4e1b3 100644
--- a/ash/wm/desks/templates/saved_desk_dialog_controller.cc
+++ b/ash/wm/desks/templates/saved_desk_dialog_controller.cc
@@ -9,11 +9,11 @@
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/style/ash_color_provider.h"
-#include "ash/wm/desks/templates/desks_templates_metrics_util.h"
 #include "ash/wm/desks/templates/saved_desk_grid_view.h"
 #include "ash/wm/desks/templates/saved_desk_icon_container.h"
 #include "ash/wm/desks/templates/saved_desk_item_view.h"
 #include "ash/wm/desks/templates/saved_desk_library_view.h"
+#include "ash/wm/desks/templates/saved_desk_metrics_util.h"
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/overview/overview_grid.h"
 #include "base/bind.h"
diff --git a/ash/wm/desks/templates/saved_desk_item_view.cc b/ash/wm/desks/templates/saved_desk_item_view.cc
index cec9765..7293a251 100644
--- a/ash/wm/desks/templates/saved_desk_item_view.cc
+++ b/ash/wm/desks/templates/saved_desk_item_view.cc
@@ -19,11 +19,11 @@
 #include "ash/style/system_shadow.h"
 #include "ash/wm/desks/desk.h"
 #include "ash/wm/desks/desks_textfield.h"
-#include "ash/wm/desks/templates/desks_templates_metrics_util.h"
 #include "ash/wm/desks/templates/saved_desk_dialog_controller.h"
 #include "ash/wm/desks/templates/saved_desk_grid_view.h"
 #include "ash/wm/desks/templates/saved_desk_icon_container.h"
 #include "ash/wm/desks/templates/saved_desk_library_view.h"
+#include "ash/wm/desks/templates/saved_desk_metrics_util.h"
 #include "ash/wm/desks/templates/saved_desk_name_view.h"
 #include "ash/wm/desks/templates/saved_desk_presenter.h"
 #include "ash/wm/overview/overview_constants.h"
diff --git a/ash/wm/desks/templates/saved_desk_library_view.cc b/ash/wm/desks/templates/saved_desk_library_view.cc
index 60951cbe..6f20bba 100644
--- a/ash/wm/desks/templates/saved_desk_library_view.cc
+++ b/ash/wm/desks/templates/saved_desk_library_view.cc
@@ -226,7 +226,7 @@
         scroll_contents->AddChildView(std::make_unique<SavedDeskGridView>());
     grid_views_.push_back(desk_template_grid_view_);
   }
-  if (features::IsSavedDesksEnabled()) {
+  if (saved_desk_util::IsDeskSaveAndRecallEnabled()) {
     grid_labels_.push_back(scroll_contents->AddChildView(MakeGridLabel(
         IDS_ASH_DESKS_TEMPLATES_LIBRARY_SAVE_AND_RECALL_GRID_LABEL)));
     save_and_recall_grid_view_ =
diff --git a/ash/wm/desks/templates/desks_templates_metrics_util.cc b/ash/wm/desks/templates/saved_desk_metrics_util.cc
similarity index 97%
rename from ash/wm/desks/templates/desks_templates_metrics_util.cc
rename to ash/wm/desks/templates/saved_desk_metrics_util.cc
index 0597b86..923a3e9 100644
--- a/ash/wm/desks/templates/desks_templates_metrics_util.cc
+++ b/ash/wm/desks/templates/saved_desk_metrics_util.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 "ash/wm/desks/templates/desks_templates_metrics_util.h"
+#include "ash/wm/desks/templates/saved_desk_metrics_util.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "components/app_constants/constants.h"
diff --git a/ash/wm/desks/templates/desks_templates_metrics_util.h b/ash/wm/desks/templates/saved_desk_metrics_util.h
similarity index 91%
rename from ash/wm/desks/templates/desks_templates_metrics_util.h
rename to ash/wm/desks/templates/saved_desk_metrics_util.h
index 1ac2034..c14b3a6 100644
--- a/ash/wm/desks/templates/desks_templates_metrics_util.h
+++ b/ash/wm/desks/templates/saved_desk_metrics_util.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 ASH_WM_DESKS_TEMPLATES_DESKS_TEMPLATES_METRICS_UTIL_H_
-#define ASH_WM_DESKS_TEMPLATES_DESKS_TEMPLATES_METRICS_UTIL_H_
+#ifndef ASH_WM_DESKS_TEMPLATES_SAVED_DESK_METRICS_UTIL_H_
+#define ASH_WM_DESKS_TEMPLATES_SAVED_DESK_METRICS_UTIL_H_
 
 #include "ash/public/cpp/desk_template.h"
 #include "components/desks_storage/core/desk_model.h"
@@ -48,4 +48,4 @@
 
 }  // namespace ash
 
-#endif  // ASH_WM_DESKS_TEMPLATES_DESKS_TEMPLATES_METRICS_UTIL_H_
+#endif  // ASH_WM_DESKS_TEMPLATES_SAVED_DESK_METRICS_UTIL_H_
diff --git a/ash/wm/desks/templates/saved_desk_presenter.cc b/ash/wm/desks/templates/saved_desk_presenter.cc
index 3c3ce15..7de5436 100644
--- a/ash/wm/desks/templates/saved_desk_presenter.cc
+++ b/ash/wm/desks/templates/saved_desk_presenter.cc
@@ -16,10 +16,10 @@
 #include "ash/wm/desks/desks_controller.h"
 #include "ash/wm/desks/desks_util.h"
 #include "ash/wm/desks/expanded_desks_bar_button.h"
-#include "ash/wm/desks/templates/desks_templates_metrics_util.h"
 #include "ash/wm/desks/templates/saved_desk_grid_view.h"
 #include "ash/wm/desks/templates/saved_desk_item_view.h"
 #include "ash/wm/desks/templates/saved_desk_library_view.h"
+#include "ash/wm/desks/templates/saved_desk_metrics_util.h"
 #include "ash/wm/desks/templates/saved_desk_name_view.h"
 #include "ash/wm/desks/zero_state_button.h"
 #include "ash/wm/overview/overview_controller.h"
diff --git a/ash/wm/desks/templates/desks_templates_test_util.cc b/ash/wm/desks/templates/saved_desk_test_util.cc
similarity index 98%
rename from ash/wm/desks/templates/desks_templates_test_util.cc
rename to ash/wm/desks/templates/saved_desk_test_util.cc
index 7e633c9..271f7398 100644
--- a/ash/wm/desks/templates/desks_templates_test_util.cc
+++ b/ash/wm/desks/templates/saved_desk_test_util.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 "ash/wm/desks/templates/desks_templates_test_util.h"
+#include "ash/wm/desks/templates/saved_desk_test_util.h"
 
 #include "ash/shell.h"
 #include "ash/style/close_button.h"
diff --git a/ash/wm/desks/templates/desks_templates_test_util.h b/ash/wm/desks/templates/saved_desk_test_util.h
similarity index 96%
rename from ash/wm/desks/templates/desks_templates_test_util.h
rename to ash/wm/desks/templates/saved_desk_test_util.h
index 05ddf42..fd756f7 100644
--- a/ash/wm/desks/templates/desks_templates_test_util.h
+++ b/ash/wm/desks/templates/saved_desk_test_util.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 ASH_WM_DESKS_TEMPLATES_DESKS_TEMPLATES_TEST_UTIL_H_
-#define ASH_WM_DESKS_TEMPLATES_DESKS_TEMPLATES_TEST_UTIL_H_
+#ifndef ASH_WM_DESKS_TEMPLATES_SAVED_DESK_TEST_UTIL_H_
+#define ASH_WM_DESKS_TEMPLATES_SAVED_DESK_TEST_UTIL_H_
 
 #include <vector>
 
@@ -165,4 +165,4 @@
 
 }  // namespace ash
 
-#endif  // ASH_WM_DESKS_TEMPLATES_DESKS_TEMPLATES_TEST_UTIL_H_
+#endif  // ASH_WM_DESKS_TEMPLATES_SAVED_DESK_TEST_UTIL_H_
diff --git a/ash/wm/desks/templates/desks_templates_unittest.cc b/ash/wm/desks/templates/saved_desk_unittest.cc
similarity index 96%
rename from ash/wm/desks/templates/desks_templates_unittest.cc
rename to ash/wm/desks/templates/saved_desk_unittest.cc
index 81fcb83e..c2b2823 100644
--- a/ash/wm/desks/templates/desks_templates_unittest.cc
+++ b/ash/wm/desks/templates/saved_desk_unittest.cc
@@ -24,8 +24,6 @@
 #include "ash/wm/desks/desks_bar_view.h"
 #include "ash/wm/desks/desks_test_util.h"
 #include "ash/wm/desks/expanded_desks_bar_button.h"
-#include "ash/wm/desks/templates/desks_templates_metrics_util.h"
-#include "ash/wm/desks/templates/desks_templates_test_util.h"
 #include "ash/wm/desks/templates/save_desk_template_button.h"
 #include "ash/wm/desks/templates/save_desk_template_button_container.h"
 #include "ash/wm/desks/templates/saved_desk_dialog_controller.h"
@@ -34,8 +32,10 @@
 #include "ash/wm/desks/templates/saved_desk_icon_view.h"
 #include "ash/wm/desks/templates/saved_desk_item_view.h"
 #include "ash/wm/desks/templates/saved_desk_library_view.h"
+#include "ash/wm/desks/templates/saved_desk_metrics_util.h"
 #include "ash/wm/desks/templates/saved_desk_name_view.h"
 #include "ash/wm/desks/templates/saved_desk_presenter.h"
+#include "ash/wm/desks/templates/saved_desk_test_util.h"
 #include "ash/wm/desks/zero_state_button.h"
 #include "ash/wm/mru_window_tracker.h"
 #include "ash/wm/overview/overview_constants.h"
@@ -81,12 +81,12 @@
 
 namespace ash {
 
-class DesksTemplatesTest : public OverviewTestBase {
+class SavedDeskTest : public OverviewTestBase {
  public:
-  DesksTemplatesTest() {}
-  DesksTemplatesTest(const DesksTemplatesTest&) = delete;
-  DesksTemplatesTest& operator=(const DesksTemplatesTest&) = delete;
-  ~DesksTemplatesTest() override = default;
+  SavedDeskTest() {}
+  SavedDeskTest(const SavedDeskTest&) = delete;
+  SavedDeskTest& operator=(const SavedDeskTest&) = delete;
+  ~SavedDeskTest() override = default;
 
   // Adds an entry to the desks model directly without capturing a desk. Allows
   // for testing the names and times of the UI directly.
@@ -430,7 +430,7 @@
 
 // Tests the helpers `AddEntry()` and `DeleteEntry()`, which will be used in
 // different tests.
-TEST_F(DesksTemplatesTest, AddDeleteEntry) {
+TEST_F(SavedDeskTest, AddDeleteEntry) {
   const base::GUID expected_uuid = base::GUID::GenerateRandomV4();
   const std::string expected_name = "desk name";
   base::Time expected_time = base::Time::Now();
@@ -448,7 +448,7 @@
 }
 
 // Tests the desks templates button visibility in clamshell mode.
-TEST_F(DesksTemplatesTest, DesksTemplatesButtonVisibilityClamshell) {
+TEST_F(SavedDeskTest, DesksTemplatesButtonVisibilityClamshell) {
   // Helper function to verify which of the desks templates buttons are
   // currently shown.
   auto verify_button_visibilities = [this](bool zero_state_shown,
@@ -525,7 +525,7 @@
 
 // Tests that the no windows widget is hidden when the desk templates grid is
 // shown.
-TEST_F(DesksTemplatesTest, NoWindowsLabelOnTemplateGridShow) {
+TEST_F(SavedDeskTest, NoWindowsLabelOnTemplateGridShow) {
   UpdateDisplay("400x300,400x300");
 
   // At least one entry is required for the templates grid to be shown.
@@ -549,7 +549,7 @@
 // Tests that the no windows widget is shown when the last desk template is
 // deleted, forcing it out of desk template grid and return to overview mode
 // onto a desk grid with no windows
-TEST_F(DesksTemplatesTest, NoWindowsLabelOnReturnToEmptyOverviewDesk) {
+TEST_F(SavedDeskTest, NoWindowsLabelOnReturnToEmptyOverviewDesk) {
   UpdateDisplay("800x600,800x600");
   // Create a test window.
   auto test_window = CreateAppWindow();
@@ -580,7 +580,7 @@
 
 // Tests that the "App does not support split-screen" label is hidden when the
 // desk templates grid is shown.
-TEST_F(DesksTemplatesTest, NoAppSplitScreenLabelOnTemplateGridShow) {
+TEST_F(SavedDeskTest, NoAppSplitScreenLabelOnTemplateGridShow) {
   std::unique_ptr<aura::Window> unsnappable_window = CreateUnsnappableWindow();
   auto test_window = CreateAppWindow();
 
@@ -622,7 +622,7 @@
 }
 
 // Tests when user enter desk templates, a11y alert being sent.
-TEST_F(DesksTemplatesTest, InvokeAccessibilityAlertOnEnterDeskTemplates) {
+TEST_F(SavedDeskTest, InvokeAccessibilityAlertOnEnterDeskTemplates) {
   TestAccessibilityControllerClient client;
 
   // At least one entry is required for the templates grid to be shown.
@@ -646,7 +646,7 @@
 }
 
 // Tests that overview items are hidden when the desk templates grid is shown.
-TEST_F(DesksTemplatesTest, HideOverviewItemsOnTemplateGridShow) {
+TEST_F(SavedDeskTest, HideOverviewItemsOnTemplateGridShow) {
   UpdateDisplay("800x600,800x600");
 
   AddEntry(base::GUID::GenerateRandomV4(), "template_1", base::Time::Now(),
@@ -671,7 +671,7 @@
 
 // Tests that when the templates grid is shown and the active desk is closed,
 // overview items stay hidden.
-TEST_F(DesksTemplatesTest, OverviewItemsStayHiddenInTemplateGridOnDeskClose) {
+TEST_F(SavedDeskTest, OverviewItemsStayHiddenInTemplateGridOnDeskClose) {
   AddEntry(base::GUID::GenerateRandomV4(), "template_1", base::Time::Now(),
            DeskTemplateType::kTemplate);
 
@@ -735,7 +735,7 @@
 }
 
 // Tests the modality of the dialogs shown in desks templates.
-TEST_F(DesksTemplatesTest, DialogSystemModal) {
+TEST_F(SavedDeskTest, DialogSystemModal) {
   UpdateDisplay("800x600,800x600");
 
   ToggleOverview();
@@ -769,7 +769,7 @@
 }
 
 // Tests that the desks templates grid and item views are populated correctly.
-TEST_F(DesksTemplatesTest, DesksTemplatesGridItems) {
+TEST_F(SavedDeskTest, DesksTemplatesGridItems) {
   UpdateDisplay("800x600,800x600");
 
   const base::GUID uuid_1 = base::GUID::GenerateRandomV4();
@@ -816,7 +816,7 @@
 }
 
 // Tests that deleting templates in the templates grid functions correctly.
-TEST_F(DesksTemplatesTest, DeleteTemplate) {
+TEST_F(SavedDeskTest, DeleteTemplate) {
   UpdateDisplay("800x600,800x600");
 
   // Populate with several entries.
@@ -864,7 +864,7 @@
 
 // Tests that the save desk button container is aligned with the first
 // overview item. Regression test for https://crbug.com/1285491.
-TEST_F(DesksTemplatesTest, SaveDeskButtonContainerAligned) {
+TEST_F(SavedDeskTest, SaveDeskButtonContainerAligned) {
   // Create a test window in the current desk.
   auto test_window1 = CreateAppWindow();
   auto test_window2 = CreateAppWindow();
@@ -915,7 +915,7 @@
 
 // Tests that the save desk as template button and save for later button are
 // enabled and disabled as expected based on the number of templates.
-TEST_F(DesksTemplatesTest, SaveDeskButtonsEnabledDisabled) {
+TEST_F(SavedDeskTest, SaveDeskButtonsEnabledDisabled) {
   desks_storage::LocalDeskDataManager::
       SetExcludeSaveAndRecallDeskInMaxEntryCountForTesting(true);
   // Create an app window which should be supported.
@@ -973,7 +973,7 @@
 
 // Tests that clicking the save desk as template button shows the templates
 // grid.
-TEST_F(DesksTemplatesTest, SaveDeskAsTemplateButtonShowsDesksTemplatesGrid) {
+TEST_F(SavedDeskTest, SaveDeskAsTemplateButtonShowsDesksTemplatesGrid) {
   // There are no saved template entries and one test window initially.
   auto test_window = CreateAppWindow();
   ToggleOverview();
@@ -996,7 +996,7 @@
 }
 
 // Tests that saving a template nudges the correct name view.
-TEST_F(DesksTemplatesTest, SaveTemplateNudgesNameView) {
+TEST_F(SavedDeskTest, SaveTemplateNudgesNameView) {
   // Other templates were added earlier.
   AddEntry(base::GUID::GenerateRandomV4(), "template1", base::Time::Now(),
            DeskTemplateType::kTemplate);
@@ -1030,7 +1030,7 @@
 // Tests that launching templates from the templates grid functions correctly.
 // We test both clicking on the card, as well as clicking on the "Use template"
 // button that shows up on hover. Both should do the same thing.
-TEST_F(DesksTemplatesTest, LaunchTemplate) {
+TEST_F(SavedDeskTest, LaunchTemplate) {
   DesksController* desks_controller = DesksController::Get();
   ASSERT_EQ(0, desks_controller->GetActiveDeskIndex());
 
@@ -1068,7 +1068,7 @@
 
 // Tests that launching templates from the templates grid nudges the new desk
 // name view.
-TEST_F(DesksTemplatesTest, LaunchTemplateNudgesNewDeskName) {
+TEST_F(SavedDeskTest, LaunchTemplateNudgesNewDeskName) {
   // Save an entry in the templates grid.
   AddEntry(base::GUID::GenerateRandomV4(), "template", base::Time::Now(),
            DeskTemplateType::kTemplate);
@@ -1103,7 +1103,7 @@
 }
 
 // Tests that the order of SavedDeskItemView is in order.
-TEST_F(DesksTemplatesTest, IconsOrder) {
+TEST_F(SavedDeskTest, IconsOrder) {
   // Create a `DeskTemplate` using which has 5 apps and each app has 1 window.
   AddEntry(base::GUID::GenerateRandomV4(), "template_1", base::Time::Now(),
            DeskTemplateSource::kUser, DeskTemplateType::kTemplate,
@@ -1134,7 +1134,7 @@
 }
 
 // Tests that both regular and lacros browsers have an icon for each unique tab.
-TEST_F(DesksTemplatesTest, NumIconsForBrowser) {
+TEST_F(SavedDeskTest, NumIconsForBrowser) {
   // Create fake restore data with one chrome and one lacros browser. Each
   // browser has two unique tabs.
   const std::string kAppId1 = app_constants::kChromeAppId;
@@ -1184,7 +1184,7 @@
 
 // Tests that icons are ordered such that active tabs and windows are ordered
 // before inactive tabs.
-TEST_F(DesksTemplatesTest, IconsOrderWithInactiveTabs) {
+TEST_F(SavedDeskTest, IconsOrderWithInactiveTabs) {
   const std::string kAppId1 = app_constants::kChromeAppId;
   constexpr int kWindowId1 = 1;
   constexpr int kActiveTabIndex1 = 1;
@@ -1246,7 +1246,7 @@
 // Tests that when two tabs are put into a desk template that have the same
 // domain but different query parameters, only one icon shows up in the template
 // to represent both tabs.
-TEST_F(DesksTemplatesTest, IdenticalURL) {
+TEST_F(SavedDeskTest, IdenticalURL) {
   const std::string kAppId = app_constants::kChromeAppId;
   constexpr int kWindowId = 1;
   constexpr int kActiveTabIndex = 1;
@@ -1293,7 +1293,7 @@
 
 // Tests that the overflow count view is visible, in bounds, displays the right
 // count when there is more than `SavedDeskIconContainer::kMaxIcons` icons.
-TEST_F(DesksTemplatesTest, OverflowIconView) {
+TEST_F(SavedDeskTest, OverflowIconView) {
   // Create a `DeskTemplate` using which has 1 app more than the max and each
   // app has 1 window.
   const int kNumOverflowApps = 1;
@@ -1331,7 +1331,7 @@
 // `SavedDeskIconContainer::kMaxIcons` icons and the overflow
 // icon view, the overflow icon view is visible and its count incremented by the
 // number of icons that had to be hidden.
-TEST_F(DesksTemplatesTest, OverflowIconViewIncrementsForHiddenIcons) {
+TEST_F(SavedDeskTest, OverflowIconViewIncrementsForHiddenIcons) {
   // Create a `DeskTemplate` using which has 3 apps more than
   // `SavedDeskIconContainer::kMaxIcons` and each app has 2 windows.
   // With each app having 2 windows, only 2 app icon views and the overflow view
@@ -1404,7 +1404,7 @@
 //  | |_______|  |_______|  |_______|_______|  |_______| |
 //  |____________________________________________________|
 //
-TEST_F(DesksTemplatesTest, IconViewMultipleWindows) {
+TEST_F(SavedDeskTest, IconViewMultipleWindows) {
   // Create a `DeskTemplate` that contains some apps with multiple windows and
   // more than kMaxIcons windows. The grid should appear like the above diagram.
   AddEntry(base::GUID::GenerateRandomV4(), "template_1", base::Time::Now(),
@@ -1452,7 +1452,7 @@
 
 // Tests that when an app has more than 99 windows, its label is changed to
 // "+99".
-TEST_F(DesksTemplatesTest, IconViewMoreThan99Windows) {
+TEST_F(SavedDeskTest, IconViewMoreThan99Windows) {
   // Create a `DeskTemplate` using which has 1 app with 101 windows.
   AddEntry(base::GUID::GenerateRandomV4(), "template_1", base::Time::Now(),
            DeskTemplateSource::kUser, DeskTemplateType::kTemplate,
@@ -1483,7 +1483,7 @@
 
 // Tests that when there are less than `SavedDeskIconContainer::kMaxIcons`
 // the overflow icon is not visible.
-TEST_F(DesksTemplatesTest, OverflowIconViewHiddenOnNoOverflow) {
+TEST_F(SavedDeskTest, OverflowIconViewHiddenOnNoOverflow) {
   // Create a `DeskTemplate` using which has
   // `SavedDeskIconContainer::kMaxIcons` apps and each app has 1 window.
   std::vector<int> window_info(SavedDeskIconContainer::kMaxIcons, 1);
@@ -1508,7 +1508,7 @@
 
 // Test that the overflow icon counts unavailable icons when there are less than
 // kMaxIcons visible in the container.
-TEST_F(DesksTemplatesTest, OverflowUnavailableLessThan5Icons) {
+TEST_F(SavedDeskTest, OverflowUnavailableLessThan5Icons) {
   // Create a `DeskTemplate` which has 4 apps and each app has 1 window. Set 2
   // of those app ids to be unavailable.
   std::vector<int> window_info(4, 1);
@@ -1542,7 +1542,7 @@
 
 // Test that the overflow icon counts unavailable icons when there are more than
 // kMaxIcons visible in the container, and hidden icons are also added.
-TEST_F(DesksTemplatesTest, OverflowUnavailableMoreThan5Icons) {
+TEST_F(SavedDeskTest, OverflowUnavailableMoreThan5Icons) {
   // Create a `DeskTemplate` which has 8 apps and each app has 1 window. Set 2
   // of those app ids to be unavailable.
   std::vector<int> window_info(8, 1);
@@ -1576,7 +1576,7 @@
 
 // Test that the overflow icon displays the count without a plus when all icons
 // are unavailable.
-TEST_F(DesksTemplatesTest, OverflowUnavailableAllUnavailableIcons) {
+TEST_F(SavedDeskTest, OverflowUnavailableAllUnavailableIcons) {
   // Create a `DeskTemplate` which has 10 apps and each app has 1 window.
   std::vector<int> window_info(10, 1);
   AddEntry(base::GUID::GenerateRandomV4(), "template", base::Time::Now(),
@@ -1609,7 +1609,7 @@
 
 // Tests that the desks templates and save desk button container are hidden when
 // entering overview in tablet mode.
-TEST_F(DesksTemplatesTest, EnteringInTabletMode) {
+TEST_F(SavedDeskTest, EnteringInTabletMode) {
   // Create a desk before entering tablet mode, otherwise the desks bar will not
   // show up.
   DesksController::Get()->NewDesk(DesksCreationRemovalSource::kKeyboard);
@@ -1638,7 +1638,7 @@
 
 // Tests that the desks templates and save desk template buttons are hidden when
 // transitioning from clamshell to tablet mode.
-TEST_F(DesksTemplatesTest, ClamshellToTabletMode) {
+TEST_F(SavedDeskTest, ClamshellToTabletMode) {
   // Create a window and add a test entry. Otherwise the templates UI wouldn't
   // show up.
   auto test_window_1 = CreateAppWindow();
@@ -1671,7 +1671,7 @@
 
 // Tests that the desks templates grid gets hidden when transitioning to tablet
 // mode.
-TEST_F(DesksTemplatesTest, ShowingTemplatesGridToTabletMode) {
+TEST_F(SavedDeskTest, ShowingTemplatesGridToTabletMode) {
   // Create a window and add a test entry. Otherwise the templates UI wouldn't
   // show up.
   auto test_window_1 = CreateAppWindow();
@@ -1714,7 +1714,7 @@
 // causing us to exit overview mode. This tests that if we save a template (and
 // get dropped into the templates grid), and then enter tablet mode, we remain
 // in overview mode. Regression test for https://crbug.com/1277769.
-TEST_F(DesksTemplatesTest, TabletModeActivationIssues) {
+TEST_F(SavedDeskTest, TabletModeActivationIssues) {
   // Create a test window.
   auto test_window = CreateAppWindow();
 
@@ -1729,7 +1729,7 @@
   ASSERT_TRUE(InOverviewSession());
 }
 
-TEST_F(DesksTemplatesTest, OverviewTabbing) {
+TEST_F(SavedDeskTest, OverviewTabbing) {
   auto test_window = CreateAppWindow();
   AddEntry(base::GUID::GenerateRandomV4(), "template1", base::Time::Now(),
            DeskTemplateType::kTemplate);
@@ -1759,7 +1759,7 @@
 
 // Tests that if the templates button is invisible, it is not part of the
 // tabbing order. Regression test for https://crbug.com/1313761.
-TEST_F(DesksTemplatesTest, TabbingInvisibleTemplatesButton) {
+TEST_F(SavedDeskTest, TabbingInvisibleTemplatesButton) {
   // First test the case there are no templates.
   ToggleOverview();
   WaitForDesksTemplatesUI();
@@ -1804,7 +1804,7 @@
 // Tests that the desks bar returns to zero state if the second-to-last desk is
 // deleted while viewing the templates grid. Also verifies that the zero state
 // buttons are visible. Regression test for https://crbug.com/1264989.
-TEST_F(DesksTemplatesTest, DesksBarReturnsToZeroState) {
+TEST_F(SavedDeskTest, DesksBarReturnsToZeroState) {
   DesksController::Get()->NewDesk(DesksCreationRemovalSource::kKeyboard);
   const base::GUID uuid = base::GUID::GenerateRandomV4();
   AddEntry(uuid, "template", base::Time::Now(), DeskTemplateType::kTemplate);
@@ -1843,7 +1843,7 @@
 
 // Tests that the unsupported apps dialog is shown when a user attempts to save
 // an active desk with unsupported apps.
-TEST_F(DesksTemplatesTest, UnsupportedAppsDialog) {
+TEST_F(SavedDeskTest, UnsupportedAppsDialog) {
   // Create a crostini window.
   auto crostini_window = CreateAppWindow();
   crostini_window->SetProperty(aura::client::kAppType,
@@ -1896,7 +1896,7 @@
 // Tests that the save desk as template button and save for later button are
 // disabled when all windows on the desk are unsupported or there are no windows
 // with Full Restore app ids. See crbug.com/1277763.
-TEST_F(DesksTemplatesTest, AllUnsupportedAppsDisablesSaveDeskButtons) {
+TEST_F(SavedDeskTest, AllUnsupportedAppsDisablesSaveDeskButtons) {
   SetDisableAppIdCheckForDeskTemplates(false);
 
   // Use `CreateTestWindow()` instead of `CreateAppWindow()`, which by default
@@ -1926,7 +1926,7 @@
 }
 
 // Tests that adding and removing unsupported windows is counted correctly.
-TEST_F(DesksTemplatesTest, AddRemoveUnsupportedWindows) {
+TEST_F(SavedDeskTest, AddRemoveUnsupportedWindows) {
   auto window1 = CreateTestWindow();
   auto window2 = CreateTestWindow();
 
@@ -1951,7 +1951,7 @@
 }
 
 // Tests the mouse and touch hover behavior on the template item view.
-TEST_F(DesksTemplatesTest, HoverOnTemplateItemView) {
+TEST_F(SavedDeskTest, HoverOnTemplateItemView) {
   auto test_window = CreateAppWindow();
   AddEntry(base::GUID::GenerateRandomV4(), "template1", base::Time::Now(),
            DeskTemplateType::kTemplate);
@@ -2011,7 +2011,7 @@
 // Tests that when a supported app doesn't have any app launch info and a
 // template is saved, the unsupported apps dialog isn't shown. See
 // crbug.com/1269466.
-TEST_F(DesksTemplatesTest, DialogDoesntShowForSupportedAppsWithoutLaunchInfo) {
+TEST_F(SavedDeskTest, DialogDoesntShowForSupportedAppsWithoutLaunchInfo) {
   constexpr int kInvalidWindowKey = -10000;
 
   // Create a normal window.
@@ -2030,7 +2030,7 @@
 
 // Tests that if there is a window minimized in overview, we don't crash when
 // launching a template. Regression test for https://crbug.com/1271337.
-TEST_F(DesksTemplatesTest, LaunchTemplateWithMinimizedOverviewWindow) {
+TEST_F(SavedDeskTest, LaunchTemplateWithMinimizedOverviewWindow) {
   // Create a test minimized window.
   auto window = CreateAppWindow();
   WindowState::Get(window.get())->Minimize();
@@ -2050,7 +2050,7 @@
 
 // Tests that there is no crash if we launch a template after deleting the
 // active desk. Regression test for https://crbug.com/1277203.
-TEST_F(DesksTemplatesTest, LaunchTemplateAfterClosingActiveDesk) {
+TEST_F(SavedDeskTest, LaunchTemplateAfterClosingActiveDesk) {
   auto* desks_controller = DesksController::Get();
   while (desks_controller->CanCreateDesks())
     desks_controller->NewDesk(DesksCreationRemovalSource::kKeyboard);
@@ -2078,7 +2078,7 @@
 // Tests that multiple feedback buttons aren't created when we transition
 // between hiding and showing the templates grid without leaving overview.
 // Regression test for https://crbug.com/1299114.
-TEST_F(DesksTemplatesTest, HideAndShowTemplatesGridWithoutLeavingOverview) {
+TEST_F(SavedDeskTest, HideAndShowTemplatesGridWithoutLeavingOverview) {
   // One window is needed to save a template.
   auto window = CreateAppWindow();
 
@@ -2110,7 +2110,7 @@
 // overview session, we can still see the template items. Opening a second time
 // can be done after deleting all the templates from the first open. Regression
 // test for https://crbug.com/1275179.
-TEST_F(DesksTemplatesTest, TemplatesAreVisibleAfterSecondSave) {
+TEST_F(SavedDeskTest, TemplatesAreVisibleAfterSecondSave) {
   // One window is needed to save a template.
   auto window = CreateAppWindow();
 
@@ -2141,7 +2141,7 @@
 }
 
 // Tests that the desks templates are organized in alphabetical order.
-TEST_F(DesksTemplatesTest, ShowTemplatesInAlphabeticalOrder) {
+TEST_F(SavedDeskTest, ShowTemplatesInAlphabeticalOrder) {
   // Create a window and add three test entry in different names.
   auto test_window = CreateAppWindow();
   AddEntry(base::GUID::GenerateRandomV4(), "B_template", base::Time::Now(),
@@ -2172,7 +2172,7 @@
 
 // Tests that the color of the desks templates button border is as expected.
 // Regression test for https://crbug.com/1265003.
-TEST_F(DesksTemplatesTest, DesksTemplatesButtonBorderColor) {
+TEST_F(SavedDeskTest, DesksTemplatesButtonBorderColor) {
   DesksController::Get()->NewDesk(DesksCreationRemovalSource::kKeyboard);
   AddEntry(base::GUID::GenerateRandomV4(), "name", base::Time::Now(),
            DeskTemplateType::kTemplate);
@@ -2221,7 +2221,7 @@
 // Tests that if we save a template (and get dropped into the templates grid),
 // delete all the templates (and the templates grid gets hidden), the windows in
 // overview get activated and restored when selected.
-TEST_F(DesksTemplatesTest, WindowActivatableAfterSaveAndDeleteTemplate) {
+TEST_F(SavedDeskTest, WindowActivatableAfterSaveAndDeleteTemplate) {
   // Create a test window.
   auto test_window = CreateAppWindow();
 
@@ -2247,7 +2247,7 @@
   EXPECT_EQ(test_window.get(), window_util::GetActiveWindow());
 }
 
-TEST_F(DesksTemplatesTest, TemplateNameBounds) {
+TEST_F(SavedDeskTest, TemplateNameBounds) {
   AddEntry(base::GUID::GenerateRandomV4(), "template name", base::Time::Now(),
            DeskTemplateType::kTemplate);
 
@@ -2280,7 +2280,7 @@
 }
 
 // Tests that we are able to edit the template name.
-TEST_F(DesksTemplatesTest, EditTemplateName) {
+TEST_F(SavedDeskTest, EditTemplateName) {
   auto test_window = CreateAppWindow();
 
   const std::string template_name = "desk name";
@@ -2352,7 +2352,7 @@
 
 // Tests for checking that certain conditions will revert the template name to
 // its original name, even if the text in the textfield has been updated.
-TEST_F(DesksTemplatesTest, TemplateNameChangeAborted) {
+TEST_F(SavedDeskTest, TemplateNameChangeAborted) {
   auto test_window = CreateAppWindow();
 
   const std::string template_name = "desk name";
@@ -2392,7 +2392,7 @@
 // Tests to verify that clicking the spacebar doesn't cause the name view to
 // lose focus (since it's within a button), and that whitespaces are handled
 // correctly.
-TEST_F(DesksTemplatesTest, TemplateNameTestSpaces) {
+TEST_F(SavedDeskTest, TemplateNameTestSpaces) {
   auto test_window = CreateAppWindow();
 
   const std::string template_name = "desk name";
@@ -2432,7 +2432,7 @@
 
 // Tests that there is no crash after we use the keyboard to change the name of
 // a template. Regression test for https://crbug.com/1279649.
-TEST_F(DesksTemplatesTest, EditTemplateNameWithKeyboardNoCrash) {
+TEST_F(SavedDeskTest, EditTemplateNameWithKeyboardNoCrash) {
   AddEntry(base::GUID::GenerateRandomV4(), "a", base::Time::Now(),
            DeskTemplateType::kTemplate);
   AddEntry(base::GUID::GenerateRandomV4(), "b", base::Time::Now(),
@@ -2459,7 +2459,7 @@
 // Tests that there is no crash when leaving the template name view focused with
 // a changed name during shutdown. Regression test for
 // https://crbug.com/1281422.
-TEST_F(DesksTemplatesTest, EditTemplateNameShutdownNoCrash) {
+TEST_F(SavedDeskTest, EditTemplateNameShutdownNoCrash) {
   // The fade out animation of the desks templates grid must be enabled for this
   // crash to have happened.
   animation_scale_ = std::make_unique<ui::ScopedAnimationDurationScaleMode>(
@@ -2488,7 +2488,7 @@
 }
 
 // Tests that the hovering over the templates name shows the expected cursor.
-TEST_F(DesksTemplatesTest, TemplatesNameHitTest) {
+TEST_F(SavedDeskTest, TemplatesNameHitTest) {
   auto* cursor_manager = Shell::Get()->cursor_manager();
 
   for (bool is_rtl : {true, false}) {
@@ -2518,7 +2518,7 @@
 }
 
 // Tests that accessibility overrides are set as expected.
-TEST_F(DesksTemplatesTest, AccessibilityFocusAnnotatorInOverview) {
+TEST_F(SavedDeskTest, AccessibilityFocusAnnotatorInOverview) {
   auto window = CreateTestWindow(gfx::Rect(100, 100));
 
   ToggleOverview();
@@ -2545,7 +2545,7 @@
 
 // Tests that accessibility overrides are set as expected after entering
 // templates view.
-TEST_F(DesksTemplatesTest, AccessibilityFocusAnnotatorInViewingTemplate) {
+TEST_F(SavedDeskTest, AccessibilityFocusAnnotatorInViewingTemplate) {
   auto window = CreateTestWindow(gfx::Rect(100, 100));
 
   AddEntry(base::GUID::GenerateRandomV4(), "test_template", base::Time::Now(),
@@ -2572,7 +2572,7 @@
 
 // Tests that accessibility overrides are set as expected after entering
 // templates view when no window opens.
-TEST_F(DesksTemplatesTest, AccessibilityFocusAnnotatorWhenNoWindowOpen) {
+TEST_F(SavedDeskTest, AccessibilityFocusAnnotatorWhenNoWindowOpen) {
   AddEntry(base::GUID::GenerateRandomV4(), "test_template", base::Time::Now(),
            DeskTemplateType::kTemplate);
 
@@ -2597,7 +2597,7 @@
 
 // Tests that the children of the overview grid matches the order they are
 // displayed so accessibility traverses it correctly.
-TEST_F(DesksTemplatesTest, AccessibilityGridItemTraversalOrder) {
+TEST_F(SavedDeskTest, AccessibilityGridItemTraversalOrder) {
   auto window = CreateTestWindow(gfx::Rect(100, 100));
 
   AddEntry(base::GUID::GenerateRandomV4(), "template_4", base::Time::Now(),
@@ -2627,7 +2627,7 @@
     ASSERT_EQ(grid_items[i], grid_child_views[i]);
 }
 
-TEST_F(DesksTemplatesTest, LayoutItemsInLandscape) {
+TEST_F(SavedDeskTest, LayoutItemsInLandscape) {
   UpdateDisplay("800x600");
 
   // Create a window and add four test entries.
@@ -2655,7 +2655,7 @@
   EXPECT_NE(grid_items[0]->bounds().y(), grid_items[3]->bounds().y());
 }
 
-TEST_F(DesksTemplatesTest, LayoutItemsInPortrait) {
+TEST_F(SavedDeskTest, LayoutItemsInPortrait) {
   UpdateDisplay("600x800");
 
   // Create a window and add four test entries.
@@ -2685,7 +2685,7 @@
 
 // Tests that there is no overlap with the shelf on our smallest supported
 // resolution.
-TEST_F(DesksTemplatesTest, ItemsDoNotOverlapShelf) {
+TEST_F(SavedDeskTest, ItemsDoNotOverlapShelf) {
   // The smallest display resolution we support is 1087x675.
   UpdateDisplay("1000x600");
 
@@ -2715,7 +2715,7 @@
 }
 
 // Tests that showing the overview records to the TemplateGrid histogram.
-TEST_F(DesksTemplatesTest, RecordDesksTemplateGridShowMetric) {
+TEST_F(SavedDeskTest, RecordDesksTemplateGridShowMetric) {
   // Make sure that LoadTemplateHistogram is recorded.
   base::HistogramTester histogram_tester;
 
@@ -2733,7 +2733,7 @@
 
 // Tests that deleting templates in the templates grid Records to the delete
 // template histogram.
-TEST_F(DesksTemplatesTest, DeleteTemplateRecordsMetric) {
+TEST_F(SavedDeskTest, DeleteTemplateRecordsMetric) {
   UpdateDisplay("800x600,800x600");
 
   // Populate with several entries.
@@ -2773,7 +2773,7 @@
 }
 
 // Tests that Launches are recorded to the appropriate histogram.
-TEST_F(DesksTemplatesTest, LaunchTemplateRecordsMetric) {
+TEST_F(SavedDeskTest, LaunchTemplateRecordsMetric) {
   DesksController* desks_controller = DesksController::Get();
   ASSERT_EQ(0, desks_controller->GetActiveDeskIndex());
 
@@ -2802,7 +2802,7 @@
 
 // Tests that clicking the save desk as template button records to the
 // new template histogram.
-TEST_F(DesksTemplatesTest, SaveDeskAsTemplateRecordsMetric) {
+TEST_F(SavedDeskTest, SaveDeskAsTemplateRecordsMetric) {
   // There are no saved template entries and one test window initially.
   auto test_window = CreateAppWindow();
   ToggleOverview();
@@ -2839,7 +2839,7 @@
 
 // Tests that UnsupportedAppDialogShow metric is recorded when the unsupported
 // app dialog is shown.
-TEST_F(DesksTemplatesTest, UnsupportedAppDialogRecordsMetric) {
+TEST_F(SavedDeskTest, UnsupportedAppDialogRecordsMetric) {
   // For asserting histogram was captured.
   base::HistogramTester histogram_tester;
 
@@ -2871,7 +2871,7 @@
 
 // Tests that the window and tab counts are properly recorded in their
 // resepctive metrics.
-TEST_F(DesksTemplatesTest, SaveDeskRecordsWindowAndTabCountMetrics) {
+TEST_F(SavedDeskTest, SaveDeskRecordsWindowAndTabCountMetrics) {
   const std::string kAppId1 = app_constants::kChromeAppId;
   constexpr int kWindowId1 = 1;
   constexpr int kActiveTabIndex1 = 1;
@@ -2930,7 +2930,7 @@
 }
 
 // Tests that the user template count metric is recorded correctly.
-TEST_F(DesksTemplatesTest, UserTemplateCountRecordsMetricCorrectly) {
+TEST_F(SavedDeskTest, UserTemplateCountRecordsMetricCorrectly) {
   // Record histogram.
   base::HistogramTester histogram_tester;
 
@@ -2974,7 +2974,7 @@
 }
 
 // Tests record metrics when current template being replaced.
-TEST_F(DesksTemplatesTest, ReplaceTemplateMetric) {
+TEST_F(SavedDeskTest, ReplaceTemplateMetric) {
   base::HistogramTester histogram_tester;
 
   UpdateDisplay("800x600,800x600");
@@ -3024,7 +3024,7 @@
 
 // Tests that there is no animation when removing a desk with windows while the
 // grid is shown. Regression test for https://crbug.com/1291770.
-TEST_F(DesksTemplatesTest, NoAnimationWhenRemovingDesk) {
+TEST_F(SavedDeskTest, NoAnimationWhenRemovingDesk) {
   AddEntry(base::GUID::GenerateRandomV4(), "template", base::Time::Now(),
            DeskTemplateType::kTemplate);
 
@@ -3058,7 +3058,7 @@
 
 // Tests that windows have their opacity reset after being hidden and then going
 // to a different desk. Regression test for https://crbug.com/1292174.
-TEST_F(DesksTemplatesTest, WindowOpacityResetAfterImmediateExit) {
+TEST_F(SavedDeskTest, WindowOpacityResetAfterImmediateExit) {
   AddEntry(base::GUID::GenerateRandomV4(), "template", base::Time::Now(),
            DeskTemplateType::kTemplate);
 
@@ -3094,7 +3094,7 @@
 
 // Tests that windows have their opacity reset after being hidden and then
 // leaving overview. Regression test for https://crbug.com/1292773.
-TEST_F(DesksTemplatesTest, WindowOpacityResetAfterLeavingOverview) {
+TEST_F(SavedDeskTest, WindowOpacityResetAfterLeavingOverview) {
   const base::GUID uuid = base::GUID::GenerateRandomV4();
   AddEntry(uuid, "template", base::Time::Now(), DeskTemplateType::kTemplate);
 
@@ -3141,7 +3141,7 @@
 
 // Tests that the desks templates name view can accept touch events and get
 // focused. Regression test for https://crbug.com/1291769.
-TEST_F(DesksTemplatesTest, TouchForNameView) {
+TEST_F(SavedDeskTest, TouchForNameView) {
   AddEntry(base::GUID::GenerateRandomV4(), "template", base::Time::Now(),
            DeskTemplateType::kTemplate);
 
@@ -3159,7 +3159,7 @@
 // Tests that the desks templates use the right time string format. It's
 // expected to align with the File App. More details can be found at:
 // https://crbug.com/1268922.
-TEST_F(DesksTemplatesTest, TimeStrFormat) {
+TEST_F(SavedDeskTest, TimeStrFormat) {
   // Uses `01-01-2022 10:30 AM`, `Today 10:30 AM`, `Yesterday 10:30 AM`, and
   // ``Tomorrow 10:30 AM`` for test.
   base::Time time_long_ago, time_today, time_yesterday;
@@ -3241,7 +3241,7 @@
 }
 
 // Test that desk templates can launch snapped windows properly.
-TEST_F(DesksTemplatesTest, SnapWindowTest) {
+TEST_F(SavedDeskTest, SnapWindowTest) {
   auto test_window = CreateAppWindow();
 
   WindowState* window_state = WindowState::Get(test_window.get());
@@ -3263,7 +3263,7 @@
 
 // Tests that we cap the number of template items shown, even if the backend has
 // more saved.
-TEST_F(DesksTemplatesTest, CapTemplateItemsShown) {
+TEST_F(SavedDeskTest, CapTemplateItemsShown) {
   desks_storage::LocalDeskDataManager::SetDisableMaxTemplateLimitForTesting(
       true);
 
@@ -3285,7 +3285,7 @@
 
 // Tests that click or tap could exit grid view and commit name change when
 // appropriate. Regression test for https://crbug.com/1290568.
-TEST_F(DesksTemplatesTest, ClickOrTapToExitGridView) {
+TEST_F(SavedDeskTest, ClickOrTapToExitGridView) {
   AddEntry(base::GUID::GenerateRandomV4(), "template_1", base::Time::Now(),
            DeskTemplateType::kTemplate);
   AddEntry(base::GUID::GenerateRandomV4(), "template_2", base::Time::Now(),
@@ -3349,7 +3349,7 @@
 
 // Tests that right clicking on the wallpaper while showing the saved desks grid
 // does not exit overview.
-TEST_F(DesksTemplatesTest, RightClickOnWallpaperStaysInOverview) {
+TEST_F(SavedDeskTest, RightClickOnWallpaperStaysInOverview) {
   AddEntry(base::GUID::GenerateRandomV4(), "template", base::Time::Now(),
            DeskTemplateType::kTemplate);
 
@@ -3384,7 +3384,7 @@
 // Tests that if there is an existing visible on all desks window, after
 // launching a new desk the window is part of the new desk and is in an overview
 // item.
-TEST_F(DesksTemplatesTest, VisibleOnAllDesksWindowShownProperly) {
+TEST_F(SavedDeskTest, VisibleOnAllDesksWindowShownProperly) {
   auto* controller = DesksController::Get();
   ASSERT_EQ(1, controller->GetNumberOfDesks());
 
@@ -3415,7 +3415,7 @@
 
 // Test save same desk as template won't create name with number on the template
 // view for the second template.
-TEST_F(DesksTemplatesTest, NoDuplicateDisplayedName) {
+TEST_F(SavedDeskTest, NoDuplicateDisplayedName) {
   // There are no saved template entries and one test window initially.
   auto test_window = CreateAppWindow();
   ToggleOverview();
@@ -3527,7 +3527,7 @@
 
 // Tests that if there is a duplicate template name, saving a new template will
 // select all the text. Regression test for https://crbug.com/1303924.
-TEST_F(DesksTemplatesTest, SelectAllAfterSavingDuplicateTemplate) {
+TEST_F(SavedDeskTest, SelectAllAfterSavingDuplicateTemplate) {
   // First add a template that has the same name as the active desk.
   ASSERT_EQ(u"Desk 1", DesksController::Get()->active_desk()->name());
   AddEntry(base::GUID::GenerateRandomV4(), "Desk 1", base::Time::Now(),
@@ -3554,7 +3554,7 @@
 // Tests that a newly saved template will always show up on the top left corner
 // regardless of its name and verify that it goes to its alphabetical order
 // once the name is confirmed.
-TEST_F(DesksTemplatesTest, NoSortBeforeNameConfirmed) {
+TEST_F(SavedDeskTest, NoSortBeforeNameConfirmed) {
   // Create a window to enable the save as template button.
   auto test_window = CreateAppWindow();
 
@@ -3598,7 +3598,7 @@
   EXPECT_EQ(u"zz", name_view->GetText());
 }
 
-TEST_F(DesksTemplatesTest, NudgeOnTheCorrectDisplay) {
+TEST_F(SavedDeskTest, NudgeOnTheCorrectDisplay) {
   UpdateDisplay("800x700,801+0-800x700");
   ASSERT_EQ(2u, Shell::GetAllRootWindows().size());
 
@@ -3620,7 +3620,7 @@
 
 // Tests that the save desk button container is properly placed after an
 // overview item is closed via swipe.
-TEST_F(DesksTemplatesTest, SaveDeskButtonContainerVisibleAfterSwipeToClose) {
+TEST_F(SavedDeskTest, SaveDeskButtonContainerVisibleAfterSwipeToClose) {
   // Use a test widget so we can close it properly after swiping to close. The
   // order matters here; overview items are ordered by MRU order, so the most
   // recently created widget corresponds to the first overview item.
@@ -3654,7 +3654,7 @@
       gfx::RectF(save_desk_button_container->GetBoundsInScreen())));
 }
 
-TEST_F(DesksTemplatesTest, AdminTemplate) {
+TEST_F(SavedDeskTest, AdminTemplate) {
   AddEntry(base::GUID::GenerateRandomV4(), "template", base::Time::Now(),
            DeskTemplateSource::kPolicy, DeskTemplateType::kTemplate,
            std::make_unique<app_restore::RestoreData>());
@@ -3682,7 +3682,7 @@
   EXPECT_NE(name_view, GetHighlightedView());
 }
 
-using DeskSaveAndRecallTest = DesksTemplatesTest;
+using DeskSaveAndRecallTest = SavedDeskTest;
 
 TEST_F(DeskSaveAndRecallTest, SaveDeskForLater) {
   UpdateDisplay("800x600,800x600");
diff --git a/ash/wm/overview/overview_controller.h b/ash/wm/overview/overview_controller.h
index 6bfd864..e05725d 100644
--- a/ash/wm/overview/overview_controller.h
+++ b/ash/wm/overview/overview_controller.h
@@ -115,7 +115,7 @@
   std::vector<aura::Window*> GetWindowsListInOverviewGridsForTest();
 
  private:
-  friend class DesksTemplatesTest;
+  friend class SavedDeskTest;
 
   void set_disable_app_id_check_for_saved_desks(bool val) {
     disable_app_id_check_for_saved_desks_ = val;
diff --git a/base/allocator/partition_allocator/BUILD.gn b/base/allocator/partition_allocator/BUILD.gn
index 0349db52..c8da009 100644
--- a/base/allocator/partition_allocator/BUILD.gn
+++ b/base/allocator/partition_allocator/BUILD.gn
@@ -74,6 +74,7 @@
     "partition_alloc-inl.h",
     "partition_alloc.cc",
     "partition_alloc.h",
+    "partition_alloc_base/atomic_ref_count.h",
     "partition_alloc_base/bits.h",
     "partition_alloc_base/cpu.cc",
     "partition_alloc_base/cpu.h",
@@ -83,6 +84,9 @@
     "partition_alloc_base/gtest_prod_util.h",
     "partition_alloc_base/logging.cc",
     "partition_alloc_base/logging.h",
+    "partition_alloc_base/memory/ref_counted.cc",
+    "partition_alloc_base/memory/ref_counted.h",
+    "partition_alloc_base/memory/scoped_refptr.h",
     "partition_alloc_base/migration_adapter.h",
     "partition_alloc_base/no_destructor.h",
     "partition_alloc_base/numerics/checked_math.h",
diff --git a/base/allocator/partition_allocator/DEPS b/base/allocator/partition_allocator/DEPS
index cbed504..a452c9a 100644
--- a/base/allocator/partition_allocator/DEPS
+++ b/base/allocator/partition_allocator/DEPS
@@ -22,8 +22,6 @@
     "+base/mac/foundation_util.h",
     "+base/mac/mac_util.h",
     "+base/mac/scoped_cftyperef.h",
-    "+base/memory/ref_counted.h",
-    "+base/memory/scoped_refptr.h",
     "+base/process/memory.h",
     "+base/strings/stringprintf.h",
     "+base/system/sys_info.h",
diff --git a/base/allocator/partition_allocator/partition_alloc_base/atomic_ref_count.h b/base/allocator/partition_allocator/partition_alloc_base/atomic_ref_count.h
new file mode 100644
index 0000000..381fce5
--- /dev/null
+++ b/base/allocator/partition_allocator/partition_alloc_base/atomic_ref_count.h
@@ -0,0 +1,69 @@
+// 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.
+
+// This is a low level implementation of atomic semantics for reference
+// counting.  Please use base/memory/ref_counted.h directly instead.
+
+#ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ALLOC_BASE_ATOMIC_REF_COUNT_H_
+#define BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ALLOC_BASE_ATOMIC_REF_COUNT_H_
+
+#include <atomic>
+
+namespace partition_alloc::internal::base {
+
+class AtomicRefCount {
+ public:
+  constexpr AtomicRefCount() : ref_count_(0) {}
+  explicit constexpr AtomicRefCount(int initial_value)
+      : ref_count_(initial_value) {}
+
+  // Increment a reference count.
+  // Returns the previous value of the count.
+  int Increment() { return Increment(1); }
+
+  // Increment a reference count by "increment", which must exceed 0.
+  // Returns the previous value of the count.
+  int Increment(int increment) {
+    return ref_count_.fetch_add(increment, std::memory_order_relaxed);
+  }
+
+  // Decrement a reference count, and return whether the result is non-zero.
+  // Insert barriers to ensure that state written before the reference count
+  // became zero will be visible to a thread that has just made the count zero.
+  bool Decrement() {
+    // TODO(jbroman): Technically this doesn't need to be an acquire operation
+    // unless the result is 1 (i.e., the ref count did indeed reach zero).
+    // However, there are toolchain issues that make that not work as well at
+    // present (notably TSAN doesn't like it).
+    return ref_count_.fetch_sub(1, std::memory_order_acq_rel) != 1;
+  }
+
+  // Return whether the reference count is one.  If the reference count is used
+  // in the conventional way, a reference count of 1 implies that the current
+  // thread owns the reference and no other thread shares it.  This call
+  // performs the test for a reference count of one, and performs the memory
+  // barrier needed for the owning thread to act on the object, knowing that it
+  // has exclusive access to the object.
+  bool IsOne() const { return ref_count_.load(std::memory_order_acquire) == 1; }
+
+  // Return whether the reference count is zero.  With conventional object
+  // referencing counting, the object will be destroyed, so the reference count
+  // should never be zero.  Hence this is generally used for a debug check.
+  bool IsZero() const {
+    return ref_count_.load(std::memory_order_acquire) == 0;
+  }
+
+  // Returns the current reference count (with no barriers). This is subtle, and
+  // should be used only for debugging.
+  int SubtleRefCountForDebug() const {
+    return ref_count_.load(std::memory_order_relaxed);
+  }
+
+ private:
+  std::atomic_int ref_count_;
+};
+
+}  // namespace partition_alloc::internal::base
+
+#endif  // BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ALLOC_BASE_ATOMIC_REF_COUNT_H_
diff --git a/base/allocator/partition_allocator/partition_alloc_base/memory/ref_counted.cc b/base/allocator/partition_allocator/partition_alloc_base/memory/ref_counted.cc
new file mode 100644
index 0000000..c935ce41
--- /dev/null
+++ b/base/allocator/partition_allocator/partition_alloc_base/memory/ref_counted.cc
@@ -0,0 +1,46 @@
+// 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.
+
+#include "base/allocator/partition_allocator/partition_alloc_base/memory/ref_counted.h"
+
+#include <limits>
+#include <ostream>
+#include <type_traits>
+
+namespace partition_alloc::internal::base::subtle {
+
+bool RefCountedThreadSafeBase::HasOneRef() const {
+  return ref_count_.IsOne();
+}
+
+bool RefCountedThreadSafeBase::HasAtLeastOneRef() const {
+  return !ref_count_.IsZero();
+}
+
+#if DCHECK_IS_ON()
+RefCountedThreadSafeBase::~RefCountedThreadSafeBase() {
+  DCHECK(in_dtor_) << "RefCountedThreadSafe object deleted without "
+                      "calling Release()";
+}
+#endif
+
+// For security and correctness, we check the arithmetic on ref counts.
+//
+// In an attempt to avoid binary bloat (from inlining the `CHECK`), we define
+// these functions out-of-line. However, compilers are wily. Further testing may
+// show that `NOINLINE` helps or hurts.
+//
+#if !defined(ARCH_CPU_X86_FAMILY)
+bool RefCountedThreadSafeBase::Release() const {
+  return ReleaseImpl();
+}
+void RefCountedThreadSafeBase::AddRef() const {
+  AddRefImpl();
+}
+void RefCountedThreadSafeBase::AddRefWithCheck() const {
+  AddRefWithCheckImpl();
+}
+#endif
+
+}  // namespace partition_alloc::internal::base::subtle
diff --git a/base/allocator/partition_allocator/partition_alloc_base/memory/ref_counted.h b/base/allocator/partition_allocator/partition_alloc_base/memory/ref_counted.h
new file mode 100644
index 0000000..674c4fd
--- /dev/null
+++ b/base/allocator/partition_allocator/partition_alloc_base/memory/ref_counted.h
@@ -0,0 +1,188 @@
+// Copyright (c) 2012 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 BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ALLOC_BASE_MEMORY_REF_COUNTED_H_
+#define BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ALLOC_BASE_MEMORY_REF_COUNTED_H_
+
+#include "base/allocator/partition_allocator/partition_alloc_base/atomic_ref_count.h"
+#include "base/allocator/partition_allocator/partition_alloc_base/memory/scoped_refptr.h"
+#include "base/base_export.h"
+#include "base/check.h"
+#include "base/check_op.h"
+#include "base/compiler_specific.h"
+#include "base/dcheck_is_on.h"
+#include "build/build_config.h"
+
+namespace partition_alloc::internal::base {
+namespace subtle {
+
+class BASE_EXPORT RefCountedThreadSafeBase {
+ public:
+  RefCountedThreadSafeBase(const RefCountedThreadSafeBase&) = delete;
+  RefCountedThreadSafeBase& operator=(const RefCountedThreadSafeBase&) = delete;
+
+  bool HasOneRef() const;
+  bool HasAtLeastOneRef() const;
+
+ protected:
+  explicit constexpr RefCountedThreadSafeBase(StartRefCountFromZeroTag) {}
+  explicit constexpr RefCountedThreadSafeBase(StartRefCountFromOneTag)
+      : ref_count_(1) {
+#if DCHECK_IS_ON()
+    needs_adopt_ref_ = true;
+#endif
+  }
+
+#if DCHECK_IS_ON()
+  ~RefCountedThreadSafeBase();
+#else
+  ~RefCountedThreadSafeBase() = default;
+#endif
+
+// Release and AddRef are suitable for inlining on X86 because they generate
+// very small code sequences. On other platforms (ARM), it causes a size
+// regression and is probably not worth it.
+#if defined(ARCH_CPU_X86_FAMILY)
+  // Returns true if the object should self-delete.
+  bool Release() const { return ReleaseImpl(); }
+  void AddRef() const { AddRefImpl(); }
+  void AddRefWithCheck() const { AddRefWithCheckImpl(); }
+#else
+  // Returns true if the object should self-delete.
+  bool Release() const;
+  void AddRef() const;
+  void AddRefWithCheck() const;
+#endif
+
+ private:
+  template <typename U>
+  friend scoped_refptr<U> AdoptRef(U*);
+
+  void Adopted() const {
+#if DCHECK_IS_ON()
+    DCHECK(needs_adopt_ref_);
+    needs_adopt_ref_ = false;
+#endif
+  }
+
+  ALWAYS_INLINE void AddRefImpl() const {
+#if DCHECK_IS_ON()
+    DCHECK(!in_dtor_);
+    // This RefCounted object is created with non-zero reference count.
+    // The first reference to such a object has to be made by AdoptRef or
+    // MakeRefCounted.
+    DCHECK(!needs_adopt_ref_);
+#endif
+    ref_count_.Increment();
+  }
+
+  ALWAYS_INLINE void AddRefWithCheckImpl() const {
+#if DCHECK_IS_ON()
+    DCHECK(!in_dtor_);
+    // This RefCounted object is created with non-zero reference count.
+    // The first reference to such a object has to be made by AdoptRef or
+    // MakeRefCounted.
+    DCHECK(!needs_adopt_ref_);
+#endif
+    CHECK_GT(ref_count_.Increment(), 0);
+  }
+
+  ALWAYS_INLINE bool ReleaseImpl() const {
+#if DCHECK_IS_ON()
+    DCHECK(!in_dtor_);
+    DCHECK(!ref_count_.IsZero());
+#endif
+    if (!ref_count_.Decrement()) {
+#if DCHECK_IS_ON()
+      in_dtor_ = true;
+#endif
+      return true;
+    }
+    return false;
+  }
+
+  mutable AtomicRefCount ref_count_{0};
+#if DCHECK_IS_ON()
+  mutable bool needs_adopt_ref_ = false;
+  mutable bool in_dtor_ = false;
+#endif
+};
+
+}  // namespace subtle
+
+// Forward declaration.
+template <class T, typename Traits>
+class RefCountedThreadSafe;
+
+// Default traits for RefCountedThreadSafe<T>.  Deletes the object when its ref
+// count reaches 0.  Overload to delete it on a different thread etc.
+template <typename T>
+struct DefaultRefCountedThreadSafeTraits {
+  static void Destruct(const T* x) {
+    // Delete through RefCountedThreadSafe to make child classes only need to be
+    // friend with RefCountedThreadSafe instead of this struct, which is an
+    // implementation detail.
+    RefCountedThreadSafe<T, DefaultRefCountedThreadSafeTraits>::DeleteInternal(
+        x);
+  }
+};
+
+//
+// A thread-safe variant of RefCounted<T>
+//
+//   class MyFoo : public base::RefCountedThreadSafe<MyFoo> {
+//    ...
+//   };
+//
+// If you're using the default trait, then you should add compile time
+// asserts that no one else is deleting your object.  i.e.
+//    private:
+//     friend class base::RefCountedThreadSafe<MyFoo>;
+//     ~MyFoo();
+//
+// We can use REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE() with RefCountedThreadSafe
+// too. See the comment above the RefCounted definition for details.
+template <class T, typename Traits = DefaultRefCountedThreadSafeTraits<T>>
+class RefCountedThreadSafe : public subtle::RefCountedThreadSafeBase {
+ public:
+  static constexpr subtle::StartRefCountFromZeroTag kRefCountPreference =
+      subtle::kStartRefCountFromZeroTag;
+
+  explicit RefCountedThreadSafe()
+      : subtle::RefCountedThreadSafeBase(T::kRefCountPreference) {}
+
+  RefCountedThreadSafe(const RefCountedThreadSafe&) = delete;
+  RefCountedThreadSafe& operator=(const RefCountedThreadSafe&) = delete;
+
+  void AddRef() const { AddRefImpl(T::kRefCountPreference); }
+
+  void Release() const {
+    if (subtle::RefCountedThreadSafeBase::Release()) {
+      ANALYZER_SKIP_THIS_PATH();
+      Traits::Destruct(static_cast<const T*>(this));
+    }
+  }
+
+ protected:
+  ~RefCountedThreadSafe() = default;
+
+ private:
+  friend struct DefaultRefCountedThreadSafeTraits<T>;
+  template <typename U>
+  static void DeleteInternal(const U* x) {
+    delete x;
+  }
+
+  void AddRefImpl(subtle::StartRefCountFromZeroTag) const {
+    subtle::RefCountedThreadSafeBase::AddRef();
+  }
+
+  void AddRefImpl(subtle::StartRefCountFromOneTag) const {
+    subtle::RefCountedThreadSafeBase::AddRefWithCheck();
+  }
+};
+
+}  // namespace partition_alloc::internal::base
+
+#endif  // BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ALLOC_BASE_MEMORY_REF_COUNTED_H_
diff --git a/base/allocator/partition_allocator/partition_alloc_base/memory/scoped_refptr.h b/base/allocator/partition_allocator/partition_alloc_base/memory/scoped_refptr.h
new file mode 100644
index 0000000..50b39a72
--- /dev/null
+++ b/base/allocator/partition_allocator/partition_alloc_base/memory/scoped_refptr.h
@@ -0,0 +1,371 @@
+// 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.
+
+#ifndef BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ALLOC_BASE_MEMORY_SCOPED_REFPTR_H_
+#define BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ALLOC_BASE_MEMORY_SCOPED_REFPTR_H_
+
+#include <stddef.h>
+
+#include <iosfwd>
+#include <type_traits>
+#include <utility>
+
+#include "base/check.h"
+#include "base/compiler_specific.h"
+
+namespace partition_alloc::internal {
+
+template <class T>
+class scoped_refptr;
+
+namespace base {
+
+template <class, typename>
+class RefCountedThreadSafe;
+
+template <typename T>
+scoped_refptr<T> AdoptRef(T* t);
+
+namespace subtle {
+
+enum AdoptRefTag { kAdoptRefTag };
+enum StartRefCountFromZeroTag { kStartRefCountFromZeroTag };
+enum StartRefCountFromOneTag { kStartRefCountFromOneTag };
+
+// scoped_refptr<T> is typically used with one of several RefCounted<T> base
+// classes or with custom AddRef and Release methods. These overloads dispatch
+// on which was used.
+
+template <typename T, typename U, typename V>
+constexpr bool IsRefCountPreferenceOverridden(
+    const T*,
+    const RefCountedThreadSafe<U, V>*) {
+  return !std::is_same<std::decay_t<decltype(T::kRefCountPreference)>,
+                       std::decay_t<decltype(U::kRefCountPreference)>>::value;
+}
+
+constexpr bool IsRefCountPreferenceOverridden(...) {
+  return false;
+}
+
+template <typename T, typename U, typename V>
+constexpr void AssertRefCountBaseMatches(const T*,
+                                         const RefCountedThreadSafe<U, V>*) {
+  static_assert(
+      std::is_base_of_v<U, T>,
+      "T implements RefCountedThreadSafe<U>, but U is not a base of T.");
+}
+
+constexpr void AssertRefCountBaseMatches(...) {}
+
+}  // namespace subtle
+
+// Creates a scoped_refptr from a raw pointer without incrementing the reference
+// count. Use this only for a newly created object whose reference count starts
+// from 1 instead of 0.
+template <typename T>
+scoped_refptr<T> AdoptRef(T* obj) {
+  using Tag = std::decay_t<decltype(T::kRefCountPreference)>;
+  static_assert(std::is_same<subtle::StartRefCountFromOneTag, Tag>::value,
+                "Use AdoptRef only if the reference count starts from one.");
+
+  DCHECK(obj);
+  DCHECK(obj->HasOneRef());
+  obj->Adopted();
+  return scoped_refptr<T>(obj, subtle::kAdoptRefTag);
+}
+
+namespace subtle {
+
+template <typename T>
+scoped_refptr<T> AdoptRefIfNeeded(T* obj, StartRefCountFromZeroTag) {
+  return scoped_refptr<T>(obj);
+}
+
+template <typename T>
+scoped_refptr<T> AdoptRefIfNeeded(T* obj, StartRefCountFromOneTag) {
+  return AdoptRef(obj);
+}
+
+}  // namespace subtle
+
+// Constructs an instance of T, which is a ref counted type, and wraps the
+// object into a scoped_refptr<T>.
+template <typename T, typename... Args>
+scoped_refptr<T> MakeRefCounted(Args&&... args) {
+  T* obj = new T(std::forward<Args>(args)...);
+  return subtle::AdoptRefIfNeeded(obj, T::kRefCountPreference);
+}
+
+// Takes an instance of T, which is a ref counted type, and wraps the object
+// into a scoped_refptr<T>.
+template <typename T>
+scoped_refptr<T> WrapRefCounted(T* t) {
+  return scoped_refptr<T>(t);
+}
+
+}  // namespace base
+
+//
+// A smart pointer class for reference counted objects.  Use this class instead
+// of calling AddRef and Release manually on a reference counted object to
+// avoid common memory leaks caused by forgetting to Release an object
+// reference.  Sample usage:
+//
+//   class MyFoo : public RefCounted<MyFoo> {
+//    ...
+//    private:
+//     friend class RefCounted<MyFoo>;  // Allow destruction by RefCounted<>.
+//     ~MyFoo();                        // Destructor must be private/protected.
+//   };
+//
+//   void some_function() {
+//     scoped_refptr<MyFoo> foo = MakeRefCounted<MyFoo>();
+//     foo->Method(param);
+//     // |foo| is released when this function returns
+//   }
+//
+//   void some_other_function() {
+//     scoped_refptr<MyFoo> foo = MakeRefCounted<MyFoo>();
+//     ...
+//     foo.reset();  // explicitly releases |foo|
+//     ...
+//     if (foo)
+//       foo->Method(param);
+//   }
+//
+// The above examples show how scoped_refptr<T> acts like a pointer to T.
+// Given two scoped_refptr<T> classes, it is also possible to exchange
+// references between the two objects, like so:
+//
+//   {
+//     scoped_refptr<MyFoo> a = MakeRefCounted<MyFoo>();
+//     scoped_refptr<MyFoo> b;
+//
+//     b.swap(a);
+//     // now, |b| references the MyFoo object, and |a| references nullptr.
+//   }
+//
+// To make both |a| and |b| in the above example reference the same MyFoo
+// object, simply use the assignment operator:
+//
+//   {
+//     scoped_refptr<MyFoo> a = MakeRefCounted<MyFoo>();
+//     scoped_refptr<MyFoo> b;
+//
+//     b = a;
+//     // now, |a| and |b| each own a reference to the same MyFoo object.
+//   }
+//
+// Also see Chromium's ownership and calling conventions:
+// https://chromium.googlesource.com/chromium/src/+/lkgr/styleguide/c++/c++.md#object-ownership-and-calling-conventions
+// Specifically:
+//   If the function (at least sometimes) takes a ref on a refcounted object,
+//   declare the param as scoped_refptr<T>. The caller can decide whether it
+//   wishes to transfer ownership (by calling std::move(t) when passing t) or
+//   retain its ref (by simply passing t directly).
+//   In other words, use scoped_refptr like you would a std::unique_ptr except
+//   in the odd case where it's required to hold on to a ref while handing one
+//   to another component (if a component merely needs to use t on the stack
+//   without keeping a ref: pass t as a raw T*).
+template <class T>
+class TRIVIAL_ABI scoped_refptr {
+ public:
+  typedef T element_type;
+
+  constexpr scoped_refptr() = default;
+
+  // Allow implicit construction from nullptr.
+  constexpr scoped_refptr(std::nullptr_t) {}
+
+  // Constructs from a raw pointer. Note that this constructor allows implicit
+  // conversion from T* to scoped_refptr<T> which is strongly discouraged. If
+  // you are creating a new ref-counted object please use
+  // base::MakeRefCounted<T>() or base::WrapRefCounted<T>(). Otherwise you
+  // should move or copy construct from an existing scoped_refptr<T> to the
+  // ref-counted object.
+  scoped_refptr(T* p) : ptr_(p) {
+    if (ptr_)
+      AddRef(ptr_);
+  }
+
+  // Copy constructor. This is required in addition to the copy conversion
+  // constructor below.
+  scoped_refptr(const scoped_refptr& r) : scoped_refptr(r.ptr_) {}
+
+  // Copy conversion constructor.
+  template <typename U,
+            typename = typename std::enable_if<
+                std::is_convertible<U*, T*>::value>::type>
+  scoped_refptr(const scoped_refptr<U>& r) : scoped_refptr(r.ptr_) {}
+
+  // Move constructor. This is required in addition to the move conversion
+  // constructor below.
+  scoped_refptr(scoped_refptr&& r) noexcept : ptr_(r.ptr_) { r.ptr_ = nullptr; }
+
+  // Move conversion constructor.
+  template <typename U,
+            typename = typename std::enable_if<
+                std::is_convertible<U*, T*>::value>::type>
+  scoped_refptr(scoped_refptr<U>&& r) noexcept : ptr_(r.ptr_) {
+    r.ptr_ = nullptr;
+  }
+
+  ~scoped_refptr() {
+    static_assert(!base::subtle::IsRefCountPreferenceOverridden(
+                      static_cast<T*>(nullptr), static_cast<T*>(nullptr)),
+                  "It's unsafe to override the ref count preference."
+                  " Please remove REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE"
+                  " from subclasses.");
+    if (ptr_)
+      Release(ptr_);
+  }
+
+  T* get() const { return ptr_; }
+
+  T& operator*() const {
+    DCHECK(ptr_);
+    return *ptr_;
+  }
+
+  T* operator->() const {
+    DCHECK(ptr_);
+    return ptr_;
+  }
+
+  scoped_refptr& operator=(std::nullptr_t) {
+    reset();
+    return *this;
+  }
+
+  scoped_refptr& operator=(T* p) { return *this = scoped_refptr(p); }
+
+  // Unified assignment operator.
+  scoped_refptr& operator=(scoped_refptr r) noexcept {
+    swap(r);
+    return *this;
+  }
+
+  // Sets managed object to null and releases reference to the previous managed
+  // object, if it existed.
+  void reset() { scoped_refptr().swap(*this); }
+
+  // Returns the owned pointer (if any), releasing ownership to the caller. The
+  // caller is responsible for managing the lifetime of the reference.
+  [[nodiscard]] T* release();
+
+  void swap(scoped_refptr& r) noexcept { std::swap(ptr_, r.ptr_); }
+
+  explicit operator bool() const { return ptr_ != nullptr; }
+
+  template <typename U>
+  bool operator==(const scoped_refptr<U>& rhs) const {
+    return ptr_ == rhs.get();
+  }
+
+  template <typename U>
+  bool operator!=(const scoped_refptr<U>& rhs) const {
+    return !operator==(rhs);
+  }
+
+  template <typename U>
+  bool operator<(const scoped_refptr<U>& rhs) const {
+    return ptr_ < rhs.get();
+  }
+
+ protected:
+  T* ptr_ = nullptr;
+
+ private:
+  template <typename U>
+  friend scoped_refptr<U> base::AdoptRef(U*);
+
+  scoped_refptr(T* p, base::subtle::AdoptRefTag) : ptr_(p) {}
+
+  // Friend required for move constructors that set r.ptr_ to null.
+  template <typename U>
+  friend class scoped_refptr;
+
+  // Non-inline helpers to allow:
+  //     class Opaque;
+  //     extern template class scoped_refptr<Opaque>;
+  // Otherwise the compiler will complain that Opaque is an incomplete type.
+  static void AddRef(T* ptr);
+  static void Release(T* ptr);
+};
+
+template <typename T>
+T* scoped_refptr<T>::release() {
+  T* ptr = ptr_;
+  ptr_ = nullptr;
+  return ptr;
+}
+
+// static
+template <typename T>
+void scoped_refptr<T>::AddRef(T* ptr) {
+  base::subtle::AssertRefCountBaseMatches(ptr, ptr);
+  ptr->AddRef();
+}
+
+// static
+template <typename T>
+void scoped_refptr<T>::Release(T* ptr) {
+  base::subtle::AssertRefCountBaseMatches(ptr, ptr);
+  ptr->Release();
+}
+
+template <typename T, typename U>
+bool operator==(const scoped_refptr<T>& lhs, const U* rhs) {
+  return lhs.get() == rhs;
+}
+
+template <typename T, typename U>
+bool operator==(const T* lhs, const scoped_refptr<U>& rhs) {
+  return lhs == rhs.get();
+}
+
+template <typename T>
+bool operator==(const scoped_refptr<T>& lhs, std::nullptr_t null) {
+  return !static_cast<bool>(lhs);
+}
+
+template <typename T>
+bool operator==(std::nullptr_t null, const scoped_refptr<T>& rhs) {
+  return !static_cast<bool>(rhs);
+}
+
+template <typename T, typename U>
+bool operator!=(const scoped_refptr<T>& lhs, const U* rhs) {
+  return !operator==(lhs, rhs);
+}
+
+template <typename T, typename U>
+bool operator!=(const T* lhs, const scoped_refptr<U>& rhs) {
+  return !operator==(lhs, rhs);
+}
+
+template <typename T>
+bool operator!=(const scoped_refptr<T>& lhs, std::nullptr_t null) {
+  return !operator==(lhs, null);
+}
+
+template <typename T>
+bool operator!=(std::nullptr_t null, const scoped_refptr<T>& rhs) {
+  return !operator==(null, rhs);
+}
+
+template <typename T>
+std::ostream& operator<<(std::ostream& out, const scoped_refptr<T>& p) {
+  return out << p.get();
+}
+
+template <typename T>
+void swap(scoped_refptr<T>& lhs, scoped_refptr<T>& rhs) noexcept {
+  lhs.swap(rhs);
+}
+
+}  // namespace partition_alloc::internal
+
+#endif  // BASE_ALLOCATOR_PARTITION_ALLOCATOR_PARTITION_ALLOC_BASE_MEMORY_SCOPED_REFPTR_H_
diff --git a/base/allocator/partition_allocator/partition_alloc_forward.h b/base/allocator/partition_allocator/partition_alloc_forward.h
index 58301032..70b456e 100644
--- a/base/allocator/partition_allocator/partition_alloc_forward.h
+++ b/base/allocator/partition_allocator/partition_alloc_forward.h
@@ -65,7 +65,6 @@
 // TODO(https://crbug.com/1288247): Remove these 'using' declarations once
 // the migration to the new namespaces gets done.
 using ::partition_alloc::PartitionRoot;
-using ::partition_alloc::PartitionStatsDumper;
 
 }  // namespace base
 
diff --git a/base/allocator/partition_allocator/starscan/pcscan_internal.cc b/base/allocator/partition_allocator/starscan/pcscan_internal.cc
index 9309f55e8..fb580d8 100644
--- a/base/allocator/partition_allocator/starscan/pcscan_internal.cc
+++ b/base/allocator/partition_allocator/starscan/pcscan_internal.cc
@@ -27,6 +27,8 @@
 #include "base/allocator/partition_allocator/partition_alloc_base/bits.h"
 #include "base/allocator/partition_allocator/partition_alloc_base/cpu.h"
 #include "base/allocator/partition_allocator/partition_alloc_base/debug/alias.h"
+#include "base/allocator/partition_allocator/partition_alloc_base/memory/ref_counted.h"
+#include "base/allocator/partition_allocator/partition_alloc_base/memory/scoped_refptr.h"
 #include "base/allocator/partition_allocator/partition_alloc_base/no_destructor.h"
 #include "base/allocator/partition_allocator/partition_alloc_check.h"
 #include "base/allocator/partition_allocator/partition_alloc_config.h"
@@ -45,8 +47,6 @@
 #include "base/allocator/partition_allocator/thread_cache.h"
 #include "base/compiler_specific.h"
 #include "base/immediate_crash.h"
-#include "base/memory/ref_counted.h"
-#include "base/memory/scoped_refptr.h"
 #include "base/threading/platform_thread.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
@@ -63,11 +63,6 @@
 
 namespace partition_alloc::internal {
 
-namespace base {
-using ::base::MakeRefCounted;
-using ::base::RefCountedThreadSafe;
-}  // namespace base
-
 [[noreturn]] NOINLINE NOT_TAIL_CALLED void DoubleFreeAttempt() {
   PA_NO_CODE_FOLDING();
   IMMEDIATE_CRASH();
diff --git a/base/allocator/partition_allocator/starscan/pcscan_internal.h b/base/allocator/partition_allocator/starscan/pcscan_internal.h
index 4d5babfd..655a026 100644
--- a/base/allocator/partition_allocator/starscan/pcscan_internal.h
+++ b/base/allocator/partition_allocator/starscan/pcscan_internal.h
@@ -13,12 +13,12 @@
 #include <utility>
 #include <vector>
 
+#include "base/allocator/partition_allocator/partition_alloc_base/memory/scoped_refptr.h"
 #include "base/allocator/partition_allocator/partition_alloc_base/no_destructor.h"
 #include "base/allocator/partition_allocator/starscan/metadata_allocator.h"
 #include "base/allocator/partition_allocator/starscan/pcscan.h"
 #include "base/allocator/partition_allocator/starscan/starscan_fwd.h"
 #include "base/allocator/partition_allocator/starscan/write_protector.h"
-#include "base/memory/scoped_refptr.h"
 
 // TODO(crbug.com/1288247): Remove this when migration is complete.
 namespace partition_alloc::internal {
diff --git a/build/android/devil_chromium.json b/build/android/devil_chromium.json
index 97c6b7e..784406d 100644
--- a/build/android/devil_chromium.json
+++ b/build/android/devil_chromium.json
@@ -1,15 +1,6 @@
 {
   "config_type": "BaseConfig",
   "dependencies": {
-    "aapt": {
-      "file_info": {
-        "linux2_x86_64": {
-          "local_paths": [
-            "../../third_party/android_sdk/public/build-tools/27.0.3/aapt"
-          ]
-        }
-      }
-    },
     "adb": {
       "file_info": {
         "linux2_x86_64": {
@@ -19,15 +10,6 @@
         }
       }
     },
-    "android_build_tools_libc++": {
-      "file_info": {
-        "linux2_x86_64": {
-          "local_paths": [
-            "../../third_party/android_sdk/public/build-tools/27.0.3/lib64/libc++.so"
-          ]
-        }
-      }
-    },
     "android_sdk": {
       "file_info": {
         "linux2_x86_64": {
@@ -37,24 +19,6 @@
         }
       }
     },
-    "dexdump": {
-      "file_info": {
-        "linux2_x86_64": {
-          "local_paths": [
-            "../../third_party/android_sdk/public/build-tools/27.0.3/dexdump"
-          ]
-        }
-      }
-    },
-    "split-select": {
-      "file_info": {
-        "linux2_x86_64": {
-          "local_paths": [
-            "../../third_party/android_sdk/public/build-tools/27.0.3/split-select"
-          ]
-        }
-      }
-    },
     "simpleperf": {
       "file_info": {
         "android_armeabi-v7a": {
diff --git a/build/config/android/config.gni b/build/config/android/config.gni
index 56e2c642..0c19c020 100644
--- a/build/config/android/config.gni
+++ b/build/config/android/config.gni
@@ -105,6 +105,8 @@
   }
 
   public_android_sdk_root = "//third_party/android_sdk/public"
+  public_android_sdk_build_tools =
+      "${public_android_sdk_root}/build-tools/31.0.0"
   if (android_sdk_release == "s") {
     default_android_sdk_root = public_android_sdk_root
     default_android_sdk_version = "31"
diff --git a/build/config/fuchsia/test/minimum.shard.test-cml b/build/config/fuchsia/test/minimum.shard.test-cml
index 86da7bb..cdd4d096 100644
--- a/build/config/fuchsia/test/minimum.shard.test-cml
+++ b/build/config/fuchsia/test/minimum.shard.test-cml
@@ -64,7 +64,6 @@
     },
     {
       protocol: [
-        "fuchsia.boot.ReadOnlyLog",
         "fuchsia.media.ProfileProvider",
         "fuchsia.process.Launcher",
         "fuchsia.sys.Loader",
diff --git a/build/fuchsia/linux_internal.sdk.sha1 b/build/fuchsia/linux_internal.sdk.sha1
index 316107d..431fbe6 100644
--- a/build/fuchsia/linux_internal.sdk.sha1
+++ b/build/fuchsia/linux_internal.sdk.sha1
@@ -1 +1 @@
-8.20220510.3.1
+8.20220511.3.1
diff --git a/build/toolchain/win/setup_toolchain.py b/build/toolchain/win/setup_toolchain.py
index 1ddc412..e680ba07 100644
--- a/build/toolchain/win/setup_toolchain.py
+++ b/build/toolchain/win/setup_toolchain.py
@@ -63,7 +63,7 @@
           # exist. This ensures a (relatively) clear error message if the
           # required SDK is not installed.
           for part in setting.split(';'):
-            if not os.path.exists(part):
+            if not os.path.exists(part) and len(part) != 0:
               raise Exception(
                   'Path "%s" from environment variable "%s" does not exist. '
                   'Make sure the necessary SDK is installed.' % (part, envvar))
diff --git a/cc/input/scrollbar_controller.cc b/cc/input/scrollbar_controller.cc
index da51b57a..174da482a 100644
--- a/cc/input/scrollbar_controller.cc
+++ b/cc/input/scrollbar_controller.cc
@@ -118,23 +118,20 @@
   if (scrollbar_part == ScrollbarPart::THUMB || perform_jump_click_on_track) {
     drag_state_ = DragState();
     bool clipped = false;
+
     drag_state_->drag_origin =
-        GetScrollbarRelativePosition(position_in_widget, &clipped);
-    // If the point were clipped we shouldn't have hit tested to a valid part.
+        perform_jump_click_on_track
+            ? DragOriginForJumpClick(scrollbar)
+            : GetScrollbarRelativePosition(position_in_widget, &clipped);
+
+    // If the point were clipped we shouldn't have hit tested to a valid
+    // part.
     DCHECK(!clipped);
 
     // Record the current scroller offset. This will be needed to snap the
     // thumb back to its original position if the pointer moves too far away
-    // from the track during a thumb drag. Additionally, if a thumb drag is
-    // being initiated *after* a jump click, scroll_position_at_start_ needs
-    // to account for that.
-    const float jump_click_thumb_drag_delta =
-        scrollbar->orientation() == ScrollbarOrientation::HORIZONTAL
-            ? scroll_result.scroll_delta.x()
-            : scroll_result.scroll_delta.y();
-    drag_state_->scroll_position_at_start_ =
-        scrollbar->current_pos() +
-        (perform_jump_click_on_track ? jump_click_thumb_drag_delta : 0);
+    // from the track during a thumb drag.
+    drag_state_->scroll_position_at_start_ = scrollbar->current_pos();
     drag_state_->scroller_length_at_previous_move =
         scrollbar->scroll_layer_length();
   }
@@ -162,6 +159,20 @@
       FROM_HERE, cancelable_autoscroll_task_->callback(), delay);
 }
 
+gfx::PointF ScrollbarController::DragOriginForJumpClick(
+    const ScrollbarLayerImplBase* scrollbar) const {
+  // If the user initiated a jump click, create an artificial drag origin to the
+  // middle of the thumb's current position. This is to simulate a jump click as
+  // though the user had initiated a drag normally and pulled the thumb down to
+  // the jump click position. This also keeps consistency with
+  // scroll_position_at_start_ which is used both to calculate scroll positions
+  // as well as for snapping back to origin if the user moves their mouse away.
+  gfx::Rect thumb = scrollbar->ComputeThumbQuadRect();
+  return scrollbar->orientation() == ScrollbarOrientation::HORIZONTAL
+             ? gfx::PointF(thumb.x() + thumb.width() / 2, 0)
+             : gfx::PointF(0, thumb.y() + thumb.height() / 2);
+}
+
 bool ScrollbarController::SnapToDragOrigin(
     const gfx::PointF pointer_position_in_widget) const {
   // Consult the ScrollbarTheme to check if thumb snapping is supported on the
diff --git a/cc/input/scrollbar_controller.h b/cc/input/scrollbar_controller.h
index 94b7e93..7664a8c 100644
--- a/cc/input/scrollbar_controller.h
+++ b/cc/input/scrollbar_controller.h
@@ -273,8 +273,13 @@
   gfx::PointF GetScrollbarRelativePosition(const gfx::PointF position_in_widget,
                                            bool* clipped) const;
 
-  // Decides if the scroller should snap to the offset that it was originally at
-  // (i.e the offset before the thumb drag).
+  // Computes an aritificial drag origin for jump clicks, to give the scrollbar
+  // a proper place to snap back to on a jump click then drag
+  gfx::PointF DragOriginForJumpClick(
+      const ScrollbarLayerImplBase* scrollbar) const;
+
+  // Decides if the scroller should snap to the offset that it was
+  // originally at (i.e the offset before the thumb drag).
   bool SnapToDragOrigin(const gfx::PointF pointer_position_in_widget) const;
 
   // Decides whether a track autoscroll should be aborted (or restarted) due to
diff --git a/cc/metrics/frame_info.cc b/cc/metrics/frame_info.cc
index 7fab0bf0..003cc67a 100644
--- a/cc/metrics/frame_info.cc
+++ b/cc/metrics/frame_info.cc
@@ -180,6 +180,10 @@
   return false;
 }
 
+bool FrameInfo::WasSmoothMainUpdateExpected() const {
+  return final_state != FrameFinalState::kNoUpdateDesired;
+}
+
 bool FrameInfo::IsScrollPrioritizeFrameDropped() const {
   // If any scroll is active the dropped frame for only the scrolling thread is
   // reported. If no scroll is active then reports if dropped frames is
diff --git a/cc/metrics/frame_info.h b/cc/metrics/frame_info.h
index 1ebdfd8..6fbbeb3 100644
--- a/cc/metrics/frame_info.h
+++ b/cc/metrics/frame_info.h
@@ -70,6 +70,7 @@
   // whether the update was part of a smooth sequence.
   bool WasSmoothCompositorUpdateDropped() const;
   bool WasSmoothMainUpdateDropped() const;
+  bool WasSmoothMainUpdateExpected() const;
 
   bool IsScrollPrioritizeFrameDropped() const;
 
diff --git a/cc/metrics/frame_sequence_metrics.cc b/cc/metrics/frame_sequence_metrics.cc
index 6b4d3b0..df79838 100644
--- a/cc/metrics/frame_sequence_metrics.cc
+++ b/cc/metrics/frame_sequence_metrics.cc
@@ -91,6 +91,13 @@
        FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)});
 }
 
+std::string GetThroughputV3HistogramName(FrameSequenceTrackerType type,
+                                         const char* thread_name) {
+  return base::StrCat(
+      {"Graphics.Smoothness.PercentDroppedFrames3.", thread_name, ".",
+       FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)});
+}
+
 std::string GetMissedDeadlineHistogramName(FrameSequenceTrackerType type,
                                            const char* thread_name) {
   return base::StrCat(
@@ -186,6 +193,9 @@
   v2_.frames_expected += metrics->v2_.frames_expected;
   v2_.frames_dropped += metrics->v2_.frames_dropped;
 
+  v3_.frames_expected += metrics->v3_.frames_expected;
+  v3_.frames_dropped += metrics->v3_.frames_dropped;
+
   if (jank_reporter_)
     jank_reporter_->Merge(std::move(metrics->jank_reporter_));
 
@@ -199,12 +209,13 @@
 bool FrameSequenceMetrics::HasEnoughDataForReporting() const {
   return impl_throughput_.frames_expected >= kMinFramesForThroughputMetric ||
          main_throughput_.frames_expected >= kMinFramesForThroughputMetric ||
-         v2_.frames_expected >= kMinFramesForThroughputMetric;
+         v2_.frames_expected >= kMinFramesForThroughputMetric ||
+         v3_.frames_expected >= kMinFramesForThroughputMetric;
 }
 
 bool FrameSequenceMetrics::HasDataLeftForReporting() const {
-  return impl_throughput_.frames_expected > 0 ||
-         main_throughput_.frames_expected > 0 || v2_.frames_expected > 0;
+  return impl_throughput_.frames_expected > 0 || v2_.frames_expected > 0 ||
+         main_throughput_.frames_expected > 0 || v3_.frames_expected > 0;
 }
 
 void FrameSequenceMetrics::AdoptTrace(FrameSequenceMetrics* adopt_from) {
@@ -264,11 +275,12 @@
   const bool compositor_report = ThroughputData::CanReportHistogram(
       this, SmoothEffectDrivingThread::kCompositor, impl_throughput_);
 
+  const auto thread_type = GetEffectiveThread();
+  const bool is_animation = ShouldReportForAnimation(type(), thread_type);
+  const bool is_interaction =
+      ShouldReportForInteraction(type(), thread_type, thread_type);
+
   if (v2_.frames_expected >= kMinFramesForThroughputMetric) {
-    const auto thread_type = GetEffectiveThread();
-    const bool is_animation = ShouldReportForAnimation(type(), thread_type);
-    const bool is_interaction =
-        ShouldReportForInteraction(type(), thread_type, thread_type);
     int percent = v2_.frames_expected == 0
                       ? 0
                       : std::ceil(100. * v2_.frames_dropped /
@@ -303,6 +315,41 @@
     v2_ = {};
   }
 
+  if (v3_.frames_expected >= kMinFramesForThroughputMetric) {
+    int percent = v3_.frames_expected == 0
+                      ? 0
+                      : std::ceil(100. * v3_.frames_dropped /
+                                  static_cast<double>(v3_.frames_expected));
+
+    if (is_animation) {
+      UMA_HISTOGRAM_PERCENTAGE(
+          "Graphics.Smoothness.PercentDroppedFrames3.AllAnimations", percent);
+    }
+    if (is_interaction) {
+      UMA_HISTOGRAM_PERCENTAGE(
+          "Graphics.Smoothness.PercentDroppedFrames3.AllInteractions", percent);
+    }
+    if (is_animation || is_interaction) {
+      UMA_HISTOGRAM_PERCENTAGE(
+          "Graphics.Smoothness.PercentDroppedFrames3.AllSequences", percent);
+    }
+
+    const char* thread_name =
+        thread_type == SmoothEffectDrivingThread::kCompositor
+            ? "CompositorThread"
+            : "MainThread";
+
+    STATIC_HISTOGRAM_POINTER_GROUP(
+        GetThroughputV3HistogramName(type(), thread_name),
+        GetIndexForMetric(thread_type, type_), kMaximumHistogramIndex,
+        Add(percent),
+        base::LinearHistogram::FactoryGet(
+            GetThroughputV3HistogramName(type(), thread_name), 1, 100, 101,
+            base::HistogramBase::kUmaTargetedHistogramFlag));
+
+    v3_ = {};
+  }
+
   absl::optional<int> impl_throughput_percent_dropped;
   absl::optional<int> impl_throughput_percent_missed;
   absl::optional<int> main_throughput_percent_dropped;
@@ -693,10 +740,18 @@
     case SmoothEffectDrivingThread::kCompositor:
       if (frame_info.WasSmoothCompositorUpdateDropped()) {
         ++v2_.frames_dropped;
+        ++v3_.frames_dropped;
       }
       ++v2_.frames_expected;
+      ++v3_.frames_expected;
       break;
     case SmoothEffectDrivingThread::kMain:
+      if (frame_info.WasSmoothMainUpdateExpected()) {
+        if (frame_info.WasSmoothMainUpdateDropped()) {
+          ++v3_.frames_dropped;
+        }
+        ++v3_.frames_expected;
+      }
       if (frame_info.WasSmoothMainUpdateDropped()) {
         ++v2_.frames_dropped;
       }
diff --git a/cc/metrics/frame_sequence_metrics.h b/cc/metrics/frame_sequence_metrics.h
index f397039..ba9fb74e 100644
--- a/cc/metrics/frame_sequence_metrics.h
+++ b/cc/metrics/frame_sequence_metrics.h
@@ -235,6 +235,12 @@
     uint32_t frames_dropped = 0;
   } v2_;
 
+  // Track state for measuring the PercentDroppedFrames v3 metrics.
+  struct {
+    uint32_t frames_expected = 0;
+    uint32_t frames_dropped = 0;
+  } v3_;
+
   ThroughputData impl_throughput_;
   ThroughputData main_throughput_;
 
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index eeab1ad8..00eddb3 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -14263,9 +14263,10 @@
   scrollbar->SetBounds(scrollbar_size);
   host_impl_->set_force_smooth_wheel_scrolling_for_testing(true);
 
+  const int thumb_len = 50;
   // Set up the thumb dimensions.
   scrollbar->SetThumbThickness(15);
-  scrollbar->SetThumbLength(50);
+  scrollbar->SetThumbLength(thumb_len);
   scrollbar->SetTrackRect(gfx::Rect(0, 15, 15, 575));
 
   // Set up scrollbar arrows.
@@ -14298,11 +14299,17 @@
                     .scrollbar_controller_for_testing()
                     ->drag_state_.has_value());
 
-    // This verifies that the jump click delta was accounted for correctly.
+    // This verifies that the start/snap-back position is the scroll position
+    // before any jump-click
     EXPECT_FLOAT_EQ(GetInputHandler()
                         .scrollbar_controller_for_testing()
                         ->drag_state_->scroll_position_at_start_,
-                    243.80952f);
+                    0.0f);
+
+    EXPECT_FLOAT_EQ(GetInputHandler()
+                        .scrollbar_controller_for_testing()
+                        ->drag_state_->drag_origin.y(),
+                    15.0f + thumb_len / 2.0f);
   }
 
   // Tear down the LayerTreeHostImpl before the InputHandlerClient.
diff --git a/chrome/VERSION b/chrome/VERSION
index 1963593b..2f6390a 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=103
 MINOR=0
-BUILD=5058
+BUILD=5059
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 13bcc6f..a94199a 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -1073,6 +1073,7 @@
     "//chrome/browser/ui/messages/android:junit",
     "//chrome/browser/user_education:java",
     "//chrome/browser/util:java",
+    "//chrome/browser/util:junit_tests",
     "//chrome/browser/version:java",
     "//chrome/browser/video_tutorials:factory_java",
     "//chrome/browser/video_tutorials:java",
@@ -3127,7 +3128,6 @@
     "//chrome/browser/ui/android/omnibox:javatests",
     "//chrome/browser/ui/android/webid/internal:javatests",
     "//chrome/browser/ui/messages/android:javatests",
-    "//chrome/browser/util:javatests",
   ]
 
   data_deps = [ "//testing/buildbot/filters:chrome_public_test_apk_filters" ]
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 67cebfe..f97f44af 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -416,8 +416,6 @@
   "java/src/org/chromium/chrome/browser/contextualsearch/RelatedSearchesUma.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/ResolvedSearchTerm.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/SelectionClientManager.java",
-  "java/src/org/chromium/chrome/browser/contextualsearch/ShortTextRunSuppression.java",
-  "java/src/org/chromium/chrome/browser/contextualsearch/SmallTextSuppression.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/TapFarFromPreviousSuppression.java",
   "java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java",
   "java/src/org/chromium/chrome/browser/crash/ChromeMinidumpUploadJobServiceImpl.java",
diff --git a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java
index a32b83cb..bacaa07 100644
--- a/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java
+++ b/chrome/android/features/start_surface/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java
@@ -320,6 +320,8 @@
     // clang-format off
     @EnableFeatures({ChromeFeatureList.TAB_TO_GTS_ANIMATION + "<Study"})
     @CommandLineFlags.Add({BASE_PARAMS})
+    @DisableIf.Build(message = "Flaky on emulators; see https://crbug.com/1324721",
+        supported_abis_includes = "x86")
     public void testRenderGrid_Incognito() throws IOException {
         // clang-format on
         ChromeTabbedActivity cta = mActivityTestRule.getActivity();
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java
index c7fc3af..f015eee 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinder.java
@@ -8,6 +8,7 @@
 
 import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
+import android.graphics.Matrix;
 import android.text.TextUtils;
 import android.view.View;
 import android.view.ViewGroup;
@@ -336,9 +337,8 @@
         Callback<Bitmap> callback = result -> {
             if (result != null) {
                 if (TabUiFeatureUtilities.isTabletGridTabSwitcherPolishEnabled(view.getContext())) {
-                    // Resize bitmap to thumbnail
-                    result = resizeBitmap(result, thumbnail.getWidth(), thumbnail.getHeight());
-                    thumbnail.setScaleType(ScaleType.CENTER_CROP);
+                    // Adjust bitmap to thumbnail.
+                    updateThumbnailMatrix(thumbnail, result, model);
                 } else {
                     thumbnail.setScaleType(ScaleType.FIT_CENTER);
                     thumbnail.setAdjustViewBounds(true);
@@ -353,11 +353,39 @@
         }
     }
 
-    private static Bitmap resizeBitmap(Bitmap source, int newWidth, int newHeight) {
-        if (newWidth <= 0 || newHeight <= 0) {
-            return source;
+    /**
+     * Update @{@link Matrix} of ImageView. Bitmap is scaled to larger of the two dimens, then top-center
+     * aligned.
+     * @param thumbnail Destination image view @{@link TabGridThumbnailView}.
+     * @param source Image bitmap to resize.
+     * @param model PropertyModel containing the destination properties.
+     */
+    private static void updateThumbnailMatrix(TabGridThumbnailView thumbnail, Bitmap source,  PropertyModel model) {
+        int newWidth = model.get(TabProperties.GRID_CARD_WIDTH);
+        int newHeight = model.get(TabProperties.GRID_CARD_HEIGHT);
+        if (newWidth <= 0 || newHeight <= 0
+                || (newWidth == source.getWidth() && newHeight == source.getHeight())) {
+            thumbnail.setScaleType(ScaleType.FIT_CENTER);
+            return;
         }
-        return Bitmap.createScaledBitmap(source, newWidth, newHeight, true);
+
+        final Matrix m = new Matrix();
+
+        // Scale image to larger of the two dimensions.
+        final float scale = Math.max(
+                (float) newWidth / source.getWidth(), (float) newHeight / source.getHeight()); //
+        m.setScale(scale, scale);
+
+        /**
+         * Bitmap is top-left aligned by default. We want to translate the image to be horizontally
+         * center-aligned. |destination width - scaled width| is the width that is out of view
+         * bounds. We need to translate bitmap (to left) by half of this distance.
+         */
+        final int xOffset = (int) ((newWidth - (source.getWidth() * scale)) / 2);
+        m.postTranslate(xOffset, 0);
+
+        thumbnail.setScaleType(ScaleType.MATRIX);
+        thumbnail.setImageMatrix(m);
     }
 
     /**
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
index ebbff66..2bdb98d 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabUiFeatureUtilities.java
@@ -113,6 +113,7 @@
                     TAB_STRIP_IMPROVEMENTS_TAB_WIDTH_PARAM, 190.f);
 
     private static Boolean sTabManagementModuleSupportedForTesting;
+    private static Boolean sGridTabSwitcherPolishEnabledForTesting;
 
     /**
      * Set whether the tab management module is supported for testing.
@@ -156,10 +157,20 @@
     }
 
     /**
+     * Set whether the tablet grid tab switcher polish is enabled for testing.
+     */
+    public static void setTabletGridTabSwitcherPolishEnabledForTesting(@Nullable Boolean enabled) {
+        sGridTabSwitcherPolishEnabledForTesting = enabled;
+    }
+
+    /**
      * @return Whether the tablet Grid Tab Switcher Polish is enabled.
      * @param context The activity context.
      */
     public static boolean isTabletGridTabSwitcherPolishEnabled(Context context) {
+        if (sGridTabSwitcherPolishEnabledForTesting != null) {
+            return sGridTabSwitcherPolishEnabledForTesting;
+        }
         return DeviceFormFactor.isNonMultiDisplayContextOnTablet(context)
                 && GRID_TAB_SWITCHER_FOR_TABLETS_POLISH.getValue();
     }
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinderUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinderUnitTest.java
new file mode 100644
index 0000000..198ad2c
--- /dev/null
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinderUnitTest.java
@@ -0,0 +1,198 @@
+// Copyright 2022 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.tasks.tab_management;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Matrix;
+import android.view.ViewGroup.LayoutParams;
+import android.widget.ImageView.ScaleType;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import org.chromium.base.Callback;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.tab_ui.R;
+import org.chromium.chrome.test.util.browser.Features;
+import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.widget.ViewLookupCachingFrameLayout;
+
+/** Junit Tests for {@link TabGridViewBinder}. */
+@RunWith(BaseRobolectricTestRunner.class)
+public final class TabGridViewBinderUnitTest {
+    private static final int INIT_WIDTH = 100;
+    private static final int INIT_HEIGHT = 200;
+    @Rule
+    public TestRule mFeaturesProcessorRule = new Features.JUnitProcessor();
+    @Mock
+    private ViewLookupCachingFrameLayout mViewGroup;
+    @Mock
+    private Context mContext;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private TabListMediator.ThumbnailFetcher mFetcher;
+    @Mock
+    private TabGridThumbnailView mThumbnailView;
+    @Captor
+    private ArgumentCaptor<Callback<Bitmap>> mCallbackCaptor;
+
+    private PropertyModel mModel;
+    private LayoutParams mLayoutParams;
+    private Bitmap mBitmap;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mModel = new PropertyModel.Builder(TabProperties.ALL_KEYS_TAB_GRID)
+                         .with(TabProperties.THUMBNAIL_FETCHER, mFetcher)
+                         .with(TabProperties.IS_INCOGNITO, false)
+                         .with(TabProperties.IS_SELECTED, true)
+                         .with(TabProperties.GRID_CARD_WIDTH, INIT_WIDTH)
+                         .with(TabProperties.GRID_CARD_HEIGHT, INIT_HEIGHT)
+                         .build();
+        when(mViewGroup.fastFindViewById(R.id.tab_thumbnail)).thenReturn(mThumbnailView);
+        when(mViewGroup.getContext()).thenReturn(mContext);
+        when(mContext.getResources()).thenReturn(mResources);
+        // Mock tablet.
+        when(mResources.getInteger(org.chromium.ui.R.integer.min_screen_width_bucket)).thenReturn(3);
+
+        // mModel, view and bitmap all use the same initial values.
+        mLayoutParams = new LayoutParams(INIT_WIDTH, INIT_HEIGHT);
+        mBitmap = Bitmap.createBitmap(INIT_WIDTH, INIT_HEIGHT, Config.RGB_565);
+        when(mViewGroup.getLayoutParams()).thenReturn(mLayoutParams);
+    }
+
+    @After
+    public void tearDown() {
+        TabUiFeatureUtilities.setTabletGridTabSwitcherPolishEnabledForTesting(null);
+    }
+
+    @Test
+    public void bindClosableTabWithCardWidth_updateCardAndThumbnail() {
+        // Update width - 200.
+        final int updatedWidth = 200;
+        mModel.set(TabProperties.GRID_CARD_WIDTH, updatedWidth);
+        TabGridViewBinder.bindClosableTab(mModel, mViewGroup, TabProperties.GRID_CARD_WIDTH);
+
+        verify(mViewGroup).setMinimumWidth(updatedWidth);
+        verify(mThumbnailView).setColorThumbnailPlaceHolder(false, true);
+        assertThat(mLayoutParams.width, equalTo(updatedWidth));
+
+        verify(mFetcher).fetch(mCallbackCaptor.capture());
+        mCallbackCaptor.getValue().onResult(mBitmap);
+
+        verify(mThumbnailView).setScaleType(ScaleType.FIT_CENTER);
+        verify(mThumbnailView).setAdjustViewBounds(true);
+        verify(mThumbnailView).setImageBitmap(mBitmap);
+        verify(mThumbnailView).maybeAdjustThumbnailHeight();
+
+        verifyNoMoreInteractions(mThumbnailView);
+    }
+
+    @Test
+    public void bindClosableTabWithCardWidthWithPolishEnabled_updateCardAndThumbnail() {
+        TabUiFeatureUtilities.setTabletGridTabSwitcherPolishEnabledForTesting(true);
+        // Update width.
+        final int updatedWidth = 200;
+        mModel.set(TabProperties.GRID_CARD_WIDTH, updatedWidth);
+
+        // Call.
+        TabGridViewBinder.bindClosableTab(mModel, mViewGroup, TabProperties.GRID_CARD_WIDTH);
+
+        // Verify.
+        verify(mViewGroup).setMinimumWidth(updatedWidth);
+        verify(mThumbnailView).setColorThumbnailPlaceHolder(false, true);
+        assertThat(mLayoutParams.width, equalTo(updatedWidth));
+        verify(mFetcher).fetch(mCallbackCaptor.capture());
+
+        // Pass bitmap to callback and verify thumbnail updated with image resize.
+        mCallbackCaptor.getValue().onResult(mBitmap);
+
+        verify(mThumbnailView).setScaleType(ScaleType.MATRIX);
+        verify(mThumbnailView).setImageBitmap(mBitmap);
+        verify(mThumbnailView).maybeAdjustThumbnailHeight();
+        ArgumentCaptor<Matrix> matrixCaptor = ArgumentCaptor.forClass(Matrix.class);
+        verify(mThumbnailView).setImageMatrix(matrixCaptor.capture());
+        verifyNoMoreInteractions(mThumbnailView);
+
+        // Verify metrics scale + translate.
+        // Scale = updatedWidth / INIT_WIDTH = 200 / 100 = 2.
+        float expectedScale = 2.f;
+        // xTranslate = (destinationWidth - scaledWidth) /2 = (200 - (100*2))/2 = 0.
+        float expectedXTrans = 0.f;
+        assertImageMatrix(matrixCaptor, expectedScale, expectedXTrans);
+    }
+
+    @Test
+    public void bindClosableTabWithCardHeightWithPolishEnabled_updateCardAndThumbnail() {
+        TabUiFeatureUtilities.setTabletGridTabSwitcherPolishEnabledForTesting(true);
+        LayoutParams thumbnailParams = new LayoutParams(INIT_WIDTH, INIT_HEIGHT);
+        when(mThumbnailView.getLayoutParams()).thenReturn(thumbnailParams);
+
+        // Update height.
+        final int updatedHeight = 400;
+        mModel.set(TabProperties.GRID_CARD_HEIGHT, updatedHeight);
+
+        // Call.
+        TabGridViewBinder.bindClosableTab(mModel, mViewGroup, TabProperties.GRID_CARD_HEIGHT);
+
+        // Verify.
+        verify(mViewGroup).setMinimumHeight(updatedHeight);
+        verify(mThumbnailView).setColorThumbnailPlaceHolder(false, true);
+        assertThat(mLayoutParams.height, equalTo(updatedHeight));
+        assertThat(thumbnailParams.height, equalTo(LayoutParams.MATCH_PARENT));
+        verify(mFetcher).fetch(mCallbackCaptor.capture());
+
+        // Pass bitmap to callback and verify thumbnail updated with image resize.
+        mCallbackCaptor.getValue().onResult(mBitmap);
+
+        verify(mThumbnailView).setScaleType(ScaleType.MATRIX);
+        verify(mThumbnailView).setImageBitmap(mBitmap);
+        verify(mThumbnailView).maybeAdjustThumbnailHeight();
+        ArgumentCaptor<Matrix> matrixCaptor = ArgumentCaptor.forClass(Matrix.class);
+        verify(mThumbnailView).setImageMatrix(matrixCaptor.capture());
+        verify(mThumbnailView).getLayoutParams();
+        verifyNoMoreInteractions(mThumbnailView);
+
+        // Verify metrics scale + translate.
+        // Scale = updatedHeight / INIT_HEIGHT = 400 / 200 = 2.
+        float expectedScale = 2.f;
+        // xTranslate = (destinationWidth - scaledWidth) /2 = (100 - (100*2))/2 = -50.
+        float expectedXTrans = -50.f;
+        assertImageMatrix(matrixCaptor, expectedScale, expectedXTrans);
+    }
+
+    private void assertImageMatrix(
+            ArgumentCaptor<Matrix> matrixCaptor, float expectedScale, float expectedTrans) {
+        float[] matValues = new float[9];
+        matrixCaptor.getValue().getValues(matValues);
+        float scaleX = matValues[0];
+        float scaleY = matValues[4];
+        float transX = matValues[2];
+        float transY = matValues[5];
+        assertThat("Incorrect image matrix scaleX", scaleX, equalTo(expectedScale));
+        assertThat("Incorrect image matrix scaleY", scaleY, equalTo(expectedScale));
+        assertThat("Incorrect image matrix transY", transY, equalTo(0f));
+        assertThat("Incorrect image matrix transX", transX, equalTo(expectedTrans));
+    }
+}
diff --git a/chrome/android/features/tab_ui/tab_management_java_sources.gni b/chrome/android/features/tab_ui/tab_management_java_sources.gni
index d01c6ebb..f806dc1 100644
--- a/chrome/android/features/tab_ui/tab_management_java_sources.gni
+++ b/chrome/android/features/tab_ui/tab_management_java_sources.gni
@@ -66,6 +66,7 @@
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/PriceMessageServiceUnitTest.java",
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java",
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridItemTouchHelperCallbackUnitTest.java",
+  "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridViewBinderUnitTest.java",
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupTitleEditorUnitTest.java",
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiMediatorUnitTest.java",
   "//chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java",
diff --git a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrDelegate.java b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrDelegate.java
index 29f3680..e5d72a21 100644
--- a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrDelegate.java
+++ b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrDelegate.java
@@ -23,6 +23,7 @@
 import org.chromium.base.Log;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
+import org.chromium.components.browser_ui.widget.gesture.BackPressHandler;
 import org.chromium.components.page_info.VrHandler;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.display.DisplayAndroid;
@@ -34,7 +35,7 @@
 import java.util.Set;
 
 /** Delegate to call into VR. */
-public abstract class VrDelegate implements VrHandler {
+public abstract class VrDelegate implements VrHandler, BackPressHandler {
     private static final String TAG = "VrDelegate";
     private static final String VR_BOOT_SYSTEM_PROPERTY = "ro.boot.vr";
     private static final String SAMSUNG_GALAXY_PREFIX = "SM-";
diff --git a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrDelegateFallback.java b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrDelegateFallback.java
index 56928ea..cc2ef56556 100644
--- a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrDelegateFallback.java
+++ b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrDelegateFallback.java
@@ -16,6 +16,8 @@
 import org.chromium.base.Log;
 import org.chromium.base.compat.ApiHelperForN;
 import org.chromium.base.library_loader.LibraryLoader;
+import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
@@ -61,6 +63,14 @@
     }
 
     @Override
+    public void handleBackPress() {}
+
+    @Override
+    public ObservableSupplier<Boolean> getHandleBackPressChangedSupplier() {
+        return new ObservableSupplierImpl<>();
+    }
+
+    @Override
     public boolean enterVrIfNecessary() {
         return false;
     }
diff --git a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrDelegateImpl.java b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrDelegateImpl.java
index 1c4bf821..cddc6d1 100644
--- a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrDelegateImpl.java
+++ b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrDelegateImpl.java
@@ -8,6 +8,8 @@
 import android.content.Intent;
 import android.os.Bundle;
 
+import org.chromium.base.supplier.ObservableSupplier;
+
 /**
  * {@link VrDelegate} implementation if the VR module is available. Mostly forwards calls to {@link
  * VrShellDelegate}.
@@ -45,6 +47,16 @@
     }
 
     @Override
+    public void handleBackPress() {
+        onBackPressed();
+    }
+
+    @Override
+    public ObservableSupplier<Boolean> getHandleBackPressChangedSupplier() {
+        return VrShellDelegate.getVrModeEnabledSupplier();
+    }
+
+    @Override
     public boolean enterVrIfNecessary() {
         return VrShellDelegate.enterVrIfNecessary();
     }
diff --git a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java
index 531e1492..a497bb5d 100644
--- a/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java
+++ b/chrome/android/features/vr/java/src/org/chromium/chrome/browser/vr/VrShellDelegate.java
@@ -44,6 +44,8 @@
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.base.supplier.ObservableSupplier;
+import org.chromium.base.supplier.ObservableSupplierImpl;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.base.task.AsyncTask;
 import org.chromium.chrome.R;
@@ -127,6 +129,7 @@
     private static boolean sRegisteredDaydreamHook;
     private static boolean sRegisteredVrAssetsComponent;
     private static boolean sTestVrShellDelegateOnStartup;
+    private static ObservableSupplierImpl<Boolean> sVrModeEnabledSupplier;
 
     private ChromeActivity mActivity;
 
@@ -590,14 +593,17 @@
      */
     public static void setVrModeEnabled(Activity activity, boolean enabled) {
         ensureLifecycleObserverInitialized();
+        if (sVrModeEnabledSupplier == null) sVrModeEnabledSupplier = new ObservableSupplierImpl<>();
         if (enabled) {
             if (sVrModeEnabledActivitys.contains(activity)) return;
             AndroidCompat.setVrModeEnabled(activity, true);
             sVrModeEnabledActivitys.add(activity);
+            sVrModeEnabledSupplier.set(true);
         } else {
             if (!sVrModeEnabledActivitys.contains(activity)) return;
             AndroidCompat.setVrModeEnabled(activity, false);
             sVrModeEnabledActivitys.remove(activity);
+            sVrModeEnabledSupplier.set(false);
         }
     }
 
@@ -1040,6 +1046,11 @@
                 android.Manifest.permission.RECORD_AUDIO);
     }
 
+    public static ObservableSupplier<Boolean> getVrModeEnabledSupplier() {
+        if (sVrModeEnabledSupplier == null) sVrModeEnabledSupplier = new ObservableSupplierImpl<>();
+        return sVrModeEnabledSupplier;
+    }
+
     private boolean isWindowModeCorrectForVr() {
         int flags = mActivity.getWindow().getDecorView().getSystemUiVisibility();
         int orientation = mActivity.getResources().getConfiguration().orientation;
diff --git a/chrome/android/java/res/drawable/custom_tabs_handle_view_shape.xml b/chrome/android/java/res/drawable/custom_tabs_handle_view_shape.xml
index f4e8c7a..1905f625 100644
--- a/chrome/android/java/res/drawable/custom_tabs_handle_view_shape.xml
+++ b/chrome/android/java/res/drawable/custom_tabs_handle_view_shape.xml
@@ -8,8 +8,8 @@
     android:shape="rectangle">
     <corners
         android:radius="1dp"
-        android:topLeftRadius="@dimen/custom_tabs_top_corner_round_radius"
-        android:topRightRadius="@dimen/custom_tabs_top_corner_round_radius"
+        android:topLeftRadius="@dimen/custom_tabs_default_corner_radius"
+        android:topRightRadius="@dimen/custom_tabs_default_corner_radius"
         android:bottomLeftRadius="0dp"
         android:bottomRightRadius="0dp" />
     <solid
diff --git a/chrome/android/java/res/layout/custom_tabs_handle_view.xml b/chrome/android/java/res/layout/custom_tabs_handle_view.xml
index 925029e..502272a 100644
--- a/chrome/android/java/res/layout/custom_tabs_handle_view.xml
+++ b/chrome/android/java/res/layout/custom_tabs_handle_view.xml
@@ -3,12 +3,17 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
-<ImageView
+<FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="@dimen/custom_tabs_top_corner_round_radius"
-    android:importantForAccessibility="no"
-    android:src="@drawable/drag_handlebar"
-    android:tint="@macro/drag_handlebar_color"
-    android:scaleType="center"
-    android:background="@drawable/custom_tabs_handle_view_shape" />
+    android:layout_height="@dimen/custom_tabs_handle_height"
+    android:background="@drawable/custom_tabs_handle_view_shape">
+
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|center_horizontal"
+        android:importantForAccessibility="no"
+        android:src="@drawable/drag_handlebar"
+        android:tint="@macro/drag_handlebar_color" />
+</FrameLayout>
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index d71d6a66..41bc3e50 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -277,7 +277,8 @@
     <dimen name="custom_tabs_bottom_bar_max_height">120dp</dimen>
     <dimen name="custom_tabs_screenshot_height">300dp</dimen>
     <dimen name="custom_tabs_screenshot_width">190dp</dimen>
-    <dimen name="custom_tabs_top_corner_round_radius">16dp</dimen>
+    <dimen name="custom_tabs_handle_height">16dp</dimen>
+    <dimen name="custom_tabs_default_corner_radius">16dp</dimen>
 
     <!-- Account chooser dialog dimensions -->
     <dimen name="account_chooser_dialog_margin">24dp</dimen>
diff --git a/chrome/android/java/res_app/layout/main.xml b/chrome/android/java/res_app/layout/main.xml
index 758217a..9ff7c8e2 100644
--- a/chrome/android/java/res_app/layout/main.xml
+++ b/chrome/android/java/res_app/layout/main.xml
@@ -13,7 +13,7 @@
         android:id="@+id/custom_tabs_handle_view_stub"
         android:inflatedId="@+id/custom_tabs_handle_view"
         android:layout_width="match_parent"
-        android:layout_height="@dimen/custom_tabs_top_corner_round_radius"
+        android:layout_height="@dimen/custom_tabs_handle_height"
         android:layout="@layout/custom_tabs_handle_view"
         android:visibility="gone" />
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ActivityUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/ActivityUtils.java
index 9ed56cb..7f59e54 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ActivityUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ActivityUtils.java
@@ -5,9 +5,14 @@
 package org.chromium.chrome.browser;
 
 import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
 
 import androidx.annotation.Nullable;
 
+import org.chromium.base.ContextUtils;
 import org.chromium.base.SysUtils;
 import org.chromium.chrome.R;
 import org.chromium.content_public.browser.WebContents;
@@ -55,4 +60,28 @@
         if (activity == null) return true;
         return activity.isDestroyed() || activity.isFinishing();
     }
+
+    /**
+     * Specify the proper non-.Main-aliased Chrome Activity for the given component.
+     *
+     * @param intent The intent to set the component for.
+     * @param component The client generated component to be validated.
+     */
+    public static void setNonAliasedComponentForMainBrowsingActivity(
+            Intent intent, ComponentName component) {
+        assert component != null;
+        Context appContext = ContextUtils.getApplicationContext();
+        if (!TextUtils.equals(component.getPackageName(), appContext.getPackageName())) {
+            return;
+        }
+        if (component.getClassName() != null
+                && TextUtils.equals(component.getClassName(),
+                        ChromeTabbedActivity.MAIN_LAUNCHER_ACTIVITY_NAME)) {
+            // Keep in sync with the activities that the .Main alias points to in
+            // AndroidManifest.xml.
+            intent.setClass(appContext, ChromeTabbedActivity.class);
+        } else {
+            intent.setComponent(component);
+        }
+    }
 }
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 6ce5af3..25456086 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser;
 
 import android.app.ActivityManager;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -36,7 +35,6 @@
 
 import org.chromium.base.CallbackController;
 import org.chromium.base.CommandLine;
-import org.chromium.base.ContextUtils;
 import org.chromium.base.IntentUtils;
 import org.chromium.base.Log;
 import org.chromium.base.MemoryPressureListener;
@@ -427,29 +425,6 @@
     }
 
     /**
-     * Specify the proper non-.Main-aliased Chrome Activity for the given component.
-     *
-     * @param intent The intent to set the component for.
-     * @param component The client generated component to be validated.
-     */
-    public static void setNonAliasedComponent(Intent intent, ComponentName component) {
-        assert component != null;
-        Context appContext = ContextUtils.getApplicationContext();
-        if (!TextUtils.equals(component.getPackageName(), appContext.getPackageName())) {
-            return;
-        }
-        if (component.getClassName() != null
-                && TextUtils.equals(component.getClassName(),
-                        ChromeTabbedActivity.MAIN_LAUNCHER_ACTIVITY_NAME)) {
-            // Keep in sync with the activities that the .Main alias points to in
-            // AndroidManifest.xml.
-            intent.setClass(appContext, ChromeTabbedActivity.class);
-        } else {
-            intent.setComponent(component);
-        }
-    }
-
-    /**
      * Constructs a ChromeTabbedActivity.
      */
     public ChromeTabbedActivity() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/DEPS b/chrome/android/java/src/org/chromium/chrome/browser/DEPS
index c90cb9a4..c7655683 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/DEPS
+++ b/chrome/android/java/src/org/chromium/chrome/browser/DEPS
@@ -90,9 +90,6 @@
   "VideoPlayerActivity\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java"
   ],
-  "BookmarkUtils\.java": [
-    "+chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java"
-  ],
   "ContextualSearchQuickActionControl\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java"
   ],
@@ -102,9 +99,6 @@
   "FeatureNotificationGuideDelegate\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java"
   ],
-  "HistoryContentManager\.java": [
-    "+chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java"
-  ],
   "IncognitoNotificationServiceImpl\.java": [
     "+chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java"
   ],
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
index 16a2e731..b190c17 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/ChromeActivity.java
@@ -2312,11 +2312,9 @@
         if (!BackPressManager.isEnabled()) {
             // TODO(crbug.com/1279941): should this stop propagating the event?
             TextBubble.dismissBubbles();
-        }
 
-        if (VrModuleProvider.getDelegate().onBackPressed()) return;
+            if (VrModuleProvider.getDelegate().onBackPressed()) return;
 
-        if (!BackPressManager.isEnabled()) {
             ArDelegate arDelegate = ArDelegateProvider.getDelegate();
             if (arDelegate != null && arDelegate.onBackPressed()) return;
 
@@ -2358,6 +2356,11 @@
             // TODO(crbug.com/1279941): consider move to RootUiCoordinator.
             mTextBubbleBackPressHandler = new TextBubbleBackPressHandler();
             mBackPressManager.addHandler(mTextBubbleBackPressHandler, Type.TEXT_BUBBLE);
+            mBackPressManager.addHandler(VrModuleProvider.getDelegate(), Type.VR_DELEGATE);
+
+            if (ArDelegateProvider.getDelegate() != null) {
+                mBackPressManager.addHandler(ArDelegateProvider.getDelegate(), Type.AR_DELEGATE);
+            }
 
             mLayoutManagerSupplier.addObserver((layoutManager) -> {
                 assert !mBackPressManager.has(Type.LAYOUT_MANAGER)
@@ -2365,10 +2368,6 @@
                 mBackPressManager.addHandler(layoutManager, Type.LAYOUT_MANAGER);
             });
 
-            if (ArDelegateProvider.getDelegate() != null) {
-                mBackPressManager.addHandler(ArDelegateProvider.getDelegate(), Type.AR_DELEGATE);
-            }
-
             mBrowserControlsManagerSupplier.addObserver((controlManager) -> {
                 assert !mBackPressManager.has(Type.FULLSCREEN)
                     : "BrowserControlManager should be set at most once";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
index 1306f75..39a30de4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkUtils.java
@@ -30,7 +30,7 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.ChromeTabbedActivity;
+import org.chromium.chrome.browser.ActivityUtils;
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.IntentHandler.IncognitoCCTCallerId;
 import org.chromium.chrome.browser.LaunchIntentDispatcher;
@@ -535,7 +535,7 @@
      *
      * @param url Url to open.
      * @param componentName Name of the component opening the URL. If null, {@link
-     *         ChromeTabbedActivity} is used.
+     *          ChromeLauncherActivity} is used.
      * @param launchType If not null, url is opened in a new tab with the specified {@link
      *         TabLaunchType}.
      */
@@ -559,7 +559,7 @@
         }
 
         if (componentName != null) {
-            ChromeTabbedActivity.setNonAliasedComponent(intent, componentName);
+            ActivityUtils.setNonAliasedComponentForMainBrowsingActivity(intent, componentName);
         } else {
             // If the bookmark manager is shown in a tab on a phone (rather than in a separate
             // activity) the component name may be null. Send the intent through
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFieldTrial.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFieldTrial.java
index eb917d7..fb60dce2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFieldTrial.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFieldTrial.java
@@ -131,9 +131,15 @@
         int IS_NOT_AN_ENTITY_SUPPRESSION_ENABLED = 10;
         /** Whether triggering is suppressed due to lack of engagement with the feature. */
         int IS_ENGAGEMENT_SUPPRESSION_ENABLED = 11;
-        /** Whether triggering is suppressed for a tap that has a short element run-length. */
+        /**
+         * @deprecated
+         * Whether triggering is suppressed for a tap that has a short element run-length.
+         */
         int IS_SHORT_TEXT_RUN_SUPPRESSION_ENABLED = 12;
-        /** Whether triggering is suppressed for a tap on small-looking text. */
+        /**
+         * @deprecated
+         * Whether triggering is suppressed for a tap on small-looking text.
+         */
         int IS_SMALL_TEXT_SUPPRESSION_ENABLED = 13;
         /**
          * Whether to disable auto-promotion of clicks in the AMP carousel into a
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
index 25ca47c..f3de8ff 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManager.java
@@ -1559,8 +1559,8 @@
     }
 
     /** Shows the Unhandled Tap UI.  Called by {@link ContextualSearchTabHelper}. */
-    void onShowUnhandledTapUIIfNeeded(int x, int y, int fontSizeDips, int textRunLength) {
-        mSelectionController.handleShowUnhandledTapUIIfNeeded(x, y, fontSizeDips, textRunLength);
+    void onShowUnhandledTapUIIfNeeded(int x, int y) {
+        mSelectionController.handleShowUnhandledTapUIIfNeeded(x, y);
     }
 
     // ============================================================================================
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
index 6e3ffd1..8618be6f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchSelectionController.java
@@ -96,10 +96,6 @@
     private float mX;
     private float mY;
 
-    // Additional tap info from Mojo.
-    int mFontSizeDips;
-    int mTextRunLength;
-
     // The time of the most last scroll activity, or 0 if none.
     private long mLastScrollTimeNs;
 
@@ -403,8 +399,6 @@
         mLastScrollTimeNs = 0;
         mTapTimeNanoseconds = 0;
         mDidExpandSelection = false;
-        mFontSizeDips = 0;
-        mTextRunLength = 0;
     }
 
     /**
@@ -432,10 +426,8 @@
      * Handles an unhandled tap gesture.
      * @param x The x coordinate in px.
      * @param y The y coordinate in px.
-     * @param fontSizeDips The font size in DPs.
-     * @param textRunLength The run-length of the text of the tapped element.
      */
-    void handleShowUnhandledTapUIIfNeeded(int x, int y, int fontSizeDips, int textRunLength) {
+    void handleShowUnhandledTapUIIfNeeded(int x, int y) {
         mWasTapGestureDetected = false;
         // TODO(donnd): refactor to avoid needing a new handler API method as suggested by Pedro.
         if (mSelectionType != SelectionType.LONG_PRESS && !mAreSelectionHandlesShown
@@ -445,8 +437,6 @@
             mSelectionType = SelectionType.TAP;
             mX = x;
             mY = y;
-            mFontSizeDips = fontSizeDips;
-            mTextRunLength = textRunLength;
             mHandler.handleValidTap();
         } else {
             // Long press, or long-press selection handles shown; reset last tap state.
@@ -469,9 +459,8 @@
         int x = (int) mX;
         int y = (int) mY;
 
-        TapSuppressionHeuristics tapHeuristics =
-                new TapSuppressionHeuristics(this, mLastTapState, x, y, contextualSearchContext,
-                        mWasSelectionEmptyBeforeTap, mFontSizeDips, mTextRunLength);
+        TapSuppressionHeuristics tapHeuristics = new TapSuppressionHeuristics(
+                this, mLastTapState, x, y, contextualSearchContext, mWasSelectionEmptyBeforeTap);
         // TODO(donnd): Move to be called when the panel closes to work with states that change.
         tapHeuristics.logConditionState();
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java
index 8770763..7172a26e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchTabHelper.java
@@ -380,11 +380,10 @@
      * coordinates.
      */
     @CalledByNative
-    void onShowUnhandledTapUIIfNeeded(int x, int y, int fontSizeDips, int textRunLength) {
+    void onShowUnhandledTapUIIfNeeded(int x, int y) {
         // Only notify the manager if we currently have a valid listener.
         if (mGestureStateListener != null && getContextualSearchManager(mTab) != null) {
-            getContextualSearchManager(mTab).onShowUnhandledTapUIIfNeeded(
-                    x, y, fontSizeDips, textRunLength);
+            getContextualSearchManager(mTab).onShowUnhandledTapUIIfNeeded(x, y);
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ShortTextRunSuppression.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ShortTextRunSuppression.java
deleted file mode 100644
index 122e37e7..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ShortTextRunSuppression.java
+++ /dev/null
@@ -1,77 +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.
-
-package org.chromium.chrome.browser.contextualsearch;
-
-import org.chromium.chrome.browser.contextualsearch.ContextualSearchFieldTrial.ContextualSearchSwitch;
-
-/**
- * Implements a {@link ContextualSearchHeuristic} for a Tap on a short section of text so that can
- * be fed into the tap suppression ML model. Short phrases may be less informative.
- * Computes the ratio of the tapped word to the entire run of text in the element (which includes
- * style changes).  This means that short navigational elements will have a high score, and a tap
- * in a longer paragraph will have a smaller score.
- */
-class ShortTextRunSuppression extends ContextualSearchHeuristic {
-    // Threshold for a "large" ratio of word to element.
-    private static final int LARGE_WORD_ELEMENT_RATIO = 3;
-
-    // Value to use when the word or element length is unavailable.
-    private static final int DEFAULT_WORD_ELEMENT_RATIO = 0;
-
-    private final boolean mIsSuppressionEnabled;
-    private final boolean mIsConditionSatisfied;
-    private final int mWordElementRatioDecile;
-
-    /**
-     * Constructs a heuristic to determine if the current Tap is in a short text run.
-     * @param contextualSearchContext The current {@link ContextualSearchContext}, so we can get
-     *        the length of the word tapped.
-     * @param elementRunLength The length of the text in the element tapped, in characters.
-     */
-    ShortTextRunSuppression(ContextualSearchContext contextualSearchContext, int elementRunLength) {
-        mIsSuppressionEnabled = ContextualSearchFieldTrial.getSwitch(
-                ContextualSearchSwitch.IS_SHORT_TEXT_RUN_SUPPRESSION_ENABLED);
-        mWordElementRatioDecile = wordElementRatio(contextualSearchContext, elementRunLength);
-        mIsConditionSatisfied = mWordElementRatioDecile >= LARGE_WORD_ELEMENT_RATIO;
-    }
-
-    @Override
-    protected boolean isConditionSatisfiedAndEnabled() {
-        return mIsSuppressionEnabled && mIsConditionSatisfied;
-    }
-
-    @Override
-    protected boolean shouldAggregateLogForTapSuppression() {
-        return true;
-    }
-
-    @Override
-    protected boolean isConditionSatisfiedForAggregateLogging() {
-        return mIsConditionSatisfied;
-    }
-
-    @Override
-    protected void logRankerTapSuppression(ContextualSearchInteractionRecorder logger) {
-        logger.logFeature(ContextualSearchInteractionRecorder.Feature.PORTION_OF_ELEMENT,
-                mWordElementRatioDecile);
-    }
-
-    /**
-     * Returns the ratio of word-length to the element-length scaled to a range from 1 to 10.
-     * @param contextualSearchContext The {@link ContextualSearchContext} that knows about the word
-     *        tapped.
-     * @param elementRunLength The length of the element containing the tapped word, in characters.
-     * @return A value in the range 0-10 with 0 meaning no ratio could be computed and larger values
-     *         reflecting higher word/element ratios.
-     */
-    private int wordElementRatio(
-            ContextualSearchContext contextualSearchContext, int elementRunLength) {
-        // If setup failed, don't suppress.
-        String wordTapped = contextualSearchContext.getWordTapped();
-        if (wordTapped == null || elementRunLength == 0) return DEFAULT_WORD_ELEMENT_RATIO;
-
-        return clamp((int) ((float) wordTapped.length() / elementRunLength * 10));
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/SmallTextSuppression.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/SmallTextSuppression.java
deleted file mode 100644
index c28a6be..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/SmallTextSuppression.java
+++ /dev/null
@@ -1,75 +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.
-
-package org.chromium.chrome.browser.contextualsearch;
-
-import org.chromium.chrome.browser.contextualsearch.ContextualSearchFieldTrial.ContextualSearchSwitch;
-
-/**
- * Implements a {@link ContextualSearchHeuristic} suggesting that a Tap on a relatively small font
- * should be ignored.
- */
-class SmallTextSuppression extends ContextualSearchHeuristic {
-    private static final int SMALL_SIZE_THRESHOLD_DIPS = 15;
-    private static final int DEFAULT_DECILIZED_VALUE = 0;
-    private static final float DECILIZE_SCALE_FACTOR = 0.5f;
-    private static final int DECILIZE_MINIMUM_INPUT = 8;
-
-    private final boolean mIsSuppressionEnabled;
-    private final boolean mIsConditionSatisfied;
-    private final int mDecilizedFontSize;
-
-    /**
-     * Constructs a heuristic to determine if the current Tap is on text with a small height.
-     * @param fontSizeDips The size of the font from Blink in dips.
-     */
-    SmallTextSuppression(int fontSizeDips) {
-        mIsSuppressionEnabled = ContextualSearchFieldTrial.getSwitch(
-                ContextualSearchSwitch.IS_SMALL_TEXT_SUPPRESSION_ENABLED);
-        mIsConditionSatisfied = isConditionSatisfied(fontSizeDips);
-        mDecilizedFontSize = decilizedFontSize(fontSizeDips);
-    }
-
-    @Override
-    protected boolean isConditionSatisfiedAndEnabled() {
-        return mIsSuppressionEnabled && mIsConditionSatisfied;
-    }
-
-    @Override
-    protected boolean shouldAggregateLogForTapSuppression() {
-        return true;
-    }
-
-    @Override
-    protected boolean isConditionSatisfiedForAggregateLogging() {
-        return mIsConditionSatisfied;
-    }
-
-    @Override
-    protected void logRankerTapSuppression(ContextualSearchInteractionRecorder logger) {
-        logger.logFeature(
-                ContextualSearchInteractionRecorder.Feature.FONT_SIZE, mDecilizedFontSize);
-    }
-
-    /**
-     * Whether the conditions are satisfied to suppress the tap based on the given params:
-     * @param fontSizeDips The size of the font in DIPs.
-     * @return whether the conditions are satisfied to suppress (but might not actually do so).
-     */
-    private boolean isConditionSatisfied(int fontSizeDips) {
-        return fontSizeDips != 0 && fontSizeDips < SMALL_SIZE_THRESHOLD_DIPS;
-    }
-
-    /**
-     * Converts the input value into a "decile", an int in the range 0-10 inclusive.
-     * @param value Any value to be scaled.  Very large values will pin at 10. Only an input of
-     *        0 will return 0.
-     * @return A value that's 0 if the input is zero and least 1 and at most 10 otherwise.
-     */
-    private int decilizedFontSize(int value) {
-        if (value == 0) return DEFAULT_DECILIZED_VALUE;
-
-        return clamp(Math.round(DECILIZE_SCALE_FACTOR * (value - DECILIZE_MINIMUM_INPUT)));
-    }
-}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java
index 46a9576..f5902b31 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/TapSuppressionHeuristics.java
@@ -19,21 +19,16 @@
      * @param y The y position of the Tap.
      * @param contextualSearchContext The {@link ContextualSearchContext} of this tap.
      * @param wasSelectionEmptyBeforeTap Whether the selection was empty before this tap.
-     * @param fontSizeDips The font size from Blink in dips.
-     * @param elementRunLength The length of the text in the element tapped, in characters.
      */
     TapSuppressionHeuristics(ContextualSearchSelectionController selectionController,
             @Nullable ContextualSearchTapState previousTapState, int x, int y,
-            ContextualSearchContext contextualSearchContext, boolean wasSelectionEmptyBeforeTap,
-            int fontSizeDips, int elementRunLength) {
+            ContextualSearchContext contextualSearchContext, boolean wasSelectionEmptyBeforeTap) {
         super();
         mHeuristics.add(new EngagementSuppression());
         mHeuristics.add(new RecentScrollTapSuppression(selectionController));
         mHeuristics.add(new TapFarFromPreviousSuppression(
                 selectionController, previousTapState, x, y, wasSelectionEmptyBeforeTap));
         mHeuristics.add(new ContextualSearchEntityHeuristic(contextualSearchContext));
-        mHeuristics.add(new ShortTextRunSuppression(contextualSearchContext, elementRunLength));
-        mHeuristics.add(new SmallTextSuppression(fontSizeDips));
         // Quick Answer that appears in the Caption via the JS API.
         QuickAnswersHeuristic quickAnswersHeuristic = new QuickAnswersHeuristic();
         setQuickAnswersHeuristic(quickAnswersHeuristic);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
index 1361539..070a3e2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabRootUiCoordinator.java
@@ -158,7 +158,8 @@
 
         CustomTabToolbar toolbar = mActivity.findViewById(R.id.toolbar);
         View coordinator = mActivity.findViewById(R.id.coordinator);
-        mCustomTabHeightStrategy.onToolbarInitialized(coordinator, toolbar);
+        mCustomTabHeightStrategy.onToolbarInitialized(
+                coordinator, toolbar, mIntentDataProvider.get().getPartialTabToolbarCornerRadius());
         toolbar.setCloseButtonPosition(mIntentDataProvider.get().getCloseButtonPosition());
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDownloadObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDownloadObserver.java
index 6d0c162..ec7be87 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDownloadObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabDownloadObserver.java
@@ -8,6 +8,7 @@
 
 import org.chromium.chrome.browser.customtabs.content.TabObserverRegistrar;
 import org.chromium.chrome.browser.dependency_injection.ActivityScope;
+import org.chromium.chrome.browser.download.DownloadManagerService;
 import org.chromium.chrome.browser.download.interstitial.DownloadInterstitialCoordinator;
 import org.chromium.chrome.browser.download.interstitial.DownloadInterstitialCoordinatorFactory;
 import org.chromium.chrome.browser.download.interstitial.NewDownloadTab;
@@ -55,6 +56,10 @@
             DownloadInterstitialCoordinator coordinator =
                     DownloadInterstitialCoordinatorFactory.create(tab::getContext,
                             tab.getOriginalUrl().getSpec(), tab.getWindowAndroid());
+            // Register the download's original URL to prevent messages UI showing in interstitial.
+            DownloadManagerService.getDownloadManagerService()
+                    .getMessageUiController(/* otrProfileId */ null)
+                    .addDownloadInterstitialSource(tab.getOriginalUrl().getSpec());
             NewDownloadTab.from(tab, coordinator, mActivity).show();
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabHeightStrategy.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabHeightStrategy.java
index 312c060..d342688 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabHeightStrategy.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabHeightStrategy.java
@@ -40,9 +40,13 @@
     }
 
     /**
-     * Provide this class with the {@link CustomTabToolbar} so it can set up the strategy, and the
-     * coordinator view to insert the UI "handle" for users to interact with to resize the Custom
-     * Tab.
+     * Provide this class with the required views and values so it can set up the strategy.
+     *
+     * @param coordinatorView Coordinator view to insert the UI handle for the users to resize the
+     *                        custom tab.
+     * @param toolbar The {@link CustomTabToolbar} to set up the strategy.
+     * @param toolbarCornerRadius The custom tab corner radius in pixels.
      */
-    public void onToolbarInitialized(View coordinatorView, CustomTabToolbar toolbar) {}
+    public void onToolbarInitialized(
+            View coordinatorView, CustomTabToolbar toolbar, @Px int toolbarCornerRadius) {}
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
index f0b445a..700cf6a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java
@@ -170,6 +170,13 @@
             "androidx.browser.customtabs.extra.INITIAL_ACTIVITY_HEIGHT_IN_PIXEL";
 
     /**
+     * Extra that, if set, makes the toolbar's top corner radii to be x pixels. This will only have
+     * effect if the custom tab is behaving as a bottom sheet. Currently, this is capped at 16dp.
+     */
+    public static final String EXTRA_TOOLBAR_CORNER_RADIUS_IN_PIXEL =
+            "androidx.browser.customtabs.extra.TOOLBAR_CORNER_RADIUS_IN_PIXEL";
+
+    /**
      * Extra that specifies the position of the close button on the toolbar. Default is
      * {@link #CLOSE_BUTTON_POSITION_DEFAULT}.
      */
@@ -241,6 +248,7 @@
     private final ColorProvider mColorProvider;
 
     private final @Px int mInitialActivityHeight;
+    private final @Px int mPartialTabToolbarCornerRadius;
 
     /**
      * Add extras to customize menu items for opening Reader Mode UI custom tab from Chrome.
@@ -369,6 +377,14 @@
 
         mInitialActivityHeight =
                 IntentUtils.safeGetIntExtra(intent, EXTRA_INITIAL_ACTIVITY_HEIGHT_IN_PIXEL, 0);
+        int defaultToolbarCornerRadius = context.getResources().getDimensionPixelSize(
+                R.dimen.custom_tabs_default_corner_radius);
+        if (CachedFeatureFlags.isEnabled(ChromeFeatureList.CCT_TOOLBAR_CUSTOMIZATIONS)) {
+            mPartialTabToolbarCornerRadius = IntentUtils.safeGetIntExtra(
+                    intent, EXTRA_TOOLBAR_CORNER_RADIUS_IN_PIXEL, defaultToolbarCornerRadius);
+        } else {
+            mPartialTabToolbarCornerRadius = defaultToolbarCornerRadius;
+        }
     }
 
     private void updateExtraMenuItems(List<Bundle> menuItems) {
@@ -866,4 +882,9 @@
         return IntentUtils.safeGetIntExtra(
                 mIntent, EXTRA_CLOSE_BUTTON_POSITION, CLOSE_BUTTON_POSITION_DEFAULT);
     }
+
+    @Override
+    public int getPartialTabToolbarCornerRadius() {
+        return mPartialTabToolbarCornerRadius;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/PartialCustomTabHeightStrategy.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/PartialCustomTabHeightStrategy.java
index 24b8777..9f0329e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/PartialCustomTabHeightStrategy.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/PartialCustomTabHeightStrategy.java
@@ -24,7 +24,6 @@
 import android.view.ViewStub;
 import android.view.WindowInsets;
 import android.view.WindowManager;
-import android.widget.ImageView;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.Px;
@@ -271,8 +270,9 @@
     }
 
     @Override
-    public void onToolbarInitialized(View coordinatorView, CustomTabToolbar toolbar) {
-        roundCorners(coordinatorView, toolbar);
+    public void onToolbarInitialized(
+            View coordinatorView, CustomTabToolbar toolbar, @Px int toolbarCornerRadius) {
+        roundCorners(coordinatorView, toolbar, toolbarCornerRadius);
 
         toolbar.setHandleStrategy(new PartialCustomTabHandleStrategy(mActivity));
     }
@@ -299,14 +299,20 @@
         updateWindowHeight(value);
     }
 
-    private void roundCorners(View coordinator, CustomTabToolbar toolbar) {
-        final float radius = mActivity.getResources().getDimensionPixelSize(
-                R.dimen.custom_tabs_top_corner_round_radius);
+    private void roundCorners(
+            View coordinator, CustomTabToolbar toolbar, @Px int toolbarCornerRadius) {
+        final float handlePortionHeight =
+                mActivity.getResources().getDimensionPixelSize(R.dimen.custom_tabs_handle_height);
 
         // Inflate the handle View.
         ViewStub handleViewStub = mActivity.findViewById(R.id.custom_tabs_handle_view_stub);
         handleViewStub.inflate();
-        ImageView handleView = mActivity.findViewById(R.id.custom_tabs_handle_view);
+        View handleView = mActivity.findViewById(R.id.custom_tabs_handle_view);
+
+        GradientDrawable background = (GradientDrawable) handleView.getBackground();
+        background.mutate();
+        background.setCornerRadii(new float[] {toolbarCornerRadius, toolbarCornerRadius,
+                toolbarCornerRadius, toolbarCornerRadius, 0, 0, 0, 0});
 
         // Pass the handle View to CustomTabToolbar for background color management.
         toolbar.setHandleView(handleView);
@@ -314,11 +320,10 @@
         // Make enough room for the handle View.
         ViewGroup.MarginLayoutParams mlp =
                 (ViewGroup.MarginLayoutParams) coordinator.getLayoutParams();
-        mlp.setMargins(0, Math.round(radius), 0, 0);
+        mlp.setMargins(0, Math.round(handlePortionHeight), 0, 0);
         coordinator.requestLayout();
 
-        mActivity.getWindow().setBackgroundDrawableResource(
-                R.drawable.custom_tabs_handle_view_shape);
+        mActivity.getWindow().setBackgroundDrawable(background);
     }
 
     private void initializeHeight() {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
index a104de9..425fd0a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/features/toolbar/CustomTabToolbar.java
@@ -102,7 +102,7 @@
     private ImageButton mCloseButton;
     private MenuButton mMenuButton;
     // This View will be non-null only for bottom sheet custom tabs.
-    private ImageView mHandleView;
+    private View mHandleView;
 
     // Color scheme and tint that will be applied to icons and text.
     private @BrandedColorScheme int mBrandedColorScheme;
@@ -568,7 +568,7 @@
         return mLocationBar;
     }
 
-    public void setHandleView(ImageView view) {
+    public void setHandleView(View view) {
         mHandleView = view;
         setHandleViewBackgroundColor(getBackground().getColor());
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
index c7d70192..81d5a68 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadManagerService.java
@@ -92,7 +92,6 @@
     public static final long UNKNOWN_BYTES_RECEIVED = -1;
 
     private static final Set<String> sFirstSeenDownloadIds = new HashSet<String>();
-    private static final Set<String> sInProgressCCTDownloadIds = new HashSet<String>();
 
     private static DownloadManagerService sDownloadManagerService;
     private static boolean sIsNetworkListenerDisabled;
@@ -208,23 +207,6 @@
     }
 
     /**
-     * Methods to modify the set of downloads currently being downloaded through the new CCT
-     * downloads UI.
-     * @param guid GUID of the offline item.
-     */
-    public static void addCCTDownload(String guid) {
-        sInProgressCCTDownloadIds.add(guid);
-    }
-
-    public static boolean inProgressCCTDownloadsContains(String guid) {
-        return sInProgressCCTDownloadIds.contains(guid);
-    }
-
-    public static void removeCCTDownload(String guid) {
-        sInProgressCCTDownloadIds.remove(guid);
-    }
-
-    /**
      * For tests only: sets the DownloadManagerService.
      * @param service An instance of DownloadManagerService.
      * @return Null or a currently set instance of DownloadManagerService.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
index 8e60c1a..0445205 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/DownloadUtils.java
@@ -479,12 +479,6 @@
     public static void openDownload(String filePath, String mimeType, String downloadGuid,
             OTRProfileID otrProfileID, String originalUrl, String referer,
             @DownloadOpenSource int source) {
-        if (ChromeFeatureList.isEnabled(ChromeFeatureList.CCT_NEW_DOWNLOAD_TAB)
-                && source == DownloadOpenSource.UNKNOWN
-                && DownloadManagerService.inProgressCCTDownloadsContains(downloadGuid)) {
-            DownloadManagerService.removeCCTDownload(downloadGuid);
-            return;
-        }
         // Mapping generic MIME type to android openable type based on URL and file extension.
         String newMimeType = MimeUtils.remapGenericMimeType(mimeType, originalUrl, filePath);
         Activity activity = ApplicationStatus.getLastTrackedFocusedActivity();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryContentManager.java b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryContentManager.java
index fdcd3846..5445594a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryContentManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/history/HistoryContentManager.java
@@ -26,7 +26,7 @@
 import org.chromium.base.IntentUtils;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.ChromeTabbedActivity;
+import org.chromium.chrome.browser.ActivityUtils;
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
 import org.chromium.chrome.browser.preferences.Pref;
@@ -377,7 +377,7 @@
             component = mActivity.getComponentName();
         }
         if (component != null) {
-            ChromeTabbedActivity.setNonAliasedComponent(viewIntent, component);
+            ActivityUtils.setNonAliasedComponentForMainBrowsingActivity(viewIntent, component);
         } else {
             viewIntent.setClass(mActivity, ChromeLauncherActivity.class);
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/share/crow/CrowButtonDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/share/crow/CrowButtonDelegateImpl.java
index 851f423..48bd26e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/share/crow/CrowButtonDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/share/crow/CrowButtonDelegateImpl.java
@@ -42,9 +42,10 @@
     }
 
     @Override
-    public void launchCustomTab(Activity currentActivity, GURL pageUrl, GURL canonicalUrl) {
+    public void launchCustomTab(
+            Activity currentActivity, GURL pageUrl, GURL canonicalUrl, boolean isFollowing) {
         String customTabUrl = buildServerUrl(new GURL(getServerUrl()), pageUrl, canonicalUrl,
-                getPublicationId(pageUrl), areMetricsEnabled());
+                getPublicationId(pageUrl), areMetricsEnabled(), isFollowing);
 
         CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
         builder.setShowTitle(true);
@@ -124,7 +125,7 @@
 
     @VisibleForTesting
     public String buildServerUrl(GURL serverUrl, GURL pageUrl, GURL canonicalPageUrl,
-            String publicationId, boolean allowMetrics) {
+            String publicationId, boolean allowMetrics, boolean isFollowing) {
         String serverSpec = serverUrl.getSpec();
         if (serverSpec.isEmpty()) return "";
         Uri.Builder builder = Uri.parse(serverSpec).buildUpon();
@@ -133,6 +134,9 @@
         builder.appendQueryParameter("relCanonUrl", canonicalPageUrl.getSpec());
         builder.appendQueryParameter("publicationId", publicationId);
         builder.appendQueryParameter("metrics", allowMetrics ? "true" : "false");
+        if (isFollowing) {
+            builder.appendQueryParameter("following", "true");
+        }
         return builder.build().toString();
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManagerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManagerImpl.java
index 7b62316a..fe23793 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManagerImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManagerImpl.java
@@ -26,10 +26,14 @@
 import org.chromium.chrome.browser.browsing_data.BrowsingDataBridge;
 import org.chromium.chrome.browser.browsing_data.BrowsingDataType;
 import org.chromium.chrome.browser.browsing_data.TimePeriod;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.signin.services.SigninManager;
 import org.chromium.chrome.browser.signin.services.SigninPreferencesManager;
 import org.chromium.chrome.browser.sync.SyncService;
 import org.chromium.components.externalauth.ExternalAuthUtils;
+import org.chromium.components.signin.AccountManagerFacade;
+import org.chromium.components.signin.AccountManagerFacadeProvider;
+import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.base.CoreAccountId;
 import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.signin.identitymanager.AccountInfoServiceProvider;
@@ -584,12 +588,33 @@
 
     @Override
     public void onAccountsCookieDeletedByUserAction() {
-        if (mIdentityManager.getPrimaryAccountInfo(ConsentLevel.SIGNIN) != null
-                && mIdentityManager.getPrimaryAccountInfo(ConsentLevel.SYNC) == null) {
-            // Clearing account cookies should trigger sign-out only when user is signed in
-            // without sync.
-            // If the user consented for sync, then the user should not be signed out,
-            // since account cookies will be rebuilt by the account reconcilor.
+        // Clearing account cookies should trigger sign-out only when user is
+        // signed in without sync. If the user consented for sync, then the user
+        // should not be signed out, since account cookies will be rebuilt by
+        // the account reconcilor.
+        if (mIdentityManager.getPrimaryAccountInfo(ConsentLevel.SIGNIN) == null
+                || mIdentityManager.getPrimaryAccountInfo(ConsentLevel.SYNC) != null) {
+            return;
+        }
+
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.ALLOW_SYNC_OFF_FOR_CHILD_ACCOUNTS)) {
+            // Child users are not allowed to sign out, so we check the child status in order to
+            // skip any signout step.  This is guarded behind a flag for now, in case changing the
+            // timings by adding an async step causes any issues for non-child accounts.
+            //
+            // TODO(crbug.com/1324567): move this logic within signOut() rather than relying on
+            // callers like SigninManager implemting this logic.
+            final AccountManagerFacade accountManagerFacade =
+                    AccountManagerFacadeProvider.getInstance();
+            accountManagerFacade.getAccounts().then(accounts -> {
+                AccountUtils.checkChildAccountStatus(
+                        accountManagerFacade, accounts, (isChild, childAccount) -> {
+                            if (!isChild) {
+                                signOut(SignoutReason.USER_DELETED_ACCOUNT_COOKIES);
+                            }
+                        });
+            });
+        } else {
             signOut(SignoutReason.USER_DELETED_ACCOUNT_COOKIES);
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/TabDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/TabDelegate.java
index 08510bb..9da9d82 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/TabDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/document/TabDelegate.java
@@ -16,6 +16,7 @@
 
 import org.chromium.base.ContextUtils;
 import org.chromium.base.IntentUtils;
+import org.chromium.chrome.browser.ActivityUtils;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.ServiceTabLauncher;
@@ -168,7 +169,7 @@
         if (componentName == null) {
             intent.setClass(ContextUtils.getApplicationContext(), ChromeLauncherActivity.class);
         } else {
-            ChromeTabbedActivity.setNonAliasedComponent(intent, componentName);
+            ActivityUtils.setNonAliasedComponentForMainBrowsingActivity(intent, componentName);
         }
         IntentHandler.setIntentExtraHeaders(
                 asyncParams.getLoadUrlParams().getExtraHeaders(), intent);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInstrumentationBase.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInstrumentationBase.java
index 60b4c079..eb76ab8 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInstrumentationBase.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchInstrumentationBase.java
@@ -237,7 +237,7 @@
         mContextualSearchManager.getBaseSelectionPopupController().setSelectedText(text);
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mContextualSearchManager.getGestureStateListener().onTouchDown();
-            mContextualSearchManager.onShowUnhandledTapUIIfNeeded(0, 0, 12, 100);
+            mContextualSearchManager.onShowUnhandledTapUIIfNeeded(0, 0);
         });
     }
 
@@ -246,7 +246,7 @@
      */
     protected void mockTapEmptySpace() {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            mContextualSearchManager.onShowUnhandledTapUIIfNeeded(0, 0, 0, 0);
+            mContextualSearchManager.onShowUnhandledTapUIIfNeeded(0, 0);
             mContextualSearchClient.onSelectionEvent(
                     SelectionEventType.SELECTION_HANDLES_CLEARED, 0, 0);
         });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java
index a9b006c..862c9f2b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadManagerServiceTest.java
@@ -416,32 +416,4 @@
         notifier.waitTillExpectedCallsComplete();
         Assert.assertTrue("All downloads should be updated.", matchSet.mMatches.isEmpty());
     }
-
-    @Test
-    @MediumTest
-    @Feature({"Download"})
-    @Features.EnableFeatures({ChromeFeatureList.CCT_NEW_DOWNLOAD_TAB})
-    public void testCCTDownloads() {
-        Assert.assertTrue(ChromeFeatureList.isEnabled(ChromeFeatureList.CCT_NEW_DOWNLOAD_TAB));
-        if (useDownloadOfflineContentProvider()) return;
-        MockDownloadNotifier notifier = new MockDownloadNotifier();
-        createDownloadManagerService(notifier, UPDATE_DELAY_FOR_TEST);
-        TestThreadUtils.runOnUiThreadBlocking(
-                (Runnable) () -> DownloadManagerService.setDownloadManagerService(mService));
-        // Try calling download completed directly.
-        DownloadInfo successful = getDownloadInfo();
-        notifier.expect(MethodID.DOWNLOAD_SUCCESSFUL, successful);
-
-        // Add the download to the inProgressCCTDownloads set before the download starts.
-        DownloadManagerService.addCCTDownload(successful.getDownloadGuid());
-        Assert.assertTrue(DownloadManagerService.inProgressCCTDownloadsContains(
-                successful.getDownloadGuid()));
-
-        mService.onDownloadCompleted(successful);
-        notifier.waitTillExpectedCallsComplete();
-
-        // Check that the download has been removed from the set after downloading successfully.
-        Assert.assertFalse(DownloadManagerService.inProgressCCTDownloadsContains(
-                successful.getDownloadGuid()));
-    }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTest.java
index 7159c4a3..876e0d7 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/DownloadTest.java
@@ -121,6 +121,9 @@
         public void onNotificationShown(ContentId id, int notificationId) {}
 
         @Override
+        public void addDownloadInterstitialSource(String originalUrl) {}
+
+        @Override
         public void onItemsAdded(List<OfflineItem> items) {}
 
         @Override
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/download/OMADownloadHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/download/OMADownloadHandlerTest.java
index 4cab786b..99f83f4a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/download/OMADownloadHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/download/OMADownloadHandlerTest.java
@@ -87,6 +87,9 @@
         }
 
         @Override
+        public void addDownloadInterstitialSource(String originalUrl) {}
+
+        @Override
         public void onItemsAdded(List<OfflineItem> items) {}
 
         @Override
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/share/crow/CrowButtonDelegateImplTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/share/crow/CrowButtonDelegateImplTest.java
index 3f0304e..54943fe 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/share/crow/CrowButtonDelegateImplTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/share/crow/CrowButtonDelegateImplTest.java
@@ -40,37 +40,43 @@
         final GURL shareUrl1 = new GURL("https://testSiteWeAreSharing.com/blog/entry");
         final GURL shareUrl2 = new GURL("https://testSiteWeAreSharing.com/?blog=1&entry=2");
         boolean allowMetrics = true;
+        boolean isFollowing = false;
 
         assertEquals("",
-                delegate.buildServerUrl(GURL.emptyGURL(), shareUrl1, shareUrl1, "", allowMetrics));
+                delegate.buildServerUrl(
+                        GURL.emptyGURL(), shareUrl1, shareUrl1, "", allowMetrics, isFollowing));
 
         // Baseline/common case.
         assertEquals(
                 "https://www.foo.com/v1/api?q=hi&pageUrl=https%3A%2F%2Ftestsitewearesharing.com%2Fblog%2Fentry&entry=menu&relCanonUrl=https%3A%2F%2Ftestsitewearesharing.com%2Fblog%2Fentry&publicationId=pubId1&metrics=true",
-                delegate.buildServerUrl(serverUrl, shareUrl1, shareUrl1, "pubId1", allowMetrics));
+                delegate.buildServerUrl(
+                        serverUrl, shareUrl1, shareUrl1, "pubId1", allowMetrics, isFollowing));
 
         // Sending a URL with urlparams of its own.
         assertEquals(
                 "https://www.foo.com/v1/api?q=hi&pageUrl=https%3A%2F%2Ftestsitewearesharing.com%2F%3Fblog%3D1%26entry%3D2&entry=menu&relCanonUrl=https%3A%2F%2Ftestsitewearesharing.com%2F%3Fblog%3D1%26entry%3D2&publicationId=pubId2&metrics=true",
-                delegate.buildServerUrl(serverUrl, shareUrl2, shareUrl2, "pubId2", allowMetrics));
+                delegate.buildServerUrl(
+                        serverUrl, shareUrl2, shareUrl2, "pubId2", allowMetrics, isFollowing));
 
         // Empty canonical URL is ok, passes as empty param.
         assertEquals(
                 "https://www.foo.com/v1/api?q=hi&pageUrl=https%3A%2F%2Ftestsitewearesharing.com%2Fblog%2Fentry&entry=menu&relCanonUrl=&publicationId=pubId1&metrics=true",
-                delegate.buildServerUrl(
-                        serverUrl, shareUrl1, GURL.emptyGURL(), "pubId1", allowMetrics));
+                delegate.buildServerUrl(serverUrl, shareUrl1, GURL.emptyGURL(), "pubId1",
+                        allowMetrics, isFollowing));
 
         // Experimental URL can be passed with an empty set of params.
         assertEquals(
                 "http://www.foo.com/v1/api?pageUrl=https%3A%2F%2Ftestsitewearesharing.com%2Fblog%2Fentry&entry=menu&relCanonUrl=https%3A%2F%2Ftestsitewearesharing.com%2Fblog%2Fentry&publicationId=pubId1&metrics=true",
-                delegate.buildServerUrl(
-                        serverUrlWithoutQueryString, shareUrl1, shareUrl1, "pubId1", allowMetrics));
+                delegate.buildServerUrl(serverUrlWithoutQueryString, shareUrl1, shareUrl1, "pubId1",
+                        allowMetrics, isFollowing));
 
-        // Metrics off should be reflected.
+        // Metrics off and already following should be reflected.
         allowMetrics = false;
+        isFollowing = true;
         assertEquals(
-                "https://www.foo.com/v1/api?q=hi&pageUrl=https%3A%2F%2Ftestsitewearesharing.com%2Fblog%2Fentry&entry=menu&relCanonUrl=https%3A%2F%2Ftestsitewearesharing.com%2Fblog%2Fentry&publicationId=pubId1&metrics=false",
-                delegate.buildServerUrl(serverUrl, shareUrl1, shareUrl1, "pubId1", allowMetrics));
+                "https://www.foo.com/v1/api?q=hi&pageUrl=https%3A%2F%2Ftestsitewearesharing.com%2Fblog%2Fentry&entry=menu&relCanonUrl=https%3A%2F%2Ftestsitewearesharing.com%2Fblog%2Fentry&publicationId=pubId1&metrics=false&following=true",
+                delegate.buildServerUrl(
+                        serverUrl, shareUrl1, shareUrl1, "pubId1", allowMetrics, isFollowing));
     }
 
     @Test
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/tabbed_mode/TabbedNavigationBarColorControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/tabbed_mode/TabbedNavigationBarColorControllerTest.java
index 95aef31..7be708b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/tabbed_mode/TabbedNavigationBarColorControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/tabbed_mode/TabbedNavigationBarColorControllerTest.java
@@ -36,6 +36,7 @@
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
 import org.chromium.chrome.test.util.ChromeTabUtils;
+import org.chromium.components.browser_ui.styles.SemanticColorUtils;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.test.util.DOMUtils;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
@@ -54,7 +55,7 @@
     @Rule
     public EmbeddedTestServerRule mTestServerRule = new EmbeddedTestServerRule();
     private Window mWindow;
-    private int mLightNavigationColor;
+    private int mRegularNavigationColor;
     private int mDarkNavigationColor;
 
     @Before
@@ -62,34 +63,34 @@
         mActivityTestRule.startMainActivityOnBlankPage();
         mWindow = mActivityTestRule.getActivity().getWindow();
         Context context = mActivityTestRule.getActivity();
-        mLightNavigationColor = context.getColor(R.color.default_bg_color_light);
+        mRegularNavigationColor = SemanticColorUtils.getBottomSystemNavColor(context);
         mDarkNavigationColor = context.getColor(R.color.default_bg_color_dark);
     }
 
     @Test
     @SmallTest
     public void testToggleOverview() {
-        assertEquals("Navigation bar should be white before entering overview mode.",
-                mLightNavigationColor, mWindow.getNavigationBarColor());
+        assertEquals("Navigation bar should be colorSurface before entering overview mode.",
+                mRegularNavigationColor, mWindow.getNavigationBarColor());
 
         LayoutTestUtils.startShowingAndWaitForLayout(
                 mActivityTestRule.getActivity().getLayoutManager(), LayoutType.TAB_SWITCHER, false);
 
-        assertEquals("Navigation bar should be white in overview mode.", mLightNavigationColor,
-                mWindow.getNavigationBarColor());
+        assertEquals("Navigation bar should be colorSurface in overview mode.",
+                mRegularNavigationColor, mWindow.getNavigationBarColor());
 
         LayoutTestUtils.startShowingAndWaitForLayout(
                 mActivityTestRule.getActivity().getLayoutManager(), LayoutType.BROWSING, false);
 
-        assertEquals("Navigation bar should be white after exiting overview mode.",
-                mLightNavigationColor, mWindow.getNavigationBarColor());
+        assertEquals("Navigation bar should be colorSurface after exiting overview mode.",
+                mRegularNavigationColor, mWindow.getNavigationBarColor());
     }
 
     @Test
     @SmallTest
     public void testToggleIncognito() {
-        assertEquals("Navigation bar should be white on normal tabs.", mLightNavigationColor,
-                mWindow.getNavigationBarColor());
+        assertEquals("Navigation bar should be colorSurface on normal tabs.",
+                mRegularNavigationColor, mWindow.getNavigationBarColor());
 
         ChromeTabUtils.newTabFromMenu(InstrumentationRegistry.getInstrumentation(),
                 mActivityTestRule.getActivity(), true, true);
@@ -100,15 +101,15 @@
         ChromeTabUtils.newTabFromMenu(InstrumentationRegistry.getInstrumentation(),
                 mActivityTestRule.getActivity(), false, true);
 
-        assertEquals("Navigation bar should be white after switching back to normal tab.",
-                mLightNavigationColor, mWindow.getNavigationBarColor());
+        assertEquals("Navigation bar should be colorSurface after switching back to normal tab.",
+                mRegularNavigationColor, mWindow.getNavigationBarColor());
     }
 
     @Test
     @MediumTest
     public void testToggleFullscreen() throws TimeoutException {
-        assertEquals("Navigation bar should be white before entering fullscreen mode.",
-                mLightNavigationColor, mWindow.getNavigationBarColor());
+        assertEquals("Navigation bar should be colorSurface before entering fullscreen mode.",
+                mRegularNavigationColor, mWindow.getNavigationBarColor());
 
         String url =
                 mTestServerRule.getServer().getURL("/content/test/data/media/video-player.html");
@@ -125,8 +126,8 @@
 
         exitFullscreen(observer, activity.getCurrentWebContents());
 
-        assertEquals("Navigation bar should be white after exiting fullscreen mode.",
-                mLightNavigationColor, mWindow.getNavigationBarColor());
+        assertEquals("Navigation bar should be colorSurface after exiting fullscreen mode.",
+                mRegularNavigationColor, mWindow.getNavigationBarColor());
     }
 
     private void enterFullscreen(FullscreenToggleObserver observer, WebContents webContents)
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java
index 6e51d9a1..1099a73 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java
@@ -5,7 +5,6 @@
 package org.chromium.chrome.browser.toolbar.top;
 
 import android.graphics.Color;
-import android.os.Build;
 import android.text.TextUtils;
 
 import androidx.test.filters.SmallTest;
@@ -37,7 +36,6 @@
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.ui.test.util.UiRestriction;
-import org.chromium.ui.util.ColorUtils;
 
 /**
  * Contains tests for the brand color feature.
@@ -54,7 +52,6 @@
     private ToolbarPhone mToolbar;
     private ToolbarDataProvider mToolbarDataProvider;
     private int mDefaultColor;
-    private boolean mSupportsDarkStatusIcons;
 
     private static String getUrlWithBrandColor(String brandColor) {
         String brandColorMetaTag = TextUtils.isEmpty(brandColor)
@@ -76,13 +73,7 @@
         });
         if (!SysUtils.isLowEndDevice()) {
             final int expectedStatusBarColor;
-            if (mSupportsDarkStatusIcons) {
-                expectedStatusBarColor = brandColor == mDefaultColor ? Color.WHITE : brandColor;
-            } else {
-                expectedStatusBarColor = brandColor == mDefaultColor
-                        ? Color.BLACK
-                        : ColorUtils.getDarkenedColorForStatusBar(brandColor);
-            }
+            expectedStatusBarColor = brandColor == mDefaultColor ? mDefaultColor : brandColor;
             CriteriaHelper.pollUiThread(() -> {
                 Criteria.checkThat(mActivityTestRule.getActivity().getWindow().getStatusBarColor(),
                         Matchers.is(expectedStatusBarColor));
@@ -96,9 +87,6 @@
         mToolbarDataProvider = mToolbar.getToolbarDataProvider();
         mDefaultColor = ChromeColors.getDefaultThemeColor(
                 mActivityTestRule.getActivity(), /* isIncognito = */ false);
-        // TODO(https://crbug.com/871805): Use helper class to determine whether dark status icons
-        // are supported.
-        mSupportsDarkStatusIcons = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
     }
 
     /**
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/VrActivityRestrictionRule.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/VrActivityRestrictionRule.java
index c8ad520..5b906a6 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/VrActivityRestrictionRule.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/rules/VrActivityRestrictionRule.java
@@ -12,6 +12,7 @@
 import org.chromium.chrome.browser.vr.TestVrShellDelegate;
 import org.chromium.chrome.browser.vr.rules.XrActivityRestriction.SupportedActivity;
 import org.chromium.chrome.browser.vr.util.XrTestRuleUtils;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
 
 /**
  * Rule that conditionally skips a test if the current VrTestRule's Activity is not
@@ -27,7 +28,11 @@
     @Override
     public Statement apply(final Statement base, final Description desc) {
         // Currently, we don't have any VR-specific logic except for standalone devices.
-        if (!TestVrShellDelegate.isOnStandalone()) {
+        // TestVrShellDelegate creation should be done on the UI thread. Run inside UI thread
+        // to ensure its internal static members are also initialized on UI thread.
+        boolean isOnStandalone = TestThreadUtils.runOnUiThreadBlockingNoException(
+                TestVrShellDelegate::isOnStandalone);
+        if (!isOnStandalone) {
             return base;
         }
         // We can only run tests in ChromeTabbedActivity on standalones, so ignore if the current
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/signin/SigninManagerImplTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/signin/SigninManagerImplTest.java
index 4a3300db0..b3c443b 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/signin/SigninManagerImplTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/signin/SigninManagerImplTest.java
@@ -21,19 +21,26 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 import org.mockito.stubbing.Answer;
 import org.robolectric.annotation.LooperMode;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.JniMocker;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.signin.services.SigninManager;
 import org.chromium.chrome.browser.sync.SyncService;
+import org.chromium.chrome.test.util.browser.Features;
+import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
+import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
 import org.chromium.components.externalauth.ExternalAuthUtils;
+import org.chromium.components.signin.AccountManagerFacadeProvider;
 import org.chromium.components.signin.AccountUtils;
 import org.chromium.components.signin.base.AccountCapabilities;
 import org.chromium.components.signin.base.AccountInfo;
 import org.chromium.components.signin.base.CoreAccountId;
+import org.chromium.components.signin.base.CoreAccountInfo;
 import org.chromium.components.signin.identitymanager.AccountInfoServiceProvider;
 import org.chromium.components.signin.identitymanager.AccountTrackerService;
 import org.chromium.components.signin.identitymanager.ConsentLevel;
@@ -44,6 +51,7 @@
 import org.chromium.components.signin.metrics.SigninAccessPoint;
 import org.chromium.components.signin.metrics.SignoutDelete;
 import org.chromium.components.signin.metrics.SignoutReason;
+import org.chromium.components.signin.test.util.FakeAccountManagerFacade;
 import org.chromium.components.sync.ModelType;
 
 import java.util.HashMap;
@@ -59,6 +67,13 @@
     private static final AccountInfo ACCOUNT_INFO =
             new AccountInfo(new CoreAccountId("gaia-id-user"), "user@domain.com", "gaia-id-user",
                     "full name", "given name", null, new AccountCapabilities(new HashMap<>()));
+    private static final CoreAccountInfo CHILD_CORE_ACCOUNT_INFO =
+            CoreAccountInfo.createFromEmailAndGaiaId(
+                    FakeAccountManagerFacade.generateChildEmail("user@domain.com"),
+                    "child-gaia-id-user");
+
+    @Rule
+    public final TestRule mFeaturesProcessorRule = new Features.JUnitProcessor();
 
     @Rule
     public final JniMocker mocker = new JniMocker();
@@ -73,6 +88,8 @@
 
     private final IdentityManager mIdentityManager =
             IdentityManager.create(NATIVE_IDENTITY_MANAGER, null /* OAuth2TokenService */);
+    private final FakeAccountManagerFacade mFakeAccountManagerFacade =
+            new FakeAccountManagerFacade();
     private SigninManagerImpl mSigninManager;
 
     @Before
@@ -96,6 +113,8 @@
                      NATIVE_IDENTITY_MANAGER, ACCOUNT_INFO.getEmail()))
                 .thenReturn(ACCOUNT_INFO);
 
+        AccountManagerFacadeProvider.setInstanceForTests(mFakeAccountManagerFacade);
+
         mSigninManager = (SigninManagerImpl) SigninManagerImpl.create(
                 NATIVE_SIGNIN_MANAGER, mAccountTrackerService, mIdentityManager, mIdentityMutator);
     }
@@ -352,7 +371,11 @@
     }
 
     @Test
+    @EnableFeatures({ChromeFeatureList.ALLOW_SYNC_OFF_FOR_CHILD_ACCOUNTS})
     public void clearingAccountCookieDoesNotTriggerSignoutWhenUserIsSignedOut() {
+        mFakeAccountManagerFacade.addAccount(
+                AccountUtils.createAccountFromName(ACCOUNT_INFO.getEmail()));
+
         mIdentityManager.onAccountsCookieDeletedByUserAction();
 
         verify(mIdentityMutator, never()).clearPrimaryAccount(anyInt(), anyInt());
@@ -361,10 +384,13 @@
     }
 
     @Test
+    @EnableFeatures({ChromeFeatureList.ALLOW_SYNC_OFF_FOR_CHILD_ACCOUNTS})
     public void clearingAccountCookieDoesNotTriggerSignoutWhenUserIsSignedInAndSync() {
         when(mIdentityManagerNativeMock.getPrimaryAccountInfo(
                      eq(NATIVE_IDENTITY_MANAGER), anyInt()))
                 .thenReturn(ACCOUNT_INFO);
+        mFakeAccountManagerFacade.addAccount(
+                AccountUtils.createAccountFromName(ACCOUNT_INFO.getEmail()));
 
         mIdentityManager.onAccountsCookieDeletedByUserAction();
 
@@ -374,10 +400,13 @@
     }
 
     @Test
-    public void clearingAccountCookieTriggersSignoutWhenUserIsSignedInWithoutSync() {
+    @EnableFeatures({ChromeFeatureList.ALLOW_SYNC_OFF_FOR_CHILD_ACCOUNTS})
+    public void clearingAccountCookieTriggersSignoutWhenNormalUserIsSignedInWithoutSync() {
         when(mIdentityManagerNativeMock.getPrimaryAccountInfo(
                      NATIVE_IDENTITY_MANAGER, ConsentLevel.SIGNIN))
                 .thenReturn(ACCOUNT_INFO);
+        mFakeAccountManagerFacade.addAccount(
+                AccountUtils.createAccountFromName(ACCOUNT_INFO.getEmail()));
 
         mIdentityManager.onAccountsCookieDeletedByUserAction();
 
@@ -390,6 +419,42 @@
     }
 
     @Test
+    @DisableFeatures({ChromeFeatureList.ALLOW_SYNC_OFF_FOR_CHILD_ACCOUNTS})
+    public void clearingAccountCookieTriggersSignoutWhenSupervisedUserIsSignedInWithoutSync() {
+        when(mIdentityManagerNativeMock.getPrimaryAccountInfo(
+                     NATIVE_IDENTITY_MANAGER, ConsentLevel.SIGNIN))
+                .thenReturn(CHILD_CORE_ACCOUNT_INFO);
+        mFakeAccountManagerFacade.addAccount(
+                AccountUtils.createAccountFromName(CHILD_CORE_ACCOUNT_INFO.getEmail()));
+
+        mIdentityManager.onAccountsCookieDeletedByUserAction();
+
+        verify(mIdentityMutator)
+                .clearPrimaryAccount(
+                        SignoutReason.USER_DELETED_ACCOUNT_COOKIES, SignoutDelete.IGNORE_METRIC);
+        // Sign-out triggered by wiping account cookies shouldn't wipe data.
+        verify(mNativeMock, never()).wipeProfileData(anyLong(), any());
+        verify(mNativeMock, never()).wipeGoogleServiceWorkerCaches(anyLong(), any());
+    }
+
+    @Test
+    @EnableFeatures({ChromeFeatureList.ALLOW_SYNC_OFF_FOR_CHILD_ACCOUNTS})
+    public void
+    clearingAccountCookieDoesNotTriggerSignoutWhenSupervisedUserIsSignedInWithoutSync() {
+        when(mIdentityManagerNativeMock.getPrimaryAccountInfo(
+                     NATIVE_IDENTITY_MANAGER, ConsentLevel.SIGNIN))
+                .thenReturn(CHILD_CORE_ACCOUNT_INFO);
+        mFakeAccountManagerFacade.addAccount(
+                AccountUtils.createAccountFromName(CHILD_CORE_ACCOUNT_INFO.getEmail()));
+
+        mIdentityManager.onAccountsCookieDeletedByUserAction();
+
+        verify(mIdentityMutator, never()).clearPrimaryAccount(anyInt(), anyInt());
+        verify(mNativeMock, never()).wipeProfileData(anyLong(), any());
+        verify(mNativeMock, never()).wipeGoogleServiceWorkerCaches(anyLong(), any());
+    }
+
+    @Test
     public void callbackNotifiedWhenNoOperationIsInProgress() {
         AtomicInteger callCount = new AtomicInteger(0);
 
diff --git a/chrome/android/monochrome/BUILD.gn b/chrome/android/monochrome/BUILD.gn
index 982aa3e..238fada1 100644
--- a/chrome/android/monochrome/BUILD.gn
+++ b/chrome/android/monochrome/BUILD.gn
@@ -45,8 +45,8 @@
   testonly = true
   pydeps_file = "scripts/monochrome_python_tests.pydeps"
   data = [
-    "${android_sdk_build_tools}/aapt2",
-    "${android_sdk_build_tools}/dexdump",
+    "${public_android_sdk_build_tools}/aapt2",
+    "${public_android_sdk_build_tools}/dexdump",
   ]
   data_deps = [
     "//build/android:devil_chromium_py",
diff --git a/chrome/app/access_code_cast_strings.grdp b/chrome/app/access_code_cast_strings.grdp
index 95ab89f..452d223 100644
--- a/chrome/app/access_code_cast_strings.grdp
+++ b/chrome/app/access_code_cast_strings.grdp
@@ -37,6 +37,26 @@
   <message name="IDS_ACCESS_CODE_CAST_INPUT_ARIA_LABEL" is_accessibility_with_no_ui="true" desc="Label for the text box where users type the access code to start casting to a Chromecast device">
     Type the access code to start casting
   </message>
+  <message name="IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_DAYS" desc="In the dialog to connect to a chromecast device using a code, this message is a footnote that lets the user know that the Chromecast device will be remembered and will remain in their list for a number of days">
+    {DAYS, plural,
+      =1 {This device will be saved for 1 day and you can connect without a code next time. This is set by your administrator.}
+      other {This device will be saved for {DAYS} days and you can connect without a code next time. This is set by your administrator.}}
+  </message>
+  <message name="IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_HOURS" desc="In the dialog to connect to a chromecast device using a code, this message is a footnote that lets the user know that the Chromecast device will be remembered and will remain in their list for a number of hours">
+    {HOURS, plural,
+      =1 {This device will be saved for 1 hour and you can connect without a code next time. This is set by your administrator.}
+      other {This device will be saved for {HOURS} hours and you can connect without a code next time. This is set by your administrator.}}
+  </message>
+  <message name="IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_MONTHS" desc="In the dialog to connect to a chromecast device using a code, this message is a footnote that lets the user know that the Chromecast device will be remembered and will remain in their list for a number of months">
+    {MONTHS, plural,
+      =1 {This device will be saved for 1 month and you can connect without a code next time. This is set by your administrator.}
+      other {This device will be saved for {MONTHS} months and you can connect without a code next time. This is set by your administrator.}}
+  </message>
+  <message name="IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_YEARS" desc="In the dialog to connect to a chromecast device using a code, this message is a footnote that lets the user know that the Chromecast device will be remembered and will remain in their list for a number of years">
+    {YEARS, plural,
+      =1 {This device will be saved for 1 year and you can connect without a code next time. This is set by your administrator.}
+      other {This device will be saved for {YEARS} years and you can connect without a code next time. This is set by your administrator.}}
+  </message>
   <message name="IDS_ACCESS_CODE_CAST_SUBMIT" desc="Text for the button that when pressed submits an access code to attempt to start casting">
     Submit
   </message>
diff --git a/chrome/app/access_code_cast_strings_grdp/IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_DAYS.png.sha1 b/chrome/app/access_code_cast_strings_grdp/IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_DAYS.png.sha1
new file mode 100644
index 0000000..57f147f
--- /dev/null
+++ b/chrome/app/access_code_cast_strings_grdp/IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_DAYS.png.sha1
@@ -0,0 +1 @@
+9b0992d53f87302cc98b9401ed8a386dc946a71f
\ No newline at end of file
diff --git a/chrome/app/access_code_cast_strings_grdp/IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_HOURS.png.sha1 b/chrome/app/access_code_cast_strings_grdp/IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_HOURS.png.sha1
new file mode 100644
index 0000000..c2e3403
--- /dev/null
+++ b/chrome/app/access_code_cast_strings_grdp/IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_HOURS.png.sha1
@@ -0,0 +1 @@
+5534dc4925d3946a668f9e660534a4f231541ad0
\ No newline at end of file
diff --git a/chrome/app/access_code_cast_strings_grdp/IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_MONTHS.png.sha1 b/chrome/app/access_code_cast_strings_grdp/IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_MONTHS.png.sha1
new file mode 100644
index 0000000..a60ea4e
--- /dev/null
+++ b/chrome/app/access_code_cast_strings_grdp/IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_MONTHS.png.sha1
@@ -0,0 +1 @@
+a0c29ea8ecd35913a0a28987c51f478238f72066
\ No newline at end of file
diff --git a/chrome/app/access_code_cast_strings_grdp/IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_YEARS.png.sha1 b/chrome/app/access_code_cast_strings_grdp/IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_YEARS.png.sha1
new file mode 100644
index 0000000..bc13ad0
--- /dev/null
+++ b/chrome/app/access_code_cast_strings_grdp/IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_YEARS.png.sha1
@@ -0,0 +1 @@
+87832e23b136c4d6d278c457c515114fe5f56fa7
\ No newline at end of file
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 12d93db4..f995e49d 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -7421,7 +7421,7 @@
           </message>
         </if>
         <if expr="use_titlecase">
-          <message name="IDS_PASSWORD_MANAGER_CHECK_REMAINING_BUTTON" desc="Button text to check left compromised passwords.">
+          <message name="IDS_PASSWORD_MANAGER_CHECK_REMAINING_BUTTON" desc="In Title Case: Button text to check left compromised passwords.">
             Check Remaining Passwords
           </message>
         </if>
@@ -12931,7 +12931,13 @@
     <message name="IDS_ACCOUNT_SELECTION_CONTINUE" desc="Title of the button that continues filling with the only available set of credentials.">
       Continue as <ph name="NAME">$1<ex>Albus (or Albus Dumbledore)</ex></ph>
     </message>
-    <message name="IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_TOS" desc="The consent text shown to the user before sign up when there are no terms of service.">
+    <message name="IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP_OR_TOS" desc="The consent text shown to the user before sign up when there is no privacy policy or terms of service.">
+      To continue, <ph name="IDENTITY_PROVIDER_ETLD_PLUS_ONE">$1<ex>idp.com</ex></ph> will share your name, email address, and profile picture with this site.
+    </message>
+    <message name="IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP" desc="The consent text shown to the user before sign up when there is no privacy policy.">
+      To continue, <ph name="IDENTITY_PROVIDER_ETLD_PLUS_ONE">$1<ex>idp.com</ex></ph> will share your name, email address, and profile picture with this site. See this site's <ph name="BEGIN_LINK">$2</ph>terms of service<ph name="END_LINK">$3</ph>.
+    </message>
+    <message name="IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_TOS" desc="The consent text shown to the user before sign up when there is no terms of service.">
       To continue, <ph name="IDENTITY_PROVIDER_ETLD_PLUS_ONE">$1<ex>idp.com</ex></ph> will share your name, email address, and profile picture with this site. See this site's <ph name="BEGIN_LINK">$2</ph>privacy policy<ph name="END_LINK">$3</ph>.
     </message>
     <message name="IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT" desc="The consent text shown to the user before sign up.">
diff --git a/chrome/app/generated_resources_grd/IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP.png.sha1 b/chrome/app/generated_resources_grd/IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP.png.sha1
new file mode 100644
index 0000000..96a0ade
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP.png.sha1
@@ -0,0 +1 @@
+a6f400a241e6a4873ae9f5219cf085a37ecf0020
\ No newline at end of file
diff --git a/chrome/app/generated_resources_grd/IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP_OR_TOS.png.sha1 b/chrome/app/generated_resources_grd/IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP_OR_TOS.png.sha1
new file mode 100644
index 0000000..a125a233
--- /dev/null
+++ b/chrome/app/generated_resources_grd/IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP_OR_TOS.png.sha1
@@ -0,0 +1 @@
+126b2ce7eae56c480f30de2728ad400e06f3e67a
\ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 5d036dd..34621f96 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2459,19 +2459,11 @@
     {features::kFedCmAutoSigninFieldTrialParamName, "true"}};
 const FeatureEntry::FeatureParam kFedCmVariationIdpSignout[] = {
     {features::kFedCmIdpSignoutFieldTrialParamName, "true"}};
-#if !BUILDFLAG(IS_ANDROID)
-const FeatureEntry::FeatureParam kFedCmVariationDesktopSettings[] = {
-    {features::kFedCmDesktopSettingsFieldTrialParamName, "true"}};
-#endif  // BUILDFLAG(IS_ANDROID)
 const FeatureEntry::FeatureVariation kFedCmFeatureVariations[] = {
     {"- with FedCM auto sign-in", kFedCmVariationAutoSignin,
      std::size(kFedCmVariationAutoSignin), nullptr},
     {"- with FedCM IDP sign-out", kFedCmVariationIdpSignout,
      std::size(kFedCmVariationIdpSignout), nullptr},
-#if !BUILDFLAG(IS_ANDROID)
-    {"- with desktop settings", kFedCmVariationDesktopSettings,
-     std::size(kFedCmVariationDesktopSettings), nullptr}
-#endif  // BUILDFLAG(IS_ANDROID)
 };
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
@@ -2894,6 +2886,18 @@
     {"QuickDim_negative_score_threshold", "0"},
 };
 
+const FeatureEntry::FeatureParam kQuickDim10sQuickLock130sFeedback[] = {
+    {"QuickDim_quick_dim_ms", "10000"},
+    {"QuickDim_quick_lock_ms", "130000"},
+    {"QuickDim_filter_config_case", "2"},
+    {"QuickDim_positive_count_threshold", "1"},
+    {"QuickDim_negative_count_threshold", "2"},
+    {"QuickDim_uncertain_count_threshold", "2"},
+    {"QuickDim_positive_score_threshold", "0"},
+    {"QuickDim_negative_score_threshold", "0"},
+    {"QuickDim_send_feedback_if_undimmed", "true"},
+};
+
 const FeatureEntry::FeatureParam kQuickDim10sQuickLock130sThreshold20[] = {
     {"QuickDim_quick_dim_ms", "10000"},
     {"QuickDim_quick_lock_ms", "130000"},
@@ -2991,6 +2995,8 @@
      std::size(kQuickDim10sQuickLock130sThreshold40), nullptr},
     {"Dim10sLock130sThreshold-40", kQuickDim10sQuickLock130sThresholdMinus40,
      std::size(kQuickDim10sQuickLock130sThresholdMinus40), nullptr},
+    {"Dim10sLock130sWithFeedback", kQuickDim10sQuickLock130sFeedback,
+     std::size(kQuickDim10sQuickLock130sFeedback), nullptr},
 };
 
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
@@ -3448,6 +3454,9 @@
     {"calendar-view", flag_descriptions::kCalendarViewName,
      flag_descriptions::kCalendarViewDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::features::kCalendarView)},
+    {"calendar-view-debug-mode", flag_descriptions::kCalendarModelDebugModeName,
+     flag_descriptions::kCalendarModelDebugModeDescription, kOsCrOS,
+     FEATURE_VALUE_TYPE(chromeos::features::kCalendarModelDebugMode)},
     {"cellular-bypass-esim-installation-connectivity-check",
      flag_descriptions::kCellularBypassESimInstallationConnectivityCheckName,
      flag_descriptions::
@@ -8054,7 +8063,7 @@
          chrome::android::kTrustedWebActivityNotificationPermissionDelegation)},
 #endif  // BUILDFLAG(IS_ANDROID)
 
-#if defined(TOOKIT_VIEWS)
+#if defined(TOOLKIT_VIEWS)
     {"side-search", flag_descriptions::kSideSearchName,
      flag_descriptions::kSideSearchDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(features::kSideSearch)},
@@ -8062,7 +8071,7 @@
     {"side-search-dse-support", flag_descriptions::kSideSearchDSESupportName,
      flag_descriptions::kSideSearchDSESupportDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(features::kSideSearchDSESupport)},
-#endif  // defined(TOOKIT_VIEWS)
+#endif  // defined(TOOLKIT_VIEWS)
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
     {"enable-component-updater-test-request",
diff --git a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/BrowserServicesIntentDataProvider.java b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/BrowserServicesIntentDataProvider.java
index bdf36b5..b8000b4 100644
--- a/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/BrowserServicesIntentDataProvider.java
+++ b/chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/BrowserServicesIntentDataProvider.java
@@ -482,4 +482,11 @@
     public @CloseButtonPosition int getCloseButtonPosition() {
         return CLOSE_BUTTON_POSITION_DEFAULT;
     }
+
+    /**
+     * Returns the partial custom tab toolbar corner radius.
+     */
+    public @Px int getPartialTabToolbarCornerRadius() {
+        return 0;
+    }
 }
diff --git a/chrome/browser/android/contextualsearch/contextual_search_tab_helper.cc b/chrome/browser/android/contextualsearch/contextual_search_tab_helper.cc
index 84124fa5..97a2f56 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_tab_helper.cc
+++ b/chrome/browser/android/contextualsearch/contextual_search_tab_helper.cc
@@ -48,15 +48,12 @@
   Java_ContextualSearchTabHelper_onContextualSearchPrefChanged(env, jobj);
 }
 
-void ContextualSearchTabHelper::OnShowUnhandledTapUIIfNeeded(
-    int x_px,
-    int y_px,
-    int font_size_dips,
-    int text_run_length) {
+void ContextualSearchTabHelper::OnShowUnhandledTapUIIfNeeded(int x_px,
+                                                             int y_px) {
   JNIEnv* env = base::android::AttachCurrentThread();
   ScopedJavaLocalRef<jobject> jobj = weak_java_ref_.get(env);
-  Java_ContextualSearchTabHelper_onShowUnhandledTapUIIfNeeded(
-      env, jobj, x_px, y_px, font_size_dips, text_run_length);
+  Java_ContextualSearchTabHelper_onShowUnhandledTapUIIfNeeded(env, jobj, x_px,
+                                                              y_px);
 }
 
 void ContextualSearchTabHelper::InstallUnhandledTapNotifierIfNeeded(
diff --git a/chrome/browser/android/contextualsearch/contextual_search_tab_helper.h b/chrome/browser/android/contextualsearch/contextual_search_tab_helper.h
index 3a182533..5ddcf75 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_tab_helper.h
+++ b/chrome/browser/android/contextualsearch/contextual_search_tab_helper.h
@@ -43,10 +43,7 @@
   // Call when an unhandled tap needs to show the UI for a tap at the given
   // position, with the given |font_size_dips|, and |text_run_length| of the
   // enclosing element.
-  void OnShowUnhandledTapUIIfNeeded(int x_px,
-                                    int y_px,
-                                    int font_size_dips,
-                                    int text_run_length);
+  void OnShowUnhandledTapUIIfNeeded(int x_px, int y_px);
 
   JavaObjectWeakGlobalRef weak_java_ref_;
   std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_;
diff --git a/chrome/browser/android/contextualsearch/unhandled_tap_notifier_impl.cc b/chrome/browser/android/contextualsearch/unhandled_tap_notifier_impl.cc
index 4e80be304..b6495f3 100644
--- a/chrome/browser/android/contextualsearch/unhandled_tap_notifier_impl.cc
+++ b/chrome/browser/android/contextualsearch/unhandled_tap_notifier_impl.cc
@@ -21,13 +21,9 @@
   float x_px = unhandled_tap_info->tapped_position_in_viewport.x();
   float y_px = unhandled_tap_info->tapped_position_in_viewport.y();
 
-  // Pixel from Blink are DIPs.
-  int font_size_dips = unhandled_tap_info->font_size_in_pixels;
-
   // Call back through the callback if possible.  (The callback uses a weakptr
   // that might make this a NOP).
-  unhandled_tap_callback_.Run(x_px, y_px, font_size_dips,
-                              unhandled_tap_info->element_text_run_length);
+  unhandled_tap_callback_.Run(x_px, y_px);
 }
 
 // static
diff --git a/chrome/browser/android/contextualsearch/unhandled_tap_web_contents_observer.h b/chrome/browser/android/contextualsearch/unhandled_tap_web_contents_observer.h
index b5c3cba..3756808 100644
--- a/chrome/browser/android/contextualsearch/unhandled_tap_web_contents_observer.h
+++ b/chrome/browser/android/contextualsearch/unhandled_tap_web_contents_observer.h
@@ -9,9 +9,7 @@
 
 namespace contextual_search {
 
-typedef base::RepeatingCallback<
-    void(int x_px, int y_px, int font_size_dips, int text_run_length)>
-    UnhandledTapCallback;
+typedef base::RepeatingCallback<void(int x_px, int y_px)> UnhandledTapCallback;
 
 // Binds a Mojo unhandled-tap notifier message-handler to the frame host
 // observed by this observer.
diff --git a/chrome/browser/ash/app_mode/kiosk_app_manager_browsertest.cc b/chrome/browser/ash/app_mode/kiosk_app_manager_browsertest.cc
index fbc9b97a..c81c6bf 100644
--- a/chrome/browser/ash/app_mode/kiosk_app_manager_browsertest.cc
+++ b/chrome/browser/ash/app_mode/kiosk_app_manager_browsertest.cc
@@ -326,20 +326,18 @@
     dict_update->SetKey(KioskAppDataBase::kKeyApps, std::move(apps_dict));
 
     // Make the app appear in device settings.
-    base::ListValue device_local_accounts;
-    std::unique_ptr<base::DictionaryValue> entry(new base::DictionaryValue);
+    base::Value::List device_local_accounts;
+    base::Value::Dict entry;
     // Fake an account id. Note this needs to match GenerateKioskAppAccountId
     // in kiosk_app_manager.cc to make SetAutoLaunchApp work with the
     // existing app entry created here.
-    entry->SetKey(kAccountsPrefDeviceLocalAccountsKeyId,
-                  base::Value(app_id + "@kiosk-apps"));
-    entry->SetKey(kAccountsPrefDeviceLocalAccountsKeyType,
-                  base::Value(policy::DeviceLocalAccount::TYPE_KIOSK_APP));
-    entry->SetKey(kAccountsPrefDeviceLocalAccountsKeyKioskAppId,
-                  base::Value(app_id));
+    entry.Set(kAccountsPrefDeviceLocalAccountsKeyId, app_id + "@kiosk-apps");
+    entry.Set(kAccountsPrefDeviceLocalAccountsKeyType,
+              policy::DeviceLocalAccount::TYPE_KIOSK_APP);
+    entry.Set(kAccountsPrefDeviceLocalAccountsKeyKioskAppId, app_id);
     device_local_accounts.Append(std::move(entry));
     owner_settings_service_->Set(kAccountsPrefDeviceLocalAccounts,
-                                 device_local_accounts);
+                                 base::Value(std::move(device_local_accounts)));
   }
 
   bool GetCachedCrx(const std::string& app_id,
diff --git a/chrome/browser/ash/app_mode/startup_app_launcher_unittest.cc b/chrome/browser/ash/app_mode/startup_app_launcher_unittest.cc
index 0c1a892..fbe8e46 100644
--- a/chrome/browser/ash/app_mode/startup_app_launcher_unittest.cc
+++ b/chrome/browser/ash/app_mode/startup_app_launcher_unittest.cc
@@ -523,17 +523,17 @@
         false /*create_service*/);
     accounts_settings_helper_->ReplaceDeviceSettingsProviderWithStub();
 
-    auto account = std::make_unique<base::DictionaryValue>();
-    account->SetKey(kAccountsPrefDeviceLocalAccountsKeyId,
-                    base::Value(kTestUserAccount));
-    account->SetKey(kAccountsPrefDeviceLocalAccountsKeyType,
-                    base::Value(policy::DeviceLocalAccount::TYPE_KIOSK_APP));
-    account->SetKey(kAccountsPrefDeviceLocalAccountsKeyKioskAppId,
-                    base::Value(kTestPrimaryAppId));
-    base::ListValue accounts;
+    base::Value::Dict account;
+    account.Set(kAccountsPrefDeviceLocalAccountsKeyId, kTestUserAccount);
+    account.Set(kAccountsPrefDeviceLocalAccountsKeyType,
+                policy::DeviceLocalAccount::TYPE_KIOSK_APP);
+    account.Set(kAccountsPrefDeviceLocalAccountsKeyKioskAppId,
+                kTestPrimaryAppId);
+    base::Value::List accounts;
     accounts.Append(std::move(account));
 
-    accounts_settings_helper_->Set(kAccountsPrefDeviceLocalAccounts, accounts);
+    accounts_settings_helper_->Set(kAccountsPrefDeviceLocalAccounts,
+                                   base::Value(std::move(accounts)));
     accounts_settings_helper_->SetString(
         kAccountsPrefDeviceLocalAccountAutoLoginId, kTestUserAccount);
     accounts_settings_helper_->SetInteger(
diff --git a/chrome/browser/ash/borealis/borealis_app_launcher.cc b/chrome/browser/ash/borealis/borealis_app_launcher.cc
index b6f685f3..646dfbf 100644
--- a/chrome/browser/ash/borealis/borealis_app_launcher.cc
+++ b/chrome/browser/ash/borealis/borealis_app_launcher.cc
@@ -53,7 +53,7 @@
       args.begin(), args.end(),
       google::protobuf::RepeatedFieldBackInserter(request.mutable_files()));
 
-  chromeos::CiceroneClient::Get()->LaunchContainerApplication(
+  ash::CiceroneClient::Get()->LaunchContainerApplication(
       std::move(request),
       base::BindOnce(
           [](OnLaunchedCallback callback,
diff --git a/chrome/browser/ash/borealis/borealis_context_unittest.cc b/chrome/browser/ash/borealis/borealis_context_unittest.cc
index 5d558ff..416885e6 100644
--- a/chrome/browser/ash/borealis/borealis_context_unittest.cc
+++ b/chrome/browser/ash/borealis/borealis_context_unittest.cc
@@ -111,7 +111,7 @@
 }
 
 TEST_F(BorealisContextTest, CiceroneFailure) {
-  auto* cicerone_client = chromeos::FakeCiceroneClient::Get();
+  auto* cicerone_client = ash::FakeCiceroneClient::Get();
 
   cicerone_client->NotifyCiceroneStopped();
   histogram_tester_.ExpectUniqueSample(
diff --git a/chrome/browser/ash/borealis/borealis_launch_watcher.cc b/chrome/browser/ash/borealis/borealis_launch_watcher.cc
index 0fa7e114..3532252 100644
--- a/chrome/browser/ash/borealis/borealis_launch_watcher.cc
+++ b/chrome/browser/ash/borealis/borealis_launch_watcher.cc
@@ -13,11 +13,11 @@
                                              std::string vm_name)
     : owner_id_(ash::ProfileHelper::GetUserIdHashFromProfile(profile)),
       vm_name_(vm_name) {
-  chromeos::CiceroneClient::Get()->AddObserver(this);
+  ash::CiceroneClient::Get()->AddObserver(this);
 }
 
 BorealisLaunchWatcher::~BorealisLaunchWatcher() {
-  chromeos::CiceroneClient::Get()->RemoveObserver(this);
+  ash::CiceroneClient::Get()->RemoveObserver(this);
 }
 
 void BorealisLaunchWatcher::AwaitLaunch(OnLaunchCallback callback) {
diff --git a/chrome/browser/ash/borealis/borealis_launch_watcher.h b/chrome/browser/ash/borealis/borealis_launch_watcher.h
index 22b9f04..8795282 100644
--- a/chrome/browser/ash/borealis/borealis_launch_watcher.h
+++ b/chrome/browser/ash/borealis/borealis_launch_watcher.h
@@ -16,7 +16,7 @@
 
 // Watches to see if a specified VM (from a given owner id and vm name) was
 // started.
-class BorealisLaunchWatcher : public chromeos::CiceroneClient::Observer {
+class BorealisLaunchWatcher : public ash::CiceroneClient::Observer {
  public:
   using OnLaunchCallback =
       base::OnceCallback<void(absl::optional<std::string>)>;
diff --git a/chrome/browser/ash/borealis/testing/dbus.cc b/chrome/browser/ash/borealis/testing/dbus.cc
index 81b2d66d..06a2e7d5 100644
--- a/chrome/browser/ash/borealis/testing/dbus.cc
+++ b/chrome/browser/ash/borealis/testing/dbus.cc
@@ -22,15 +22,15 @@
 
 FakeCiceroneHelper::FakeCiceroneHelper(BasicDBusHelper* basic_helper) {
   DCHECK(basic_helper);
-  chromeos::CiceroneClient::InitializeFake();
+  ash::CiceroneClient::InitializeFake();
 }
 
 FakeCiceroneHelper::~FakeCiceroneHelper() {
-  chromeos::CiceroneClient::Shutdown();
+  ash::CiceroneClient::Shutdown();
 }
 
-chromeos::FakeCiceroneClient* FakeCiceroneHelper::FakeCiceroneClient() {
-  return chromeos::FakeCiceroneClient::Get();
+ash::FakeCiceroneClient* FakeCiceroneHelper::FakeCiceroneClient() {
+  return ash::FakeCiceroneClient::Get();
 }
 
 FakeSeneschalHelper::FakeSeneschalHelper(BasicDBusHelper* basic_helper) {
diff --git a/chrome/browser/ash/borealis/testing/dbus.h b/chrome/browser/ash/borealis/testing/dbus.h
index ee657af..9dd0aff 100644
--- a/chrome/browser/ash/borealis/testing/dbus.h
+++ b/chrome/browser/ash/borealis/testing/dbus.h
@@ -6,12 +6,12 @@
 #define CHROME_BROWSER_ASH_BOREALIS_TESTING_DBUS_H_
 
 namespace ash {
+class FakeCiceroneClient;
 class FakeConciergeClient;
 class FakeSeneschalClient;
 }  // namespace ash
 
 namespace chromeos {
-class FakeCiceroneClient;
 class FakeDlcserviceClient;
 }  // namespace chromeos
 
@@ -29,7 +29,7 @@
   ~FakeCiceroneHelper();
 
   // Returns a handle to the dbus fake for cicerone.
-  chromeos::FakeCiceroneClient* FakeCiceroneClient();
+  ash::FakeCiceroneClient* FakeCiceroneClient();
 };
 
 class FakeSeneschalHelper {
diff --git a/chrome/browser/ash/chrome_browser_main_parts_ash.cc b/chrome/browser/ash/chrome_browser_main_parts_ash.cc
index 5126a37..d9b4cfd3 100644
--- a/chrome/browser/ash/chrome_browser_main_parts_ash.cc
+++ b/chrome/browser/ash/chrome_browser_main_parts_ash.cc
@@ -1624,7 +1624,9 @@
     //
     // When status is PERMANENTLY_UNTRUSTED, client assumes this status is final
     // until browser restarts. Client does not proceed without signature
-    // verification, so retry is not attempted.
+    // verification, so retry is not attempted. This status may be caused
+    // if device is running pre-OOBE, or if the policy proto blob fails the
+    // signature check.
     return;
   }
 
@@ -1632,7 +1634,15 @@
   device_activity_controller_ =
       std::make_unique<device_activity::DeviceActivityController>(
           device_activity::ChromeDeviceMetadataParameters{
-              chrome::GetChannel() /* chromeos_channel */},
+              chrome::GetChannel() /* chromeos_channel */,
+              device_activity::DeviceActivityController::GetMarketSegment(
+                  g_browser_process->platform_part()
+                      ->browser_policy_connector_ash()
+                      ->GetDeviceMode(),
+                  g_browser_process->platform_part()
+                      ->browser_policy_connector_ash()
+                      ->GetEnterpriseMarketSegment()) /* market_segment */,
+          },
           g_browser_process->local_state(),
           g_browser_process->system_network_context_manager()
               ->GetSharedURLLoaderFactory(),
diff --git a/chrome/browser/ash/crosapi/browser_manager.cc b/chrome/browser/ash/crosapi/browser_manager.cc
index f620afc..b18bfd3 100644
--- a/chrome/browser/ash/crosapi/browser_manager.cc
+++ b/chrome/browser/ash/crosapi/browser_manager.cc
@@ -231,7 +231,6 @@
 // The returns struct is used by the main thread as parameters to launch Lacros.
 LaunchParamsFromBackground DoLacrosBackgroundWorkPreLaunch(
     base::FilePath lacros_dir,
-    bool cleared_user_data_dir,
     bool clear_shared_resource_file) {
   LaunchParamsFromBackground params;
 
@@ -924,14 +923,10 @@
   // the data wipe check logic from `BrowserDataMigrator` to browser_util.
   const std::string user_id_hash = ash::ProfileHelper::GetUserIdHashFromProfile(
       ProfileManager::GetPrimaryUserProfile());
-  // Check if user data directory needs to be wiped for a backward incompatible
-  // update.
-  bool cleared_user_data_dir = !browser_util::IsDataWipeRequired(user_id_hash);
 
   base::ThreadPool::PostTaskAndReplyWithResult(
       FROM_HERE, {base::MayBlock()},
       base::BindOnce(&DoLacrosBackgroundWorkPreLaunch, lacros_path_,
-                     cleared_user_data_dir,
                      is_initial_lacros_launch_after_reboot_),
       base::BindOnce(&BrowserManager::StartWithLogFile,
                      weak_factory_.GetWeakPtr(),
@@ -1335,7 +1330,7 @@
   if (state_ == State::STOPPED && !shutdown_requested_ &&
       (GetLaunchOnLoginPref() || (browser_util::IsLacrosPrimaryBrowser() &&
                                   !IsLoginLacrosOpeningDisabledForTesting()))) {
-    Start(std::move(initial_browser_action));
+    MaybeStart(std::move(initial_browser_action));
   }
 }
 
@@ -1444,7 +1439,6 @@
   if (state_ == State::STOPPED && !shutdown_requested_ &&
       !keep_alive_features_.empty() && !relaunch_requested_) {
     CHECK(browser_util::IsLacrosEnabled());
-    CHECK(browser_util::IsLacrosAllowedToLaunch());
     MaybeStart(browser_util::InitialBrowserAction(
         mojom::InitialBrowserAction::kDoNotOpenWindow));
   }
diff --git a/chrome/browser/ash/crostini/ansible/ansible_management_service.cc b/chrome/browser/ash/crostini/ansible/ansible_management_service.cc
index 8273db3..b87e5aa 100644
--- a/chrome/browser/ash/crostini/ansible/ansible_management_service.cc
+++ b/chrome/browser/ash/crostini/ansible/ansible_management_service.cc
@@ -24,8 +24,8 @@
 
 namespace {
 
-chromeos::CiceroneClient* GetCiceroneClient() {
-  return chromeos::CiceroneClient::Get();
+ash::CiceroneClient* GetCiceroneClient() {
+  return ash::CiceroneClient::Get();
 }
 
 }  // namespace
diff --git a/chrome/browser/ash/crostini/ansible/ansible_management_service_unittest.cc b/chrome/browser/ash/crostini/ansible/ansible_management_service_unittest.cc
index baca0c7..06291d4 100644
--- a/chrome/browser/ash/crostini/ansible/ansible_management_service_unittest.cc
+++ b/chrome/browser/ash/crostini/ansible/ansible_management_service_unittest.cc
@@ -35,7 +35,7 @@
  public:
   AnsibleManagementServiceTest() {
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
 
@@ -67,7 +67,7 @@
 
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/crostini/ansible/ansible_management_test_helper.cc b/chrome/browser/ash/crostini/ansible/ansible_management_test_helper.cc
index 7628e8a0..791b9b91 100644
--- a/chrome/browser/ash/crostini/ansible/ansible_management_test_helper.cc
+++ b/chrome/browser/ash/crostini/ansible/ansible_management_test_helper.cc
@@ -15,7 +15,7 @@
 
 AnsibleManagementTestHelper::AnsibleManagementTestHelper(Profile* profile)
     : profile_(profile) {
-  fake_cicerone_client_ = chromeos::FakeCiceroneClient::Get();
+  fake_cicerone_client_ = ash::FakeCiceroneClient::Get();
 }
 
 void AnsibleManagementTestHelper::SetUpAnsiblePlaybookPreference() {
diff --git a/chrome/browser/ash/crostini/ansible/ansible_management_test_helper.h b/chrome/browser/ash/crostini/ansible/ansible_management_test_helper.h
index f3a34813..f3a0f37 100644
--- a/chrome/browser/ash/crostini/ansible/ansible_management_test_helper.h
+++ b/chrome/browser/ash/crostini/ansible/ansible_management_test_helper.h
@@ -39,7 +39,7 @@
   base::test::ScopedFeatureList scoped_feature_list_;
 
   // Owned by chromeos::DBusThreadManager
-  chromeos::FakeCiceroneClient* fake_cicerone_client_;
+  ash::FakeCiceroneClient* fake_cicerone_client_;
 };
 
 }  // namespace crostini
diff --git a/chrome/browser/ash/crostini/crostini_disk_unittest.cc b/chrome/browser/ash/crostini/crostini_disk_unittest.cc
index 8bfc29c1..2bf46a8 100644
--- a/chrome/browser/ash/crostini/crostini_disk_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_disk_unittest.cc
@@ -55,7 +55,7 @@
  public:
   CrostiniDiskTestDbus() {
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
     fake_concierge_client_ = ash::FakeConciergeClient::Get();
@@ -73,7 +73,7 @@
     profile_.reset();
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/crostini/crostini_export_import.cc b/chrome/browser/ash/crostini/crostini_export_import.cc
index 74b3be7e..d3956c3 100644
--- a/chrome/browser/ash/crostini/crostini_export_import.cc
+++ b/chrome/browser/ash/crostini/crostini_export_import.cc
@@ -684,8 +684,7 @@
 }
 
 bool CrostiniExportImport::GetExportImportOperationStatus() const {
-  ContainerId id(kCrostiniDefaultVmName, kCrostiniDefaultContainerName);
-  return status_trackers_.find(id) != status_trackers_.end();
+  return status_trackers_.size() != 0;
 }
 
 base::WeakPtr<CrostiniExportImportNotificationController>
diff --git a/chrome/browser/ash/crostini/crostini_export_import_unittest.cc b/chrome/browser/ash/crostini/crostini_export_import_unittest.cc
index 4e5e76b..5d363fd 100644
--- a/chrome/browser/ash/crostini/crostini_export_import_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_export_import_unittest.cc
@@ -126,11 +126,11 @@
                               kCrostiniDefaultContainerName),
         custom_container_id_("MyVM", "MyContainer") {
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
     fake_seneschal_client_ = ash::FakeSeneschalClient::Get();
-    fake_cicerone_client_ = chromeos::FakeCiceroneClient::Get();
+    fake_cicerone_client_ = ash::FakeCiceroneClient::Get();
   }
 
   CrostiniExportImportTest(const CrostiniExportImportTest&) = delete;
@@ -139,7 +139,7 @@
   ~CrostiniExportImportTest() override {
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
@@ -186,7 +186,7 @@
  protected:
   Profile* profile() { return profile_.get(); }
 
-  chromeos::FakeCiceroneClient* fake_cicerone_client_;
+  ash::FakeCiceroneClient* fake_cicerone_client_;
   ash::FakeSeneschalClient* fake_seneschal_client_;
 
   std::unique_ptr<TestingProfile> profile_;
diff --git a/chrome/browser/ash/crostini/crostini_installer_unittest.cc b/chrome/browser/ash/crostini/crostini_installer_unittest.cc
index c0561cca..841dce80 100644
--- a/chrome/browser/ash/crostini/crostini_installer_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_installer_unittest.cc
@@ -65,7 +65,7 @@
 
   class WaitingFakeConciergeClient : public ash::FakeConciergeClient {
    public:
-    explicit WaitingFakeConciergeClient(chromeos::FakeCiceroneClient* client)
+    explicit WaitingFakeConciergeClient(ash::FakeCiceroneClient* client)
         : ash::FakeConciergeClient(client) {}
 
     void StartTerminaVm(
@@ -102,8 +102,7 @@
     vm_tools::cicerone::OsRelease os_release;
     os_release.set_id("debian");
     os_release.set_version_id("10");
-    chromeos::FakeCiceroneClient::Get()->set_lxd_container_os_release(
-        os_release);
+    ash::FakeCiceroneClient::Get()->set_lxd_container_os_release(os_release);
   }
 
   void SetUp() override {
@@ -119,10 +118,10 @@
 
     chromeos::DlcserviceClient::InitializeFake();
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     SetOSRelease();
     waiting_fake_concierge_client_ =
-        new WaitingFakeConciergeClient(chromeos::FakeCiceroneClient::Get());
+        new WaitingFakeConciergeClient(ash::FakeCiceroneClient::Get());
 
     ash::SeneschalClient::InitializeFake();
 
@@ -155,7 +154,7 @@
     ash::disks::MockDiskMountManager::Shutdown();
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
     chromeos::DlcserviceClient::Shutdown();
 
diff --git a/chrome/browser/ash/crostini/crostini_low_disk_notification.cc b/chrome/browser/ash/crostini/crostini_low_disk_notification.cc
index 40a542c4..e8f4d55 100644
--- a/chrome/browser/ash/crostini/crostini_low_disk_notification.cc
+++ b/chrome/browser/ash/crostini/crostini_low_disk_notification.cc
@@ -36,8 +36,8 @@
 const uint64_t kNotificationSevereThreshold = 512 << 20;  // 512MB
 constexpr base::TimeDelta kNotificationInterval = base::Minutes(2);
 
-chromeos::CiceroneClient* GetCiceroneClient() {
-  return chromeos::CiceroneClient::Get();
+ash::CiceroneClient* GetCiceroneClient() {
+  return ash::CiceroneClient::Get();
 }
 
 }  // namespace
diff --git a/chrome/browser/ash/crostini/crostini_low_disk_notification.h b/chrome/browser/ash/crostini/crostini_low_disk_notification.h
index 0cb1767..a2fad1c0 100644
--- a/chrome/browser/ash/crostini/crostini_low_disk_notification.h
+++ b/chrome/browser/ash/crostini/crostini_low_disk_notification.h
@@ -26,7 +26,7 @@
 // class should be created after DBus has been initialized and destroyed before
 // DBus has been shutdown.
 // This class must be instantiated on the UI thread.
-class CrostiniLowDiskNotification : public chromeos::CiceroneClient::Observer {
+class CrostiniLowDiskNotification : public ash::CiceroneClient::Observer {
  public:
   // Registers this class as a Cicerone Observer.
   CrostiniLowDiskNotification();
@@ -38,7 +38,7 @@
   // Unregisters from observing events.
   ~CrostiniLowDiskNotification() override;
 
-  // chromeos::CiceroneClient::Observer override.
+  // ash::CiceroneClient::Observer override.
   void OnLowDiskSpaceTriggered(
       const vm_tools::cicerone::LowDiskSpaceTriggeredSignal& signal) override;
 
diff --git a/chrome/browser/ash/crostini/crostini_low_disk_notification_unittest.cc b/chrome/browser/ash/crostini/crostini_low_disk_notification_unittest.cc
index e9028d2..f91fa9f 100644
--- a/chrome/browser/ash/crostini/crostini_low_disk_notification_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_low_disk_notification_unittest.cc
@@ -39,7 +39,7 @@
   void SetUp() override {
     BrowserWithTestWindowTest::SetUp();
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
 
@@ -71,7 +71,7 @@
     low_disk_notification_.reset();
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
     BrowserWithTestWindowTest::TearDown();
   }
diff --git a/chrome/browser/ash/crostini/crostini_manager.cc b/chrome/browser/ash/crostini/crostini_manager.cc
index 93d666c..d7a6503 100644
--- a/chrome/browser/ash/crostini/crostini_manager.cc
+++ b/chrome/browser/ash/crostini/crostini_manager.cc
@@ -86,8 +86,8 @@
 
 namespace {
 
-chromeos::CiceroneClient* GetCiceroneClient() {
-  return chromeos::CiceroneClient::Get();
+ash::CiceroneClient* GetCiceroneClient() {
+  return ash::CiceroneClient::Get();
 }
 
 ash::ConciergeClient* GetConciergeClient() {
diff --git a/chrome/browser/ash/crostini/crostini_manager.h b/chrome/browser/ash/crostini/crostini_manager.h
index 20f4a2c8..d60e841 100644
--- a/chrome/browser/ash/crostini/crostini_manager.h
+++ b/chrome/browser/ash/crostini/crostini_manager.h
@@ -156,7 +156,7 @@
                         public chromeos::AnomalyDetectorClient::Observer,
                         public ash::ConciergeClient::VmObserver,
                         public ash::ConciergeClient::ContainerObserver,
-                        public chromeos::CiceroneClient::Observer,
+                        public ash::CiceroneClient::Observer,
                         public chromeos::NetworkStateHandlerObserver,
                         public chromeos::PowerManagerClient::Observer {
  public:
diff --git a/chrome/browser/ash/crostini/crostini_manager_unittest.cc b/chrome/browser/ash/crostini/crostini_manager_unittest.cc
index a63c7ae..7c47d95 100644
--- a/chrome/browser/ash/crostini/crostini_manager_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_manager_unittest.cc
@@ -194,10 +194,10 @@
             TestingBrowserProcess::GetGlobal())),
         browser_part_(g_browser_process->platform_part()) {
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
-    fake_cicerone_client_ = chromeos::FakeCiceroneClient::Get();
+    fake_cicerone_client_ = ash::FakeCiceroneClient::Get();
     fake_concierge_client_ = ash::FakeConciergeClient::Get();
     fake_anomaly_detector_client_ =
         static_cast<chromeos::FakeAnomalyDetectorClient*>(
@@ -210,7 +210,7 @@
   ~CrostiniManagerTest() override {
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
@@ -288,7 +288,7 @@
         user_manager::UserManager::Get());
   }
 
-  chromeos::FakeCiceroneClient* fake_cicerone_client_;
+  ash::FakeCiceroneClient* fake_cicerone_client_;
   ash::FakeConciergeClient* fake_concierge_client_;
   // Owned by chromeos::DBusThreadManager
   chromeos::FakeAnomalyDetectorClient* fake_anomaly_detector_client_;
@@ -2356,9 +2356,9 @@
   void SendProgressSignal() {
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE,
-        base::BindOnce(
-            &chromeos::FakeCiceroneClient::NotifyUpgradeContainerProgress,
-            base::Unretained(fake_cicerone_client_), progress_signal_));
+        base::BindOnce(&ash::FakeCiceroneClient::NotifyUpgradeContainerProgress,
+                       base::Unretained(fake_cicerone_client_),
+                       progress_signal_));
   }
 
  protected:
diff --git a/chrome/browser/ash/crostini/crostini_package_notification_unittest.cc b/chrome/browser/ash/crostini/crostini_package_notification_unittest.cc
index 233d752..303ca16 100644
--- a/chrome/browser/ash/crostini/crostini_package_notification_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_package_notification_unittest.cc
@@ -35,7 +35,7 @@
 
   void SetUp() override {
     DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
     task_environment_ = std::make_unique<content::BrowserTaskEnvironment>(
@@ -56,7 +56,7 @@
     task_environment_.reset();
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/crostini/crostini_package_service_unittest.cc b/chrome/browser/ash/crostini/crostini_package_service_unittest.cc
index 1bb82a68..f474b71 100644
--- a/chrome/browser/ash/crostini/crostini_package_service_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_package_service_unittest.cc
@@ -44,11 +44,11 @@
 
 namespace {
 
+using ::ash::FakeCiceroneClient;
 using ::ash::FakeConciergeClient;
 using ::ash::FakeSeneschalClient;
 using ::chromeos::DBusMethodCallback;
 using ::chromeos::DBusThreadManager;
-using ::chromeos::FakeCiceroneClient;
 using ::testing::_;
 using ::testing::Invoke;
 using ::testing::IsEmpty;
@@ -175,10 +175,10 @@
   void SetUp() override {
     DBusThreadManager::Initialize();
 
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
-    fake_cicerone_client_ = chromeos::FakeCiceroneClient::Get();
+    fake_cicerone_client_ = ash::FakeCiceroneClient::Get();
     ASSERT_TRUE(fake_cicerone_client_);
     fake_seneschal_client_ = FakeSeneschalClient::Get();
     ASSERT_TRUE(fake_seneschal_client_);
@@ -235,7 +235,7 @@
     task_environment_.reset();
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/crostini/crostini_port_forwarder_unittest.cc b/chrome/browser/ash/crostini/crostini_port_forwarder_unittest.cc
index 4620daf..b932793 100644
--- a/chrome/browser/ash/crostini/crostini_port_forwarder_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_port_forwarder_unittest.cc
@@ -44,7 +44,7 @@
 
   void SetUp() override {
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
     chromeos::PermissionBrokerClient::InitializeFake();
@@ -69,7 +69,7 @@
     profile_.reset();
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/crostini/crostini_sshfs_unittest.cc b/chrome/browser/ash/crostini/crostini_sshfs_unittest.cc
index 78c27e8..af52247 100644
--- a/chrome/browser/ash/crostini/crostini_sshfs_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_sshfs_unittest.cc
@@ -71,7 +71,7 @@
  public:
   CrostiniSshfsHelperTest() {
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
     profile_ = std::make_unique<TestingProfile>();
@@ -109,7 +109,7 @@
     profile_.reset();
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/crostini/crostini_upgrade_available_notification_unittest.cc b/chrome/browser/ash/crostini/crostini_upgrade_available_notification_unittest.cc
index ebe9170a..fd9b365 100644
--- a/chrome/browser/ash/crostini/crostini_upgrade_available_notification_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_upgrade_available_notification_unittest.cc
@@ -52,7 +52,7 @@
   void SetUp() override {
     BrowserWithTestWindowTest::SetUp();
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
 
@@ -68,7 +68,7 @@
     BrowserWithTestWindowTest::TearDown();
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/crostini/crostini_util_unittest.cc b/chrome/browser/ash/crostini/crostini_util_unittest.cc
index caf009c9..7078a56 100644
--- a/chrome/browser/ash/crostini/crostini_util_unittest.cc
+++ b/chrome/browser/ash/crostini/crostini_util_unittest.cc
@@ -47,7 +47,7 @@
             TestingBrowserProcess::GetGlobal())),
         browser_part_(g_browser_process->platform_part()) {
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
 
@@ -57,7 +57,7 @@
   ~CrostiniUtilTest() override {
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/dbus/ash_dbus_helper.cc b/chrome/browser/ash/dbus/ash_dbus_helper.cc
index bf32c29..1f5003a 100644
--- a/chrome/browser/ash/dbus/ash_dbus_helper.cc
+++ b/chrome/browser/ash/dbus/ash_dbus_helper.cc
@@ -115,7 +115,7 @@
   InitializeDBusClient<AuthPolicyClient>(bus);
   InitializeDBusClient<BiodClient>(bus);  // For device::Fingerprint.
   InitializeDBusClient<chromeos::CdmFactoryDaemonClient>(bus);
-  InitializeDBusClient<chromeos::CiceroneClient>(bus);
+  InitializeDBusClient<CiceroneClient>(bus);
   // ConciergeClient depends on CiceroneClient.
   InitializeDBusClient<ConciergeClient>(bus);
   InitializeDBusClient<chromeos::CrasAudioClient>(bus);
@@ -250,7 +250,7 @@
   cros_healthd::CrosHealthdClient::Shutdown();
   chromeos::CrasAudioClient::Shutdown();
   ConciergeClient::Shutdown();
-  chromeos::CiceroneClient::Shutdown();
+  CiceroneClient::Shutdown();
   chromeos::CdmFactoryDaemonClient::Shutdown();
   BiodClient::Shutdown();
   AuthPolicyClient::Shutdown();
diff --git a/chrome/browser/ash/dbus/vm/vm_applications_service_provider.cc b/chrome/browser/ash/dbus/vm/vm_applications_service_provider.cc
index 1f42232..09d68e5a2 100644
--- a/chrome/browser/ash/dbus/vm/vm_applications_service_provider.cc
+++ b/chrome/browser/ash/dbus/vm/vm_applications_service_provider.cc
@@ -308,7 +308,7 @@
             for (const auto& file_url : file_urls) {
               data->signal.add_files(file_url);
             }
-            chromeos::CiceroneClient::Get()->FileSelected(data->signal);
+            CiceroneClient::Get()->FileSelected(data->signal);
           },
           std::move(data)));
 }
diff --git a/chrome/browser/ash/exo/chrome_data_exchange_delegate_unittest.cc b/chrome/browser/ash/exo/chrome_data_exchange_delegate_unittest.cc
index 3858ac5..de8cb87 100644
--- a/chrome/browser/ash/exo/chrome_data_exchange_delegate_unittest.cc
+++ b/chrome/browser/ash/exo/chrome_data_exchange_delegate_unittest.cc
@@ -73,7 +73,7 @@
  public:
   void SetUp() override {
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ConciergeClient::InitializeFake();
     SeneschalClient::InitializeFake();
 
@@ -119,7 +119,7 @@
     profile_.reset();
     SeneschalClient::Shutdown();
     ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/file_manager/file_browser_handlers.cc b/chrome/browser/ash/file_manager/file_browser_handlers.cc
index 87216b98..ef24c559 100644
--- a/chrome/browser/ash/file_manager/file_browser_handlers.cc
+++ b/chrome/browser/ash/file_manager/file_browser_handlers.cc
@@ -339,21 +339,20 @@
   SetupHandlerHostFileAccessPermissions(
       file_definition_list.get(), extension_.get(), handler_pid);
 
-  std::unique_ptr<base::ListValue> event_args(new base::ListValue());
-  event_args->Append(action_id_);
-  auto details = std::make_unique<base::DictionaryValue>();
+  std::vector<base::Value> event_args;
+  event_args.emplace_back(action_id_);
+  base::Value::Dict details;
   // Get file definitions. These will be replaced with Entry instances by
   // dispatchEvent() method from event_binding.js.
   auto file_entries = file_manager::util::ConvertEntryDefinitionListToListValue(
       *entry_definition_list);
 
-  details->SetKey("entries",
-                  base::Value::FromUniquePtrValue(std::move(file_entries)));
-  event_args->Append(std::move(details));
+  details.Set("entries",
+              base::Value::FromUniquePtrValue(std::move(file_entries)));
+  event_args.emplace_back(std::move(details));
   auto event = std::make_unique<extensions::Event>(
       extensions::events::FILE_BROWSER_HANDLER_ON_EXECUTE,
-      "fileBrowserHandler.onExecute",
-      std::move(*event_args).TakeListDeprecated(), profile_);
+      "fileBrowserHandler.onExecute", std::move(event_args), profile_);
   router->DispatchEventToExtension(extension_->id(), std::move(event));
 
   ExecuteDoneOnUIThread(true, "");
diff --git a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
index b91190f..37add5ba 100644
--- a/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/ash/file_manager/file_manager_browsertest_base.cc
@@ -73,6 +73,7 @@
 #include "chrome/browser/ash/file_manager/volume_manager.h"
 #include "chrome/browser/ash/guest_os/public/guest_os_mount_provider.h"
 #include "chrome/browser/ash/guest_os/public/guest_os_service.h"
+#include "chrome/browser/ash/guest_os/public/types.h"
 #include "chrome/browser/ash/smb_client/smb_service.h"
 #include "chrome/browser/ash/smb_client/smb_service_factory.h"
 #include "chrome/browser/browser_process.h"
@@ -1704,13 +1705,16 @@
     return crostini::ContainerId::GetDefault();
   }
 
+  guest_os::VmType vm_type() override {
+    return guest_os::VmType::ApplicationList_VmType_TERMINA;
+  }
+
   int cid_;
   int cid() override { return cid_; }
 
  private:
   Profile* profile_;
   std::string name_;
-  std::unique_ptr<GuestOsTestVolume> volume_;
 };
 
 // GuestOsTestVolume: local test volume for the "Guest OS" directories.
diff --git a/chrome/browser/ash/file_manager/file_watcher_unittest.cc b/chrome/browser/ash/file_manager/file_watcher_unittest.cc
index 34e9f2bf..2c534ee 100644
--- a/chrome/browser/ash/file_manager/file_watcher_unittest.cc
+++ b/chrome/browser/ash/file_manager/file_watcher_unittest.cc
@@ -36,7 +36,7 @@
   FileManagerFileWatcherTest()
       : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP) {
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
   }
@@ -44,7 +44,7 @@
   ~FileManagerFileWatcherTest() override {
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
@@ -207,8 +207,8 @@
 }
 
 TEST_F(FileManagerFileWatcherTest, WatchCrostiniFile) {
-  chromeos::FakeCiceroneClient* fake_cicerone_client =
-      chromeos::FakeCiceroneClient::Get();
+  ash::FakeCiceroneClient* fake_cicerone_client =
+      ash::FakeCiceroneClient::Get();
 
   const base::FilePath kVirtualPath("foo/bar.txt");
   const char kExtensionId[] = "extension-id";
diff --git a/chrome/browser/ash/file_manager/fileapi_util.cc b/chrome/browser/ash/file_manager/fileapi_util.cc
index f5cffba..ecc47929 100644
--- a/chrome/browser/ash/file_manager/fileapi_util.cc
+++ b/chrome/browser/ash/file_manager/fileapi_util.cc
@@ -623,7 +623,8 @@
   auto entries = std::make_unique<base::ListValue>();
   for (auto it = entry_definition_list.begin();
        it != entry_definition_list.end(); ++it) {
-    entries->Append(ConvertEntryDefinitionToValue(*it));
+    entries->GetList().Append(
+        base::Value::FromUniquePtrValue(ConvertEntryDefinitionToValue(*it)));
   }
   return entries;
 }
diff --git a/chrome/browser/ash/file_manager/path_util.h b/chrome/browser/ash/file_manager/path_util.h
index 276a27c..afa501c 100644
--- a/chrome/browser/ash/file_manager/path_util.h
+++ b/chrome/browser/ash/file_manager/path_util.h
@@ -182,6 +182,11 @@
     const std::vector<storage::FileSystemURL>& file_system_urls,
     ConvertToContentUrlsCallback callback);
 
+// Replace `prefix` with `replacement` on `s`.
+bool ReplacePrefix(std::string* s,
+                   const std::string& prefix,
+                   const std::string& replacement);
+
 // Convert path into a string suitable for display in settings.
 // Replacements:
 // * /home/chronos/user/Downloads                => Downloads
diff --git a/chrome/browser/ash/file_manager/path_util_unittest.cc b/chrome/browser/ash/file_manager/path_util_unittest.cc
index 0443837..3a57338 100644
--- a/chrome/browser/ash/file_manager/path_util_unittest.cc
+++ b/chrome/browser/ash/file_manager/path_util_unittest.cc
@@ -33,6 +33,7 @@
 #include "chrome/browser/ash/file_manager/volume_manager.h"
 #include "chrome/browser/ash/file_manager/volume_manager_factory.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
+#include "chrome/browser/ash/guest_os/public/types.h"
 #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/chromeos/fileapi/file_system_backend.h"
@@ -324,7 +325,7 @@
 
   // Initialize DBUS and running container.
   chromeos::DBusThreadManager::Initialize();
-  chromeos::CiceroneClient::InitializeFake();
+  ash::CiceroneClient::InitializeFake();
   ash::ConciergeClient::InitializeFake();
   ash::SeneschalClient::InitializeFake();
 
@@ -807,7 +808,7 @@
 
 TEST_F(FileManagerPathUtilConvertUrlTest, ConvertPathToArcUrl_MyDriveArcvm) {
   chromeos::DBusThreadManager::Initialize();
-  chromeos::CiceroneClient::InitializeFake();
+  ash::CiceroneClient::InitializeFake();
   ash::ConciergeClient::InitializeFake();
   ash::SeneschalClient::InitializeFake();
 
@@ -1157,7 +1158,8 @@
 
   volume_manager->AddVolumeForTesting(Volume::CreateForSftpGuestOs(
       "guest_os_label", base::FilePath("/mount_path/guest_os"),
-      base::FilePath("/remote_mount_path/guest_os")));
+      base::FilePath("/remote_mount_path/guest_os"),
+      guest_os::VmType::ApplicationList_VmType_TERMINA));
 
   volume_manager->AddVolumeForTesting(Volume::CreateForMTP(
       base::FilePath("/mount_path/mtp"), "mtp_label", false));
diff --git a/chrome/browser/ash/file_manager/trash_io_task.cc b/chrome/browser/ash/file_manager/trash_io_task.cc
index 53156af..37dff1f 100644
--- a/chrome/browser/ash/file_manager/trash_io_task.cc
+++ b/chrome/browser/ash/file_manager/trash_io_task.cc
@@ -5,17 +5,40 @@
 #include "chrome/browser/ash/file_manager/trash_io_task.h"
 
 #include "base/callback.h"
+#include "base/strings/strcat.h"
+#include "base/system/sys_info.h"
+#include "base/time/time_to_iso8601.h"
+#include "chrome/browser/ash/file_manager/path_util.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 
 namespace file_manager {
 namespace io_task {
 
+constexpr char kTrashFolderName[] = ".Trash";
+
+namespace {
+
+const std::string GenerateTrashInfoContents(
+    const std::string& relative_restore_path,
+    const base::Time& deletion_time) {
+  return base::StrCat({"[Trash Info]\nPath=", relative_restore_path,
+                       "\nDeletionDate=", base::TimeToISO8601(deletion_time)});
+}
+
+TrashEntry::TrashEntry() : deletion_time(base::Time::Now()) {}
+TrashEntry::~TrashEntry() = default;
+
+TrashEntry::TrashEntry(TrashEntry&& other) = default;
+TrashEntry& TrashEntry::operator=(TrashEntry&& other) = default;
+
+}  // namespace
+
 TrashIOTask::TrashIOTask(
     std::vector<storage::FileSystemURL> file_urls,
     Profile* profile,
     scoped_refptr<storage::FileSystemContext> file_system_context)
-    : file_system_context_(file_system_context) {
+    : profile_(profile), file_system_context_(file_system_context) {
   progress_.state = State::kQueued;
   progress_.type = OperationType::kTrash;
   progress_.bytes_transferred = 0;
@@ -23,6 +46,7 @@
 
   for (const auto& url : file_urls) {
     progress_.sources.emplace_back(url, absl::nullopt);
+    trash_entries_.emplace_back();
   }
 }
 
@@ -44,6 +68,76 @@
                           IOTask::CompleteCallback complete_callback) {
   progress_callback_ = std::move(progress_callback);
   complete_callback_ = std::move(complete_callback);
+
+  if (!ConstructTrashEntries()) {
+    Complete(State::kError);
+    return;
+  }
+
+  // TODO(b/231250202): Implement verification of free disk space.
+  Complete(State::kSuccess);
+}
+
+// Calls the completion callback for the task. |progress_| should not be
+// accessed after calling this.
+void TrashIOTask::Complete(State state) {
+  progress_.state = state;
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(std::move(complete_callback_), std::move(progress_)));
+}
+
+bool TrashIOTask::ConstructTrashEntries() {
+  const base::FilePath my_files_path =
+      util::GetMyFilesFolderForProfile(profile_);
+  const base::FilePath downloads_path =
+      util::GetDownloadsFolderForProfile(profile_);
+
+  for (int i = 0; i < progress_.sources.size(); i++) {
+    base::FilePath source_path = progress_.sources[i].url.path();
+    if (downloads_path.IsParent(source_path) &&
+        UpdateTrashEntryAndIncrementRequiredSpace(i, downloads_path)) {
+      continue;
+    }
+
+    if (my_files_path.IsParent(source_path) &&
+        UpdateTrashEntryAndIncrementRequiredSpace(i, my_files_path)) {
+      continue;
+    }
+
+    // One of the selected files is unable to be trashed.
+    return false;
+  }
+
+  return true;
+}
+
+bool TrashIOTask::UpdateTrashEntryAndIncrementRequiredSpace(
+    size_t idx,
+    const base::FilePath& trash_parent_path) {
+  base::FilePath source_path = progress_.sources[idx].url.path();
+  std::string relative_restore_path = source_path.value();
+  if (!file_manager::util::ReplacePrefix(&relative_restore_path,
+                                         trash_parent_path.value(), "")) {
+    return false;
+  }
+
+  TrashEntry& entry = trash_entries_[idx];
+  entry.trash_path = trash_parent_path.Append(kTrashFolderName);
+  entry.trash_info_contents =
+      GenerateTrashInfoContents(relative_restore_path, entry.deletion_time);
+
+  size_t trash_contents_size = entry.trash_info_contents.size();
+  progress_.total_bytes += trash_contents_size;
+
+  const auto& required_size = required_sizes_.find(trash_parent_path);
+  if (required_size != required_sizes_.end()) {
+    required_size->second += trash_contents_size;
+    return true;
+  }
+
+  required_sizes_.emplace(trash_parent_path, trash_contents_size);
+  return true;
 }
 
 void TrashIOTask::Cancel() {
diff --git a/chrome/browser/ash/file_manager/trash_io_task.h b/chrome/browser/ash/file_manager/trash_io_task.h
index 3fe2cf7c..f8b0657 100644
--- a/chrome/browser/ash/file_manager/trash_io_task.h
+++ b/chrome/browser/ash/file_manager/trash_io_task.h
@@ -5,9 +5,12 @@
 #ifndef CHROME_BROWSER_ASH_FILE_MANAGER_TRASH_IO_TASK_H_
 #define CHROME_BROWSER_ASH_FILE_MANAGER_TRASH_IO_TASK_H_
 
+#include <memory>
 #include <vector>
 
+#include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
 #include "chrome/browser/ash/file_manager/io_task.h"
 #include "storage/browser/file_system/file_system_context.h"
 #include "storage/browser/file_system/file_system_operation_runner.h"
@@ -19,6 +22,33 @@
 namespace file_manager {
 namespace io_task {
 
+namespace {
+
+struct TrashEntry {
+  TrashEntry();
+  ~TrashEntry();
+
+  TrashEntry(TrashEntry&& other);
+  TrashEntry& operator=(TrashEntry&& other);
+
+  // The final location for the trashed file, this may not be the same as the
+  // `url` in the case of naming conflicts.
+  base::FilePath trash_path;
+
+  // The date of deletion, stored in the metadata file to help scheduled
+  // cleanup.
+  base::Time deletion_time;
+
+  // The contents of the .trashinfo file containing the deletion date and
+  // original path of the trashed file.
+  std::string trash_info_contents;
+};
+
+}  // namespace
+
+// Constant representing the Trash folder name.
+extern const char kTrashFolderName[];
+
 // This class represents a trash task. A trash task attempts to trash zero or
 // more files by first moving them to a .Trash/files or .Trash-{UID}/files
 // folder that resides in a parent folder for:
@@ -45,8 +75,24 @@
   void Cancel() override;
 
  private:
+  void Complete(State state);
+  bool ConstructTrashEntries();
+  bool UpdateTrashEntryAndIncrementRequiredSpace(
+      size_t idx,
+      const base::FilePath& trash_parent_path);
+
+  raw_ptr<Profile> profile_;
+
   scoped_refptr<storage::FileSystemContext> file_system_context_;
 
+  // Stores the total size required for all the metadata files, keyed by the
+  // trash location to ensure enough disk space to write.
+  std::vector<TrashEntry> trash_entries_;
+
+  // Stores the required size per parent location to ensure the IOTask has
+  // enough available space to write out the metadata.
+  std::map<base::FilePath, int64_t> required_sizes_;
+
   // Stores the id of the trash operation if one is in progress. Used so the
   // trash can be cancelled.
   absl::optional<storage::FileSystemOperationRunner::OperationID> operation_id_;
diff --git a/chrome/browser/ash/file_manager/trash_io_task_unittest.cc b/chrome/browser/ash/file_manager/trash_io_task_unittest.cc
new file mode 100644
index 0000000..740f587
--- /dev/null
+++ b/chrome/browser/ash/file_manager/trash_io_task_unittest.cc
@@ -0,0 +1,171 @@
+// Copyright 2022 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/file_manager/trash_io_task.h"
+
+#include "ash/components/disks/disk_mount_manager.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/rand_util.h"
+#include "base/run_loop.h"
+#include "base/test/gmock_callback_support.h"
+#include "base/test/mock_callback.h"
+#include "chrome/browser/ash/file_manager/fake_disk_mount_manager.h"
+#include "chrome/browser/ash/file_manager/path_util.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/browser_task_environment.h"
+#include "storage/browser/file_system/external_mount_points.h"
+#include "storage/browser/test/test_file_system_context.h"
+#include "storage/common/file_system/file_system_mount_option.h"
+#include "storage/common/file_system/file_system_types.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/storage_key/storage_key.h"
+
+namespace file_manager {
+namespace io_task {
+namespace {
+
+using ::base::test::RunClosure;
+using ::testing::_;
+using ::testing::Field;
+using ::testing::Return;
+
+constexpr size_t kTestFileSize = 32;
+
+class TrashIOTaskTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    file_system_context_ = storage::CreateFileSystemContextForTesting(
+        nullptr, temp_dir_.GetPath());
+    profile_ =
+        std::make_unique<TestingProfile>(base::FilePath(temp_dir_.GetPath()));
+
+    // Register `temp_dir_` as the parent directory for Downloads.
+    storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
+        file_manager::util::GetDownloadsMountPointName(profile_.get()),
+        storage::kFileSystemTypeLocal, storage::FileSystemMountOption(),
+        temp_dir_.GetPath());
+
+    // Create Downloads inside the `temp_dir_`.
+    downloads_dir_ = temp_dir_.GetPath().Append(
+        file_manager::util::GetDownloadsMountPointName(profile_.get()));
+    ASSERT_TRUE(base::CreateDirectory(downloads_dir_));
+  }
+
+  void TearDown() override {
+    // Ensure any previously registered mount points for Downloads are revoked.
+    storage::ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
+        file_manager::util::GetDownloadsMountPointName(profile_.get()));
+  }
+
+  storage::FileSystemURL CreateFileSystemURL(const std::string& path) {
+    return file_system_context_->CreateCrackedFileSystemURL(
+        kTestStorageKey, storage::kFileSystemTypeTest,
+        base::FilePath::FromUTF8Unsafe(path));
+  }
+
+  content::BrowserTaskEnvironment task_environment_;
+  std::unique_ptr<TestingProfile> profile_;
+  const blink::StorageKey kTestStorageKey =
+      blink::StorageKey::CreateFromStringForTesting("chrome-extension://abc");
+
+  base::ScopedTempDir temp_dir_;
+  base::FilePath downloads_dir_;
+  scoped_refptr<storage::FileSystemContext> file_system_context_;
+};
+
+TEST_F(TrashIOTaskTest, FileInUnsupportedDirectoryShouldError) {
+  base::ScopedTempDir unsupported_dir;
+  ASSERT_TRUE(unsupported_dir.CreateUniqueTempDir());
+
+  std::string foo_contents = base::RandBytesAsString(kTestFileSize);
+  const base::FilePath file_path = unsupported_dir.GetPath().Append("foo.txt");
+  ASSERT_TRUE(base::WriteFile(file_path, foo_contents));
+
+  base::RunLoop run_loop;
+  std::vector<storage::FileSystemURL> source_urls = {
+      CreateFileSystemURL(file_path.value()),
+  };
+
+  base::MockRepeatingCallback<void(const ProgressStatus&)> progress_callback;
+  base::MockOnceCallback<void(ProgressStatus)> complete_callback;
+
+  // Progress callback should not be called as the construction of the IOTask
+  // is expected to fail.
+  EXPECT_CALL(progress_callback, Run(_)).Times(0);
+
+  // We should get one complete callback when the construction of trash entries
+  // fails to finish.
+  EXPECT_CALL(complete_callback,
+              Run(Field(&ProgressStatus::state, State::kError)))
+      .WillOnce(RunClosure(run_loop.QuitClosure()));
+
+  TrashIOTask task(source_urls, profile_.get(), file_system_context_);
+  task.Execute(progress_callback.Get(), complete_callback.Get());
+  run_loop.Run();
+}
+
+TEST_F(TrashIOTaskTest, MixedUnsupportedAndSupportedDirectoriesShouldError) {
+  base::ScopedTempDir unsupported_dir;
+  ASSERT_TRUE(unsupported_dir.CreateUniqueTempDir());
+
+  std::string foo_contents = base::RandBytesAsString(kTestFileSize);
+  const base::FilePath file_path_unsupported =
+      unsupported_dir.GetPath().Append("foo.txt");
+  const base::FilePath file_path_supported = downloads_dir_.Append("bar.txt");
+  ASSERT_TRUE(base::WriteFile(file_path_unsupported, foo_contents));
+  ASSERT_TRUE(base::WriteFile(file_path_supported, foo_contents));
+
+  base::RunLoop run_loop;
+  std::vector<storage::FileSystemURL> source_urls = {
+      CreateFileSystemURL(file_path_unsupported.value()),
+      CreateFileSystemURL(file_path_supported.value()),
+  };
+
+  base::MockRepeatingCallback<void(const ProgressStatus&)> progress_callback;
+  base::MockOnceCallback<void(ProgressStatus)> complete_callback;
+
+  // Progress callback should not be called as the construction of the IOTask
+  // is expected to fail.
+  EXPECT_CALL(progress_callback, Run(_)).Times(0);
+
+  // We should get one complete callback when the construction of trash entries
+  // fails to finish.
+  EXPECT_CALL(complete_callback,
+              Run(Field(&ProgressStatus::state, State::kError)))
+      .WillOnce(RunClosure(run_loop.QuitClosure()));
+
+  TrashIOTask task(source_urls, profile_.get(), file_system_context_);
+  task.Execute(progress_callback.Get(), complete_callback.Get());
+  run_loop.Run();
+}
+
+TEST_F(TrashIOTaskTest, SupportedDirectoryOnly) {
+  std::string foo_contents = base::RandBytesAsString(kTestFileSize);
+  const base::FilePath file_path = downloads_dir_.Append("foo.txt");
+  ASSERT_TRUE(base::WriteFile(file_path, foo_contents));
+
+  base::RunLoop run_loop;
+  std::vector<storage::FileSystemURL> source_urls = {
+      CreateFileSystemURL(file_path.value()),
+  };
+
+  base::MockRepeatingCallback<void(const ProgressStatus&)> progress_callback;
+  base::MockOnceCallback<void(ProgressStatus)> complete_callback;
+
+  // TODO(b/231250202): Update this once the Trash logic has been written.
+  EXPECT_CALL(complete_callback,
+              Run(Field(&ProgressStatus::state, State::kSuccess)))
+      .WillOnce(RunClosure(run_loop.QuitClosure()));
+
+  TrashIOTask task(source_urls, profile_.get(), file_system_context_);
+  task.Execute(progress_callback.Get(), complete_callback.Get());
+  run_loop.Run();
+}
+
+}  // namespace
+}  // namespace io_task
+}  // namespace file_manager
diff --git a/chrome/browser/ash/file_manager/url_util.cc b/chrome/browser/ash/file_manager/url_util.cc
index b303ca5b..6d1efcf 100644
--- a/chrome/browser/ash/file_manager/url_util.cc
+++ b/chrome/browser/ash/file_manager/url_util.cc
@@ -77,64 +77,63 @@
     const std::string& search_query,
     bool show_android_picker_apps,
     std::vector<std::string> volume_filter) {
-  base::DictionaryValue arg_value;
-  arg_value.SetStringKey("type", GetDialogTypeAsString(type));
-  arg_value.SetStringKey("title", title);
-  arg_value.SetStringKey("currentDirectoryURL", current_directory_url.spec());
-  arg_value.SetStringKey("selectionURL", selection_url.spec());
+  base::Value::Dict arg_value;
+  arg_value.Set("type", GetDialogTypeAsString(type));
+  arg_value.Set("title", title);
+  arg_value.Set("currentDirectoryURL", current_directory_url.spec());
+  arg_value.Set("selectionURL", selection_url.spec());
   // |targetName| is only relevant for SaveAs.
   if (type == ui::SelectFileDialog::Type::SELECT_SAVEAS_FILE)
-    arg_value.SetStringKey("targetName", target_name);
-  arg_value.SetStringKey("searchQuery", search_query);
-  arg_value.SetBoolKey("showAndroidPickerApps", show_android_picker_apps);
+    arg_value.Set("targetName", target_name);
+  arg_value.Set("searchQuery", search_query);
+  arg_value.Set("showAndroidPickerApps", show_android_picker_apps);
 
   if (file_types) {
-    base::ListValue types_list;
+    base::Value::List types_list;
     for (size_t i = 0; i < file_types->extensions.size(); ++i) {
-      base::Value extensions_list(base::Value::Type::LIST);
+      base::Value::List extensions_list;
       for (size_t j = 0; j < file_types->extensions[i].size(); ++j)
         extensions_list.Append(file_types->extensions[i][j]);
 
-      auto dict = std::make_unique<base::DictionaryValue>();
-      dict->SetKey("extensions", std::move(extensions_list));
+      base::Value::Dict dict;
+      dict.Set("extensions", std::move(extensions_list));
 
       if (i < file_types->extension_description_overrides.size()) {
         std::u16string desc = file_types->extension_description_overrides[i];
-        dict->SetStringKey("description", desc);
+        dict.Set("description", desc);
       }
 
       // file_type_index is 1-based. 0 means no selection at all.
-      dict->SetBoolKey("selected",
-                       (static_cast<size_t>(file_type_index) == (i + 1)));
+      dict.Set("selected", static_cast<size_t>(file_type_index) == (i + 1));
 
       types_list.Append(std::move(dict));
     }
-    arg_value.SetKey("typeList", std::move(types_list));
+    arg_value.Set("typeList", std::move(types_list));
 
-    arg_value.SetBoolKey("includeAllFiles", file_types->include_all_files);
+    arg_value.Set("includeAllFiles", file_types->include_all_files);
   }
 
   if (file_types) {
     switch (file_types->allowed_paths) {
       case ui::SelectFileDialog::FileTypeInfo::NATIVE_PATH:
-        arg_value.SetStringKey(kAllowedPaths, kNativePath);
+        arg_value.Set(kAllowedPaths, kNativePath);
         break;
       case ui::SelectFileDialog::FileTypeInfo::ANY_PATH:
-        arg_value.SetStringKey(kAllowedPaths, kAnyPath);
+        arg_value.Set(kAllowedPaths, kAnyPath);
         break;
       case ui::SelectFileDialog::FileTypeInfo::ANY_PATH_OR_URL:
-        arg_value.SetStringKey(kAllowedPaths, kAnyPathOrUrl);
+        arg_value.Set(kAllowedPaths, kAnyPathOrUrl);
         break;
     }
   } else {
-    arg_value.SetStringKey(kAllowedPaths, kNativePath);
+    arg_value.Set(kAllowedPaths, kNativePath);
   }
 
   if (!volume_filter.empty()) {
-    base::Value volume_filter_list(base::Value::Type::LIST);
+    base::Value::List volume_filter_list;
     for (const auto& item : volume_filter)
       volume_filter_list.Append(item);
-    arg_value.SetKey("volumeFilter", std::move(volume_filter_list));
+    arg_value.Set("volumeFilter", std::move(volume_filter_list));
   }
 
   std::string json_args;
diff --git a/chrome/browser/ash/file_manager/volume_manager.cc b/chrome/browser/ash/file_manager/volume_manager.cc
index 14756cc..bff46fdb 100644
--- a/chrome/browser/ash/file_manager/volume_manager.cc
+++ b/chrome/browser/ash/file_manager/volume_manager.cc
@@ -43,6 +43,7 @@
 #include "chrome/browser/ash/file_manager/volume_manager_factory.h"
 #include "chrome/browser/ash/file_manager/volume_manager_observer.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
+#include "chrome/browser/ash/guest_os/public/types.h"
 #include "chrome/browser/ash/profiles/profile_helper.h"
 #include "chrome/browser/media_galleries/fileapi/mtp_device_map_service.h"
 #include "chrome/browser/profiles/profile.h"
@@ -444,7 +445,8 @@
 std::unique_ptr<Volume> Volume::CreateForSftpGuestOs(
     const std::string display_name,
     const base::FilePath& sftp_mount_path,
-    const base::FilePath& remote_mount_path) {
+    const base::FilePath& remote_mount_path,
+    const guest_os::VmType vm_type) {
   std::unique_ptr<Volume> volume(new Volume());
   volume->type_ = VOLUME_TYPE_GUEST_OS;
   volume->device_type_ = chromeos::DEVICE_TYPE_UNKNOWN;
@@ -456,6 +458,7 @@
   volume->volume_id_ = GenerateVolumeId(*volume);
   volume->volume_label_ = display_name;
   volume->watchable_ = false;
+  volume->vm_type_ = vm_type;
   return volume;
 }
 
@@ -805,10 +808,11 @@
 void VolumeManager::AddSftpGuestOsVolume(
     const std::string display_name,
     const base::FilePath& sftp_mount_path,
-    const base::FilePath& remote_mount_path) {
+    const base::FilePath& remote_mount_path,
+    const guest_os::VmType vm_type) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   std::unique_ptr<Volume> volume = Volume::CreateForSftpGuestOs(
-      display_name, sftp_mount_path, remote_mount_path);
+      display_name, sftp_mount_path, remote_mount_path, vm_type);
   // Ignore if volume already exists.
   if (mounted_volumes_.find(volume->volume_id()) != mounted_volumes_.end())
     return;
@@ -1695,12 +1699,15 @@
       (error_code == chromeos::MOUNT_ERROR_PATH_NOT_MOUNTED)) {
     // Remove metadata associated with the mount. It will be a no-op if it
     // wasn't mounted or unmounted out of band. We need the VolumeId to be
-    // consistent, which means the mount path needs to be the same. display_name
-    // and remote_mount_path aren't needed and we don't know them at unmount so
-    // leave them blank.
-    DoUnmountEvent(
-        chromeos::MOUNT_ERROR_NONE,
-        *Volume::CreateForSftpGuestOs("", sftp_mount_path, base::FilePath()));
+    // consistent, which means the mount path needs to be the same.
+    // display_name, remote_mount_path and vm_type aren't needed and we don't
+    // know them at unmount so leave them blank.
+    DoUnmountEvent(chromeos::MOUNT_ERROR_NONE,
+                   // TODO(b/230667118): Once http://crrev/3627129 makes it into
+                   // Chrome change the type to unknown.
+                   *Volume::CreateForSftpGuestOs(
+                       "", sftp_mount_path, base::FilePath(),
+                       guest_os::VmType::ApplicationList_VmType_TERMINA));
     if (callback)
       std::move(callback).Run(true);
     return;
diff --git a/chrome/browser/ash/file_manager/volume_manager.h b/chrome/browser/ash/file_manager/volume_manager.h
index 42a34fad..441603a 100644
--- a/chrome/browser/ash/file_manager/volume_manager.h
+++ b/chrome/browser/ash/file_manager/volume_manager.h
@@ -7,6 +7,7 @@
 
 #include <map>
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
 
@@ -24,11 +25,13 @@
 #include "chrome/browser/ash/file_system_provider/observer.h"
 #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
 #include "chrome/browser/ash/file_system_provider/service.h"
+#include "chrome/browser/ash/guest_os/public/types.h"
 #include "chromeos/dbus/cros_disks/cros_disks_client.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/storage_monitor/removable_storage_observer.h"
 #include "services/device/public/mojom/mtp_manager.mojom.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
 
 class Profile;
 
@@ -126,7 +129,8 @@
   static std::unique_ptr<Volume> CreateForSftpGuestOs(
       const std::string display_name,
       const base::FilePath& sftp_mount_path,
-      const base::FilePath& remote_mount_path);
+      const base::FilePath& remote_mount_path,
+      const guest_os::VmType vm_type);
   static std::unique_ptr<Volume> CreateForAndroidFiles(
       const base::FilePath& mount_path);
   static std::unique_ptr<Volume> CreateForDocumentsProvider(
@@ -197,6 +201,8 @@
   }
   bool hidden() const { return hidden_; }
 
+  absl::optional<guest_os::VmType> vm_type() const { return vm_type_; }
+
  private:
   Volume();
 
@@ -284,6 +290,9 @@
   // True if the volume is hidden and never shown to the user through File
   // Manager.
   bool hidden_;
+
+  // Only set for VOLUME_TYPE_GUEST_OS, identifies the type of Guest OS VM.
+  absl::optional<guest_os::VmType> vm_type_;
 };
 
 // Manages Volumes for file manager. Example of Volumes:
@@ -365,7 +374,8 @@
   // removed on unmount (including Guest OS shutdown).
   void AddSftpGuestOsVolume(const std::string display_name,
                             const base::FilePath& sftp_mount_path,
-                            const base::FilePath& remote_mount_path);
+                            const base::FilePath& remote_mount_path,
+                            const guest_os::VmType vm_type);
 
   // Removes specified sshfs crostini mount. Runs `callback` with true if the
   // mount was removed successfully or wasn't mounted to begin with. Runs
diff --git a/chrome/browser/ash/guest_os/guest_os_registry_service_icon_browsertest.cc b/chrome/browser/ash/guest_os/guest_os_registry_service_icon_browsertest.cc
index 1300c38..cdc5a25f 100644
--- a/chrome/browser/ash/guest_os/guest_os_registry_service_icon_browsertest.cc
+++ b/chrome/browser/ash/guest_os/guest_os_registry_service_icon_browsertest.cc
@@ -30,17 +30,17 @@
  public:
   void SetUpInProcessBrowserTestFixture() override {
     InProcessBrowserTest::SetUpInProcessBrowserTestFixture();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
-    fake_cicerone_client_ = chromeos::FakeCiceroneClient::Get();
+    fake_cicerone_client_ = ash::FakeCiceroneClient::Get();
   }
 
   void TearDownInProcessBrowserTestFixture() override {
     service_.reset();
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     InProcessBrowserTest::TearDownInProcessBrowserTestFixture();
   }
 
@@ -132,7 +132,7 @@
   }
 
  protected:
-  chromeos::FakeCiceroneClient* fake_cicerone_client_;
+  ash::FakeCiceroneClient* fake_cicerone_client_;
   static constexpr char kSvgData[] =
       "<svg width='20px' height='20px' viewBox='0 0 24 24' "
       "fill='rgb(95,99,104)' "
diff --git a/chrome/browser/ash/guest_os/guest_os_share_path_unittest.cc b/chrome/browser/ash/guest_os/guest_os_share_path_unittest.cc
index b60c59c..fe88a18 100644
--- a/chrome/browser/ash/guest_os/guest_os_share_path_unittest.cc
+++ b/chrome/browser/ash/guest_os/guest_os_share_path_unittest.cc
@@ -224,7 +224,7 @@
             TestingBrowserProcess::GetGlobal())),
         browser_part_(g_browser_process->platform_part()) {
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
 
@@ -238,7 +238,7 @@
   ~GuestOsSharePathTest() override {
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/guest_os/guest_os_stability_monitor.cc b/chrome/browser/ash/guest_os/guest_os_stability_monitor.cc
index 1255ec1..c80d5cb 100644
--- a/chrome/browser/ash/guest_os/guest_os_stability_monitor.cc
+++ b/chrome/browser/ash/guest_os/guest_os_stability_monitor.cc
@@ -21,7 +21,7 @@
       base::BindOnce(&GuestOsStabilityMonitor::ConciergeStarted,
                      weak_ptr_factory_.GetWeakPtr()));
 
-  auto* cicerone_client = chromeos::CiceroneClient::Get();
+  auto* cicerone_client = ash::CiceroneClient::Get();
   DCHECK(cicerone_client);
   cicerone_client->WaitForServiceToBeAvailable(
       base::BindOnce(&GuestOsStabilityMonitor::CiceroneStarted,
@@ -54,7 +54,7 @@
 void GuestOsStabilityMonitor::CiceroneStarted(bool is_available) {
   DCHECK(is_available);
 
-  auto* cicerone_client = chromeos::CiceroneClient::Get();
+  auto* cicerone_client = ash::CiceroneClient::Get();
   DCHECK(cicerone_client);
   cicerone_observer_.Observe(cicerone_client);
 }
diff --git a/chrome/browser/ash/guest_os/guest_os_stability_monitor.h b/chrome/browser/ash/guest_os/guest_os_stability_monitor.h
index 69b0c78..c53ec800 100644
--- a/chrome/browser/ash/guest_os/guest_os_stability_monitor.h
+++ b/chrome/browser/ash/guest_os/guest_os_stability_monitor.h
@@ -54,7 +54,7 @@
 // call |LogUnexpectedVmShutdown| if any are considered unexpected.
 // Take care to ignore VMs owned by other implementers.
 class GuestOsStabilityMonitor : ash::ConciergeClient::Observer,
-                                chromeos::CiceroneClient::Observer,
+                                ash::CiceroneClient::Observer,
                                 ash::SeneschalClient::Observer,
                                 chromeos::ChunneldClient::Observer {
  public:
@@ -75,7 +75,7 @@
   void ConciergeServiceStopped() override;
   void ConciergeServiceStarted() override;
 
-  //  chromeos::CiceroneClient::Observer::
+  //  ash::CiceroneClient::Observer::
   void CiceroneServiceStopped() override;
   void CiceroneServiceStarted() override;
 
@@ -91,8 +91,7 @@
   std::string histogram_;
   base::ScopedObservation<ash::ConciergeClient, ash::ConciergeClient::Observer>
       concierge_observer_;
-  base::ScopedObservation<chromeos::CiceroneClient,
-                          chromeos::CiceroneClient::Observer>
+  base::ScopedObservation<ash::CiceroneClient, ash::CiceroneClient::Observer>
       cicerone_observer_;
   base::ScopedObservation<ash::SeneschalClient, ash::SeneschalClient::Observer>
       seneschal_observer_;
diff --git a/chrome/browser/ash/guest_os/guest_os_stability_monitor_unittest.cc b/chrome/browser/ash/guest_os/guest_os_stability_monitor_unittest.cc
index f65e6ce..bd57da7 100644
--- a/chrome/browser/ash/guest_os/guest_os_stability_monitor_unittest.cc
+++ b/chrome/browser/ash/guest_os/guest_os_stability_monitor_unittest.cc
@@ -31,7 +31,7 @@
  public:
   GuestOsStabilityMonitorTest() : task_env_() {
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
 
@@ -57,7 +57,7 @@
     profile_.reset();
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
@@ -102,7 +102,7 @@
 }
 
 TEST_F(GuestOsStabilityMonitorTest, CiceroneFailure) {
-  auto* cicerone_client = chromeos::FakeCiceroneClient::Get();
+  auto* cicerone_client = ash::FakeCiceroneClient::Get();
 
   cicerone_client->NotifyCiceroneStopped();
   histogram_tester_.ExpectUniqueSample(crostini::kCrostiniStabilityHistogram,
diff --git a/chrome/browser/ash/guest_os/guest_os_test_helpers.cc b/chrome/browser/ash/guest_os/guest_os_test_helpers.cc
index b257cc7..fd552d2 100644
--- a/chrome/browser/ash/guest_os/guest_os_test_helpers.cc
+++ b/chrome/browser/ash/guest_os/guest_os_test_helpers.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/ash/guest_os/guest_os_test_helpers.h"
+#include "chrome/browser/ash/guest_os/public/types.h"
 
 #ifndef CHROME_BROWSER_ASH_GUEST_OS_PUBLIC_GUEST_OS_TEST_HELPERS_H_
 #define CHROME_BROWSER_ASH_GUEST_OS_PUBLIC_GUEST_OS_TEST_HELPERS_H_
@@ -28,6 +29,10 @@
   return container_id_;
 }
 
+VmType MockMountProvider::vm_type() {
+  return VmType::ApplicationList_VmType_PLUGIN_VM;
+}
+
 }  // namespace guest_os
 
 #endif  // CHROME_BROWSER_ASH_GUEST_OS_PUBLIC_GUEST_OS_TEST_HELPERS_H_
diff --git a/chrome/browser/ash/guest_os/guest_os_test_helpers.h b/chrome/browser/ash/guest_os/guest_os_test_helpers.h
index 9a999ef4..afda07be 100644
--- a/chrome/browser/ash/guest_os/guest_os_test_helpers.h
+++ b/chrome/browser/ash/guest_os/guest_os_test_helpers.h
@@ -7,6 +7,7 @@
 
 #include "chrome/browser/ash/crostini/crostini_util.h"
 #include "chrome/browser/ash/guest_os/public/guest_os_mount_provider.h"
+#include "chrome/browser/ash/guest_os/public/types.h"
 
 namespace guest_os {
 class MockMountProvider : public GuestOsMountProvider {
@@ -18,6 +19,8 @@
   Profile* profile() override;
   crostini::ContainerId ContainerId() override;
 
+  VmType vm_type() override;
+
   Profile* profile_;
   crostini::ContainerId container_id_;
 };
diff --git a/chrome/browser/ash/guest_os/public/guest_os_mount_provider.cc b/chrome/browser/ash/guest_os/public/guest_os_mount_provider.cc
index 5d7befc..3b97bde 100644
--- a/chrome/browser/ash/guest_os/public/guest_os_mount_provider.cc
+++ b/chrome/browser/ash/guest_os/public/guest_os_mount_provider.cc
@@ -29,7 +29,8 @@
       std::string display_name,
       std::string mount_label,
       base::FilePath homedir,
-      const ash::disks::DiskMountManager::MountPointInfo& mount_info)
+      const ash::disks::DiskMountManager::MountPointInfo& mount_info,
+      VmType vm_type)
       : profile_(profile), mount_label_(mount_label) {
     base::FilePath mount_path = base::FilePath(mount_info.mount_path);
     if (!storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
@@ -44,7 +45,7 @@
     auto* vmgr = file_manager::VolumeManager::Get(profile_);
     if (vmgr) {
       // vmgr is null in unit tests.
-      vmgr->AddSftpGuestOsVolume(display_name, mount_path, homedir);
+      vmgr->AddSftpGuestOsVolume(display_name, mount_path, homedir, vm_type);
     }
   }
 
@@ -81,13 +82,15 @@
                                      crostini::ContainerId container_id,
                                      int cid,
                                      int port,
-                                     base::FilePath homedir)
+                                     base::FilePath homedir,
+                                     VmType vm_type)
       : profile_(profile),
         display_name_(display_name),
         container_id_(container_id),
         cid_(cid),
         port_(port),
-        homedir_(homedir) {}
+        homedir_(homedir),
+        vm_type_(vm_type) {}
 
   // Mount.
   void Build(RealCallback callback) override {
@@ -118,7 +121,7 @@
       return;
     }
     auto scoped_volume = std::make_unique<ScopedVolume>(
-        profile_, display_name_, mount_label_, homedir_, mount_info);
+        profile_, display_name_, mount_label_, homedir_, mount_info, vm_type_);
 
     // CachedCallback magic keeps the scope alive until we're destroyed or it's
     // invalidated.
@@ -132,6 +135,7 @@
   int cid_;
   int port_;  // vsock port
   base::FilePath homedir_;
+  VmType vm_type_;
 
   // Note: This should remain the last member so it'll be destroyed and
   // invalidate its weak pointers before any other members are destroyed.
@@ -141,7 +145,8 @@
 void GuestOsMountProvider::Mount(base::OnceCallback<void(bool)> callback) {
   if (!callback_) {
     callback_ = std::make_unique<GuestOsMountProviderInner>(
-        profile(), DisplayName(), ContainerId(), cid(), port(), homedir());
+        profile(), DisplayName(), ContainerId(), cid(), port(), homedir(),
+        vm_type());
   }
   callback_->Get(base::BindOnce(
       [](base::OnceCallback<void(bool)> callback,
diff --git a/chrome/browser/ash/guest_os/public/guest_os_mount_provider.h b/chrome/browser/ash/guest_os/public/guest_os_mount_provider.h
index 279d1cf..b8bd9a7 100644
--- a/chrome/browser/ash/guest_os/public/guest_os_mount_provider.h
+++ b/chrome/browser/ash/guest_os/public/guest_os_mount_provider.h
@@ -9,6 +9,7 @@
 #include "base/callback_forward.h"
 #include "base/files/file_path.h"
 #include "chrome/browser/ash/crostini/crostini_util.h"
+#include "chrome/browser/ash/guest_os/public/types.h"
 
 class Profile;
 
@@ -41,6 +42,11 @@
   virtual int port();
   virtual base::FilePath homedir();
 
+  // The type of VM which this provider creates mounts for. Needed for e.g.
+  // enterprise policy which applies different rules to each disk volume
+  // depending on the underlying VM.
+  virtual VmType vm_type() = 0;
+
   // Requests the provider to mount its volume.
   void Mount(base::OnceCallback<void(bool)> callback);
 
diff --git a/chrome/browser/ash/guest_os/public/guest_os_mount_provider_unittest.cc b/chrome/browser/ash/guest_os/public/guest_os_mount_provider_unittest.cc
index d28eccf..a8e7537 100644
--- a/chrome/browser/ash/guest_os/public/guest_os_mount_provider_unittest.cc
+++ b/chrome/browser/ash/guest_os/public/guest_os_mount_provider_unittest.cc
@@ -139,6 +139,7 @@
   ASSERT_TRUE(volume);
   EXPECT_EQ(volume->type(), file_manager::VOLUME_TYPE_GUEST_OS);
   EXPECT_EQ(volume->volume_label(), provider_->DisplayName());
+  EXPECT_EQ(volume->vm_type(), provider_->vm_type());
 }
 
 TEST_F(GuestOsMountProviderTest, MultipleCallsAreQueuedAndOnlyMountOnce) {
diff --git a/chrome/browser/ash/guest_os/public/types.h b/chrome/browser/ash/guest_os/public/types.h
new file mode 100644
index 0000000..2660f9f1
--- /dev/null
+++ b/chrome/browser/ash/guest_os/public/types.h
@@ -0,0 +1,14 @@
+// Copyright 2022 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_ASH_GUEST_OS_PUBLIC_TYPES_H_
+#define CHROME_BROWSER_ASH_GUEST_OS_PUBLIC_TYPES_H_
+
+#include "chromeos/dbus/vm_applications/apps.pb.h"
+namespace guest_os {
+
+using VmType = vm_tools::apps::ApplicationList_VmType;
+}
+
+#endif  // CHROME_BROWSER_ASH_GUEST_OS_PUBLIC_TYPES_H_
diff --git a/chrome/browser/ash/input_method/assistive_suggester.cc b/chrome/browser/ash/input_method/assistive_suggester.cc
index ca442f9..1cae859 100644
--- a/chrome/browser/ash/input_method/assistive_suggester.cc
+++ b/chrome/browser/ash/input_method/assistive_suggester.cc
@@ -332,7 +332,7 @@
   // Some parts of the code reserve negative/zero context_id for unfocused
   // context. As a result we should make sure it is not being errornously set to
   // a negative number, and cause unexpected behaviour.
-  DCHECK(context_id);
+  DCHECK(context_id > 0);
   focused_context_id_ = context_id;
   personal_info_suggester_.OnFocus(context_id);
   emoji_suggester_.OnFocus(context_id);
diff --git a/chrome/browser/ash/login/crash_restore_browsertest.cc b/chrome/browser/ash/login/crash_restore_browsertest.cc
index 34dc66a1..fa8f0e4f 100644
--- a/chrome/browser/ash/login/crash_restore_browsertest.cc
+++ b/chrome/browser/ash/login/crash_restore_browsertest.cc
@@ -163,27 +163,27 @@
   // Register test users so that UserManager knows them and make kUserId3 as the
   // last active user.
   void RegisterUsers() {
-    base::DictionaryValue local_state;
+    base::Value::Dict local_state;
 
-    const char* kTestUserIds[] = {kUserId1, kUserId2, kUserId3};
+    static const char* const kTestUserIds[] = {kUserId1, kUserId2, kUserId3};
 
-    auto users_list = std::make_unique<base::ListValue>();
-    for (auto* user_id : kTestUserIds)
-      users_list->Append(user_id);
+    base::Value::List users_list;
+    for (const auto* user_id : kTestUserIds)
+      users_list.Append(user_id);
 
-    local_state.SetList("LoggedInUsers", std::move(users_list));
-    local_state.SetStringKey("LastActiveUser", kUserId3);
+    local_state.Set("LoggedInUsers", std::move(users_list));
+    local_state.Set("LastActiveUser", kUserId3);
 
-    auto known_users_list = std::make_unique<base::ListValue>();
+    base::Value::List known_users_list;
     int gaia_id = 10000;
-    for (auto* user_id : kTestUserIds) {
-      auto user_dict = std::make_unique<base::DictionaryValue>();
-      user_dict->SetString("account_type", "google");
-      user_dict->SetString("email", user_id);
-      user_dict->SetString("gaia_id", base::NumberToString(gaia_id++));
-      known_users_list->Append(std::move(user_dict));
+    for (const auto* user_id : kTestUserIds) {
+      base::Value::Dict user_dict;
+      user_dict.Set("account_type", "google");
+      user_dict.Set("email", user_id);
+      user_dict.Set("gaia_id", base::NumberToString(gaia_id++));
+      known_users_list.Append(std::move(user_dict));
     }
-    local_state.SetList("KnownUsers", std::move(known_users_list));
+    local_state.Set("KnownUsers", std::move(known_users_list));
 
     std::string local_state_json;
     ASSERT_TRUE(base::JSONWriter::Write(local_state, &local_state_json));
diff --git a/chrome/browser/ash/login/existing_user_controller_auto_login_unittest.cc b/chrome/browser/ash/login/existing_user_controller_auto_login_unittest.cc
index 25426175..f370495 100644
--- a/chrome/browser/ash/login/existing_user_controller_auto_login_unittest.cc
+++ b/chrome/browser/ash/login/existing_user_controller_auto_login_unittest.cc
@@ -70,15 +70,14 @@
         FakeSessionManagerClient::Get(), new ownership::MockOwnerKeyUtil());
     DeviceSettingsService::Get()->Load();
 
-    std::unique_ptr<base::DictionaryValue> account(new base::DictionaryValue);
-    account->SetKey(kAccountsPrefDeviceLocalAccountsKeyId,
-                    base::Value(auto_login_user_id_));
-    account->SetKey(
-        kAccountsPrefDeviceLocalAccountsKeyType,
-        base::Value(policy::DeviceLocalAccount::TYPE_PUBLIC_SESSION));
-    base::ListValue accounts;
+    base::Value::Dict account;
+    account.Set(kAccountsPrefDeviceLocalAccountsKeyId, auto_login_user_id_);
+    account.Set(kAccountsPrefDeviceLocalAccountsKeyType,
+                policy::DeviceLocalAccount::TYPE_PUBLIC_SESSION);
+    base::Value::List accounts;
     accounts.Append(std::move(account));
-    settings_helper_.Set(kAccountsPrefDeviceLocalAccounts, accounts);
+    settings_helper_.Set(kAccountsPrefDeviceLocalAccounts,
+                         base::Value(std::move(accounts)));
 
     // Prevent settings changes from auto-starting the timer.
     existing_user_controller_->local_account_auto_login_id_subscription_ = {};
diff --git a/chrome/browser/ash/login/screens/hid_detection_screen.cc b/chrome/browser/ash/login/screens/hid_detection_screen.cc
index f8a12255..dbaef3c 100644
--- a/chrome/browser/ash/login/screens/hid_detection_screen.cc
+++ b/chrome/browser/ash/login/screens/hid_detection_screen.cc
@@ -7,6 +7,7 @@
 #include <memory>
 #include <utility>
 
+#include "ash/components/hid_detection/hid_detection_utils.h"
 #include "ash/constants/ash_switches.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
@@ -475,6 +476,8 @@
     SetKeyboardDeviceName(info_ref->name);
     SendKeyboardDeviceNotification();
   }
+  DCHECK(!info_ref.is_null());
+  hid_detection::RecordHidConnected(*info_ref);
 }
 
 void HIDDetectionScreen::InputDeviceAddedForTesting(InputDeviceInfoPtr info) {
diff --git a/chrome/browser/ash/login/screens/hid_detection_screen_browsertest.cc b/chrome/browser/ash/login/screens/hid_detection_screen_browsertest.cc
index 2687813..b3db999 100644
--- a/chrome/browser/ash/login/screens/hid_detection_screen_browsertest.cc
+++ b/chrome/browser/ash/login/screens/hid_detection_screen_browsertest.cc
@@ -2,10 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "ash/components/hid_detection/hid_detection_utils.h"
 #include "ash/constants/ash_switches.h"
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/run_loop.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_chromeos_version_info.h"
 #include "chrome/browser/ash/login/login_wizard.h"
 #include "chrome/browser/ash/login/screens/base_screen.h"
@@ -31,6 +33,7 @@
 namespace {
 
 using ::testing::_;
+using HidType = hid_detection::HidType;
 
 const test::UIPath kHidContinueButton = {"hid-detection",
                                          "hid-continue-button"};
@@ -89,6 +92,11 @@
         ->get_exit_result_for_testing();
   }
 
+  void AssertHidConnectedCount(HidType hid_type, int count) {
+    histogram_tester_.ExpectBucketCount("OOBE.HidDetectionScreen.HidConnected",
+                                        hid_type, count);
+  }
+
   test::HIDControllerMixin hid_controller_{&mixin_host_};
 
  private:
@@ -98,6 +106,8 @@
   // Chromeboxes.
   base::test::ScopedChromeOSVersionInfo version_{"DEVICETYPE=CHROMEBOX",
                                                  base::Time::Now()};
+
+  base::HistogramTester histogram_tester_;
 };
 
 IN_PROC_BROWSER_TEST_F(HIDDetectionScreenChromeboxTest, NoDevicesConnected) {
@@ -117,11 +127,13 @@
   // or touchscreen, the keyboard only reports usb and bluetooth states.
   hid_controller_.AddMouse(device::mojom::InputDeviceType::TYPE_SERIO);
   test::OobeJS().ExpectEnabledPath(kHidContinueButton);
+  AssertHidConnectedCount(HidType::kSerialPointer, /*count=*/1);
 
   hid_controller_.AddKeyboard(device::mojom::InputDeviceType::TYPE_SERIO);
   EXPECT_EQ("connected", handler()->mouse_state_for_test());
   EXPECT_EQ("usb", handler()->keyboard_state_for_test());
   test::OobeJS().ExpectEnabledPath(kHidContinueButton);
+  AssertHidConnectedCount(HidType::kSerialKeyboard, /*count=*/1);
 
   // Remove generic devices, add usb devices.
   hid_controller_.RemoveDevices();
@@ -132,6 +144,8 @@
   EXPECT_EQ("usb", handler()->mouse_state_for_test());
   EXPECT_EQ("usb", handler()->keyboard_state_for_test());
   test::OobeJS().ExpectEnabledPath(kHidContinueButton);
+  AssertHidConnectedCount(HidType::kUsbKeyboard, /*count=*/1);
+  AssertHidConnectedCount(HidType::kUsbPointer, /*count=*/1);
 
   // Remove usb devices, add bluetooth devices.
   hid_controller_.RemoveDevices();
@@ -141,6 +155,8 @@
   EXPECT_EQ("paired", handler()->mouse_state_for_test());
   EXPECT_EQ("paired", handler()->keyboard_state_for_test());
   test::OobeJS().ExpectEnabledPath(kHidContinueButton);
+  AssertHidConnectedCount(HidType::kBluetoothKeyboard, /*count=*/1);
+  AssertHidConnectedCount(HidType::kBluetoothPointer, /*count=*/1);
 }
 
 // Test that if there is any Bluetooth device connected on HID screen, the
@@ -228,11 +244,15 @@
     hid_controller_.ConnectUSBDevices();
   }
   ~HIDDetectionSkipTest() override = default;
+
+ protected:
+  base::HistogramTester histogram_tester;
 };
 
 IN_PROC_BROWSER_TEST_F(HIDDetectionSkipTest, BothDevicesPreConnected) {
   OobeScreenWaiter(WelcomeView::kScreenId).Wait();
   EXPECT_FALSE(GetExitResult().has_value());
+  histogram_tester.ExpectTotalCount("OOBE.HidDetectionScreen.HidConnected", 0);
 }
 
 class HIDDetectionDeviceOwnedTest : public HIDDetectionScreenChromeboxTest {
@@ -342,9 +362,46 @@
   test::OobeJS().ExpectEnabledPath(kHidContinueButton);
 
   hid_controller_.ConnectUSBDevices();
+  test::OobeJS().CreateVisibilityWaiter(true, kHidMouseTick)->Wait();
+  test::OobeJS().CreateVisibilityWaiter(true, kHidKeyboardTick)->Wait();
+
   hid_controller_.RemoveDevices();
+  test::OobeJS().CreateVisibilityWaiter(false, kHidMouseTick)->Wait();
+  test::OobeJS().CreateVisibilityWaiter(false, kHidKeyboardTick)->Wait();
 
   test::OobeJS().ExpectEnabledPath(kHidContinueButton);
 }
 
+class HIDDetectionScreenPreConnectedDeviceTest
+    : public HIDDetectionScreenChromeboxTest {
+ public:
+  HIDDetectionScreenPreConnectedDeviceTest() {
+    hid_controller_.set_wait_until_idle_after_device_update(false);
+    hid_controller_.AddMouse(device::mojom::InputDeviceType::TYPE_USB);
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(HIDDetectionScreenPreConnectedDeviceTest,
+                       MousePreConnected) {
+  // Continue button should be enabled if at least one device is connected.
+  OobeScreenWaiter(HIDDetectionView::kScreenId).Wait();
+  test::OobeJS().ExpectHiddenPath(kHidTouchscreenEntry);
+  test::OobeJS().CreateVisibilityWaiter(true, kHidMouseTick)->Wait();
+  test::OobeJS().ExpectEnabledPath(kHidContinueButton);
+  AssertHidConnectedCount(HidType::kUsbPointer, /*count=*/0);
+  AssertHidConnectedCount(HidType::kUsbKeyboard, /*count=*/0);
+
+  hid_controller_.AddKeyboard(device::mojom::InputDeviceType::TYPE_USB);
+  test::OobeJS().CreateVisibilityWaiter(true, kHidKeyboardTick)->Wait();
+  test::OobeJS().ExpectEnabledPath(kHidContinueButton);
+  AssertHidConnectedCount(HidType::kUsbPointer, /*count=*/0);
+  AssertHidConnectedCount(HidType::kUsbKeyboard, /*count=*/1);
+
+  hid_controller_.AddKeyboard(device::mojom::InputDeviceType::TYPE_USB);
+  test::OobeJS().CreateVisibilityWaiter(true, kHidKeyboardTick)->Wait();
+  test::OobeJS().ExpectEnabledPath(kHidContinueButton);
+  AssertHidConnectedCount(HidType::kUsbPointer, /*count=*/0);
+  AssertHidConnectedCount(HidType::kUsbKeyboard, /*count=*/2);
+}
+
 }  // namespace ash
diff --git a/chrome/browser/ash/login/users/default_user_image/default_user_images.cc b/chrome/browser/ash/login/users/default_user_image/default_user_images.cc
index 4e9de319..895a8c1 100644
--- a/chrome/browser/ash/login/users/default_user_image/default_user_images.cc
+++ b/chrome/browser/ash/login/users/default_user_image/default_user_images.cc
@@ -378,11 +378,11 @@
 std::unique_ptr<base::ListValue> GetCurrentImageSetAsListValue() {
   auto image_urls = std::make_unique<base::ListValue>();
   for (auto& user_image : GetCurrentImageSet()) {
-    auto image_data = std::make_unique<base::DictionaryValue>();
-    image_data->SetIntKey("index", user_image.index);
-    image_data->SetStringKey("title", std::move(user_image.title));
-    image_data->SetStringKey("url", user_image.url.spec());
-    image_urls->Append(std::move(image_data));
+    base::Value::Dict image_data;
+    image_data.Set("index", user_image.index);
+    image_data.Set("title", std::move(user_image.title));
+    image_data.Set("url", user_image.url.spec());
+    image_urls->GetList().Append(std::move(image_data));
   }
   return image_urls;
 }
diff --git a/chrome/browser/ash/plugin_vm/plugin_vm_files.cc b/chrome/browser/ash/plugin_vm/plugin_vm_files.cc
index eb37742f..6be77cb 100644
--- a/chrome/browser/ash/plugin_vm/plugin_vm_files.cc
+++ b/chrome/browser/ash/plugin_vm/plugin_vm_files.cc
@@ -107,7 +107,7 @@
       std::make_move_iterator(file_paths.end()),
       google::protobuf::RepeatedFieldBackInserter(request.mutable_files()));
 
-  chromeos::CiceroneClient::Get()->LaunchContainerApplication(
+  ash::CiceroneClient::Get()->LaunchContainerApplication(
       std::move(request),
       base::BindOnce(
           [](const std::string& app_id, LaunchPluginVmAppCallback callback,
diff --git a/chrome/browser/ash/plugin_vm/plugin_vm_files_unittest.cc b/chrome/browser/ash/plugin_vm/plugin_vm_files_unittest.cc
index ca17dfe..cdab9ee 100644
--- a/chrome/browser/ash/plugin_vm/plugin_vm_files_unittest.cc
+++ b/chrome/browser/ash/plugin_vm/plugin_vm_files_unittest.cc
@@ -98,14 +98,14 @@
   struct ScopedDBusThreadManager {
     ScopedDBusThreadManager() {
       chromeos::DBusThreadManager::Initialize();
-      chromeos::CiceroneClient::InitializeFake();
+      ash::CiceroneClient::InitializeFake();
       ash::ConciergeClient::InitializeFake();
       ash::SeneschalClient::InitializeFake();
     }
     ~ScopedDBusThreadManager() {
       ash::SeneschalClient::Shutdown();
       ash::ConciergeClient::Shutdown();
-      chromeos::CiceroneClient::Shutdown();
+      ash::CiceroneClient::Shutdown();
       chromeos::DBusThreadManager::Shutdown();
     }
   } dbus_thread_manager_;
@@ -180,7 +180,7 @@
   ASSERT_FALSE(launch_plugin_vm_callback.is_null());
 
   LaunchContainerApplicationCallback cicerone_response_callback;
-  chromeos::FakeCiceroneClient::Get()->SetOnLaunchContainerApplicationCallback(
+  ash::FakeCiceroneClient::Get()->SetOnLaunchContainerApplicationCallback(
       base::BindLambdaForTesting(
           [&](const vm_tools::cicerone::LaunchContainerApplicationRequest&
                   request,
diff --git a/chrome/browser/ash/plugin_vm/plugin_vm_manager_impl_unittest.cc b/chrome/browser/ash/plugin_vm/plugin_vm_manager_impl_unittest.cc
index 9108c20..41788d43 100644
--- a/chrome/browser/ash/plugin_vm/plugin_vm_manager_impl_unittest.cc
+++ b/chrome/browser/ash/plugin_vm/plugin_vm_manager_impl_unittest.cc
@@ -49,7 +49,7 @@
  public:
   PluginVmManagerImplTest() {
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
     testing_profile_ = std::make_unique<TestingProfile>();
@@ -91,7 +91,7 @@
     testing_profile_.reset();
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/policy/dlp/dlp_files_controller_unittest.cc b/chrome/browser/ash/policy/dlp/dlp_files_controller_unittest.cc
index 4c09ba28..9ff0f7ca 100644
--- a/chrome/browser/ash/policy/dlp/dlp_files_controller_unittest.cc
+++ b/chrome/browser/ash/policy/dlp/dlp_files_controller_unittest.cc
@@ -234,7 +234,7 @@
     crostini_features.set_enabled(true);
 
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
 
@@ -269,7 +269,7 @@
     DlpFilesControllerTest::TearDown();
 
     chromeos::DBusThreadManager::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     ash::ConciergeClient::Shutdown();
     ash::SeneschalClient::Shutdown();
 
diff --git a/chrome/browser/ash/policy/handlers/lock_to_single_user_manager_unittest.cc b/chrome/browser/ash/policy/handlers/lock_to_single_user_manager_unittest.cc
index 496bc01..57e3135 100644
--- a/chrome/browser/ash/policy/handlers/lock_to_single_user_manager_unittest.cc
+++ b/chrome/browser/ash/policy/handlers/lock_to_single_user_manager_unittest.cc
@@ -50,7 +50,7 @@
     // DBusThreadManager::Get() for GetAnomalyDetectorClient.
     chromeos::DBusThreadManager::Initialize();
 
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
 
@@ -102,7 +102,7 @@
     chromeos::CryptohomeMiscClient::Shutdown();
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ash/policy/status_collector/child_status_collector_browsertest.cc b/chrome/browser/ash/policy/status_collector/child_status_collector_browsertest.cc
index d0dc2c5..0f19c8d 100644
--- a/chrome/browser/ash/policy/status_collector/child_status_collector_browsertest.cc
+++ b/chrome/browser/ash/policy/status_collector/child_status_collector_browsertest.cc
@@ -204,7 +204,7 @@
     chromeos::DBusThreadManager::GetSetterForTesting()->SetUpdateEngineClient(
         base::WrapUnique<chromeos::UpdateEngineClient>(update_engine_client_));
 
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
     chromeos::PowerManagerClient::InitializeFake();
@@ -220,7 +220,7 @@
     // |testing_profile_| must be destructed while ConciergeClient is alive.
     testing_profile_.reset();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     TestingBrowserProcess::GetGlobal()->SetLocalState(nullptr);
 
     // Finish pending tasks.
diff --git a/chrome/browser/ash/policy/status_collector/device_status_collector_browsertest.cc b/chrome/browser/ash/policy/status_collector/device_status_collector_browsertest.cc
index a11c77b..99efa2b 100644
--- a/chrome/browser/ash/policy/status_collector/device_status_collector_browsertest.cc
+++ b/chrome/browser/ash/policy/status_collector/device_status_collector_browsertest.cc
@@ -918,7 +918,7 @@
     chromeos::LoginState::Initialize();
     ash::cros_healthd::FakeCrosHealthd::Initialize();
 
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
   }
@@ -932,7 +932,7 @@
     // |testing_profile_| must be destroyed while ConciergeClient is alive.
     testing_profile_.reset();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::LoginState::Shutdown();
     chromeos::TpmManagerClient::Shutdown();
     chromeos::AttestationClient::Shutdown();
diff --git a/chrome/browser/ash/policy/status_collector/legacy_device_status_collector_browsertest.cc b/chrome/browser/ash/policy/status_collector/legacy_device_status_collector_browsertest.cc
index 6b7417a..f5ecf48 100644
--- a/chrome/browser/ash/policy/status_collector/legacy_device_status_collector_browsertest.cc
+++ b/chrome/browser/ash/policy/status_collector/legacy_device_status_collector_browsertest.cc
@@ -906,7 +906,7 @@
     chromeos::TpmManagerClient::InitializeFake();
     chromeos::LoginState::Initialize();
 
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
   }
@@ -921,7 +921,7 @@
     // |testing_profile_| must be destroyed while ConciergeClient is alive.
     testing_profile_.reset();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::LoginState::Shutdown();
     chromeos::TpmManagerClient::Shutdown();
     chromeos::AttestationClient::Shutdown();
diff --git a/chrome/browser/ash/settings/device_settings_provider_unittest.cc b/chrome/browser/ash/settings/device_settings_provider_unittest.cc
index bf19c0e..fcdc894 100644
--- a/chrome/browser/ash/settings/device_settings_provider_unittest.cc
+++ b/chrome/browser/ash/settings/device_settings_provider_unittest.cc
@@ -736,17 +736,16 @@
   BuildAndInstallDevicePolicy();
 
   // On load, the deprecated spec should have been converted to the new format.
-  base::ListValue expected_accounts;
-  std::unique_ptr<base::DictionaryValue> entry_dict(
-      new base::DictionaryValue());
-  entry_dict->SetStringKey(kAccountsPrefDeviceLocalAccountsKeyId,
-                           policy::PolicyBuilder::kFakeUsername);
-  entry_dict->SetIntKey(kAccountsPrefDeviceLocalAccountsKeyType,
-                        policy::DeviceLocalAccount::TYPE_PUBLIC_SESSION);
+  base::Value::List expected_accounts;
+  base::Value::Dict entry_dict;
+  entry_dict.Set(kAccountsPrefDeviceLocalAccountsKeyId,
+                 policy::PolicyBuilder::kFakeUsername);
+  entry_dict.Set(kAccountsPrefDeviceLocalAccountsKeyType,
+                 policy::DeviceLocalAccount::TYPE_PUBLIC_SESSION);
   expected_accounts.Append(std::move(entry_dict));
   const base::Value* actual_accounts =
       provider_->Get(kAccountsPrefDeviceLocalAccounts);
-  EXPECT_EQ(expected_accounts, *actual_accounts);
+  EXPECT_EQ(expected_accounts, actual_accounts->GetList());
 }
 
 TEST_F(DeviceSettingsProviderTest, DecodeDeviceState) {
diff --git a/chrome/browser/ash/usb/cros_usb_detector_unittest.cc b/chrome/browser/ash/usb/cros_usb_detector_unittest.cc
index 763a834..8ac72ed 100644
--- a/chrome/browser/ash/usb/cros_usb_detector_unittest.cc
+++ b/chrome/browser/ash/usb/cros_usb_detector_unittest.cc
@@ -134,10 +134,10 @@
  public:
   CrosUsbDetectorTest() {
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ConciergeClient::InitializeFake();
     SeneschalClient::InitializeFake();
-    fake_cicerone_client_ = chromeos::FakeCiceroneClient::Get();
+    fake_cicerone_client_ = ash::FakeCiceroneClient::Get();
     fake_concierge_client_ = FakeConciergeClient::Get();
     fake_vm_plugin_dispatcher_client_ =
         static_cast<chromeos::FakeVmPluginDispatcherClient*>(
@@ -155,7 +155,7 @@
     disks::DiskMountManager::Shutdown();
     SeneschalClient::Shutdown();
     ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
@@ -285,7 +285,7 @@
   disks::MockDiskMountManager* mock_disk_mount_manager_;
   disks::DiskMountManager::DiskMap disks_;
 
-  chromeos::FakeCiceroneClient* fake_cicerone_client_;
+  ash::FakeCiceroneClient* fake_cicerone_client_;
   FakeConciergeClient* fake_concierge_client_;
   // Owned by chromeos::DBusThreadManager
   chromeos::FakeVmPluginDispatcherClient* fake_vm_plugin_dispatcher_client_;
diff --git a/chrome/browser/ash/web_applications/media_app/media_app_integration_browsertest.cc b/chrome/browser/ash/web_applications/media_app/media_app_integration_browsertest.cc
index d8faa087..f2319c23 100644
--- a/chrome/browser/ash/web_applications/media_app/media_app_integration_browsertest.cc
+++ b/chrome/browser/ash/web_applications/media_app/media_app_integration_browsertest.cc
@@ -1051,6 +1051,7 @@
 
   // Check the metric is recorded.
   histograms.ExpectTotalCount("Apps.DefaultAppLaunch.FromFileManager", 1);
+  histograms.ExpectBucketCount("Apps.MediaApp.Load.OtherOpenWindowCount", 0, 1);
 }
 
 IN_PROC_BROWSER_TEST_P(MediaAppIntegrationDarkLightModeEnabledTest,
@@ -1122,8 +1123,12 @@
   EXPECT_EQ(kFileAudioOgg, WaitForAudioTrackTitle(audio_web_ui));
   EXPECT_EQ("640x480", WaitForImageAlt(image_web_ui, kFileJpeg640x480));
 
-  // Check the metrics are recorded.
+  // Check the metrics are recorded: 2 launches from file manager machinery, and
+  // 0 other open windows for the first launch; 1 other open window for the
+  // second launch.
   histograms.ExpectTotalCount("Apps.DefaultAppLaunch.FromFileManager", 2);
+  histograms.ExpectBucketCount("Apps.MediaApp.Load.OtherOpenWindowCount", 0, 1);
+  histograms.ExpectBucketCount("Apps.MediaApp.Load.OtherOpenWindowCount", 1, 1);
 }
 
 // Ensures audio files opened in the media app successfully autoplay.
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_utils.cc b/chrome/browser/ash/web_applications/personalization_app/personalization_app_utils.cc
index bf92da8..1443cee3 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_utils.cc
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_utils.cc
@@ -3,7 +3,13 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/ash/web_applications/personalization_app/personalization_app_utils.h"
+
 #include "chrome/browser/ash/profiles/profile_helper.h"
+#include "chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.h"
+#include "chrome/browser/ash/web_applications/personalization_app/personalization_app_keyboard_backlight_provider_impl.h"
+#include "chrome/browser/ash/web_applications/personalization_app/personalization_app_theme_provider_impl.h"
+#include "chrome/browser/ash/web_applications/personalization_app/personalization_app_user_provider_impl.h"
+#include "chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/account_id/account_id.h"
 #include "components/user_manager/user.h"
@@ -11,6 +17,26 @@
 namespace ash {
 namespace personalization_app {
 
+PersonalizationAppUI* CreatePersonalizationAppUI(content::WebUI* web_ui) {
+  auto ambient_provider = std::make_unique<
+      ash::personalization_app::PersonalizationAppAmbientProviderImpl>(web_ui);
+  auto keyboard_backlight_provider =
+      std::make_unique<ash::personalization_app::
+                           PersonalizationAppKeyboardBacklightProviderImpl>(
+          web_ui);
+  auto theme_provider = std::make_unique<
+      ash::personalization_app::PersonalizationAppThemeProviderImpl>(web_ui);
+  auto user_provider = std::make_unique<
+      ash::personalization_app::PersonalizationAppUserProviderImpl>(web_ui);
+  auto wallpaper_provider = std::make_unique<
+      ash::personalization_app::PersonalizationAppWallpaperProviderImpl>(
+      web_ui);
+  return new ash::personalization_app::PersonalizationAppUI(
+      web_ui, std::move(ambient_provider),
+      std::move(keyboard_backlight_provider), std::move(theme_provider),
+      std::move(user_provider), std::move(wallpaper_provider));
+}
+
 const user_manager::User* GetUser(const Profile* profile) {
   auto* profile_helper = chromeos::ProfileHelper::Get();
   DCHECK(profile_helper);
diff --git a/chrome/browser/ash/web_applications/personalization_app/personalization_app_utils.h b/chrome/browser/ash/web_applications/personalization_app/personalization_app_utils.h
index 6312a2e..c627493 100644
--- a/chrome/browser/ash/web_applications/personalization_app/personalization_app_utils.h
+++ b/chrome/browser/ash/web_applications/personalization_app/personalization_app_utils.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_ASH_WEB_APPLICATIONS_PERSONALIZATION_APP_PERSONALIZATION_APP_UTILS_H_
 #define CHROME_BROWSER_ASH_WEB_APPLICATIONS_PERSONALIZATION_APP_PERSONALIZATION_APP_UTILS_H_
 
+#include "ash/webui/personalization_app/personalization_app_ui.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/account_id/account_id.h"
 #include "components/user_manager/user.h"
@@ -12,6 +13,10 @@
 namespace ash {
 namespace personalization_app {
 
+// Creates the PersonalizationAppUI to be registered in
+// ChromeWebUIControllerFactory.
+PersonalizationAppUI* CreatePersonalizationAppUI(content::WebUI* web_ui);
+
 const user_manager::User* GetUser(const Profile* profile);
 
 AccountId GetAccountId(const Profile* profile);
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
index ea26359..a16914e 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate_unittest.cc
@@ -18,6 +18,7 @@
 #include "base/json/values_util.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
 #include "base/run_loop.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
@@ -53,6 +54,9 @@
 #include "chrome/browser/privacy_sandbox/privacy_sandbox_settings_factory.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
 #include "chrome/browser/safe_browsing/verdict_cache_manager_factory.h"
+#include "chrome/browser/segmentation_platform/segmentation_platform_service_factory.h"
+#include "chrome/browser/segmentation_platform/ukm_data_manager_test_utils.h"
+#include "chrome/browser/segmentation_platform/ukm_database_client.h"
 #include "chrome/browser/signin/chrome_signin_client_factory.h"
 #include "chrome/browser/signin/test_signin_client_builder.h"
 #include "chrome/browser/spellchecker/spellcheck_custom_dictionary.h"
@@ -71,6 +75,7 @@
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/pref_names.h"
+#include "chrome/test/base/fake_profile_manager.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
@@ -122,7 +127,9 @@
 #include "components/privacy_sandbox/privacy_sandbox_settings.h"
 #include "components/safe_browsing/core/browser/verdict_cache_manager.h"
 #include "components/security_interstitials/content/stateful_ssl_host_state_delegate.h"
+#include "components/segmentation_platform/public/features.h"
 #include "components/site_isolation/pref_names.h"
+#include "components/ukm/test_ukm_recorder.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browsing_data_filter_builder.h"
@@ -484,6 +491,72 @@
   raw_ptr<favicon::FaviconService> favicon_service_ = nullptr;
 };
 
+class RemoveUkmDataTester {
+ public:
+  static constexpr optimization_guide::proto::OptimizationTarget kSegmentId =
+      optimization_guide::proto::OptimizationTarget::
+          OPTIMIZATION_TARGET_SEGMENTATION_CHROME_LOW_USER_ENGAGEMENT;
+
+  RemoveUkmDataTester() : test_utils_(&ukm_recorder_) {
+    test_utils_.PreProfileInit({kSegmentId});
+    segmentation_platform::UkmDatabaseClient::GetInstance().PreProfileInit();
+  }
+
+  RemoveUkmDataTester(const RemoveUkmDataTester&) = delete;
+  RemoveUkmDataTester& operator=(const RemoveUkmDataTester&) = delete;
+
+  ~RemoveUkmDataTester() {
+    TestingBrowserProcess::GetGlobal()->SetProfileManager(nullptr);
+  }
+
+  [[nodiscard]] bool Init(Profile* profile) {
+    // Setup required dependencies for segmentation platform:
+    segmentation_platform::SegmentationPlatformServiceFactory::GetInstance()
+        ->set_local_state_for_testing(&local_state_);
+    EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
+    TestingBrowserProcess::GetGlobal()->SetProfileManager(
+        std::make_unique<FakeProfileManager>(temp_dir_.GetPath()));
+
+    // Create the platform to kick off initialization.
+    segmentation_platform::SegmentationPlatformServiceFactory::GetForProfile(
+        profile);
+    history_service_ = HistoryServiceFactory::GetForProfile(
+        profile, ServiceAccessType::EXPLICIT_ACCESS);
+    if (!history_service_)
+      return false;
+    test_utils_.set_history_service(history_service_);
+
+    // Run model overrides to start storing UKM metrics.
+    test_utils_.WaitForModelRequestAndUpdateWith(
+        kSegmentId, test_utils_.GetSamplePageLoadMetadata("SELECT 1"));
+
+    return true;
+  }
+
+  [[nodiscard]] bool UkmDatabaseContainsURL(const GURL& url) {
+    return test_utils_.IsUrlInDatabase(url);
+  }
+
+  void AddURL(const GURL& url, base::Time time) {
+    test_utils_.RecordPageLoadUkm(url, time);
+
+    // Wait for URL to be written to database, UKM recorder needs to run all
+    // necessary tasks before sending observation.
+    while (!UkmDatabaseContainsURL(url)) {
+      base::RunLoop().RunUntilIdle();
+    }
+  }
+
+ private:
+  ukm::TestUkmRecorder ukm_recorder_;
+  TestingPrefServiceSimple local_state_;
+  base::ScopedTempDir temp_dir_;
+  raw_ptr<history::HistoryService> history_service_;
+  segmentation_platform::UkmDataManagerTestUtils test_utils_;
+
+  base::WeakPtrFactory<RemoveUkmDataTester> weak_ptr_factory_{this};
+};
+
 std::unique_ptr<KeyedService> BuildProtocolHandlerRegistry(
     content::BrowserContext* context) {
   Profile* profile = Profile::FromBrowserContext(context);
@@ -1808,6 +1881,48 @@
   EXPECT_TRUE(tester.HistoryContainsURL(kOrigin2));
 }
 
+class ChromeBrowsingDataRemoverDelegateEnabledUkmDatabaseTest
+    : public ChromeBrowsingDataRemoverDelegateTest {
+ public:
+  ChromeBrowsingDataRemoverDelegateEnabledUkmDatabaseTest() {
+    // Enable features that will trigger platform to store URLs in database.
+    feature_list_.InitWithFeatures(
+        {segmentation_platform::features::kSegmentationPlatformFeature,
+         segmentation_platform::features::
+             kSegmentationPlatformLowEngagementFeature,
+         segmentation_platform::features::kSegmentationPlatformUkmEngine},
+        {});
+  }
+};
+
+TEST_F(ChromeBrowsingDataRemoverDelegateEnabledUkmDatabaseTest, RemoveUkmUrls) {
+  RemoveUkmDataTester tester;
+  ASSERT_TRUE(tester.Init(GetProfile()));
+
+  const base::Time timestamp1 = base::Time::Now();
+  const base::Time timestamp2 = timestamp1 + base::Hours(2);
+
+  const GURL kOrigin1("http://host1.com:1");
+  tester.AddURL(kOrigin1, timestamp1);
+  const GURL kOrigin2("http://host2.com:1");
+  tester.AddURL(kOrigin2, timestamp2);
+  ASSERT_TRUE(tester.UkmDatabaseContainsURL(kOrigin2));
+
+  // Removing history URLs will remove URLs from the platform.
+  BlockUntilBrowsingDataRemoved(base::Time(), timestamp1 + base::Hours(1),
+                                constants::DATA_TYPE_HISTORY, false);
+
+  EXPECT_FALSE(tester.UkmDatabaseContainsURL(kOrigin1));
+  EXPECT_TRUE(tester.UkmDatabaseContainsURL(kOrigin2));
+
+  // Removing history URLs will remove URLs from the platform.
+  BlockUntilBrowsingDataRemoved(base::Time(), base::Time::Max(),
+                                constants::DATA_TYPE_HISTORY, false);
+
+  EXPECT_FALSE(tester.UkmDatabaseContainsURL(kOrigin1));
+  EXPECT_FALSE(tester.UkmDatabaseContainsURL(kOrigin2));
+}
+
 // Verify that clearing autofill form data works.
 TEST_F(ChromeBrowsingDataRemoverDelegateTest, AutofillRemovalLastHour) {
   GetProfile()->CreateWebDataService();
diff --git a/chrome/browser/browsing_topics/browsing_topics_internals_browsertest.cc b/chrome/browser/browsing_topics/browsing_topics_internals_browsertest.cc
index e36b1a7..60ad48dd 100644
--- a/chrome/browser/browsing_topics/browsing_topics_internals_browsertest.cc
+++ b/chrome/browser/browsing_topics/browsing_topics_internals_browsertest.cc
@@ -259,7 +259,8 @@
             features::kPrivacySandboxAdsAPIsOverride,
             privacy_sandbox::kPrivacySandboxSettings3,
             optimization_guide::features::kPageContentAnnotations,
-            optimization_guide::features::kBatchAnnotationsValidation});
+            optimization_guide::features::kPageContentAnnotationsValidation,
+        });
   }
 
  protected:
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 33b6a05..4c6dba68 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -99,6 +99,7 @@
     "//ash/components/enhanced_network_tts/mojom",
     "//ash/components/fwupd",
     "//ash/components/geolocation",
+    "//ash/components/hid_detection:hid_detection",
     "//ash/components/login/auth",
     "//ash/components/login/session",
     "//ash/components/multidevice",
@@ -1658,6 +1659,7 @@
     "../ash/guest_os/public/guest_os_service_factory.h",
     "../ash/guest_os/public/guest_os_wayland_server.cc",
     "../ash/guest_os/public/guest_os_wayland_server.h",
+    "../ash/guest_os/public/types.h",
     "../ash/guest_os/virtual_machines/virtual_machines_util.cc",
     "../ash/guest_os/virtual_machines/virtual_machines_util.h",
     "../ash/guest_os/vm_sk_forwarding_native_message_host.cc",
@@ -4281,6 +4283,7 @@
     "../ash/file_manager/io_task_controller_unittest.cc",
     "../ash/file_manager/path_util_unittest.cc",
     "../ash/file_manager/speedometer_unittest.cc",
+    "../ash/file_manager/trash_io_task_unittest.cc",
     "../ash/file_manager/url_util_unittest.cc",
     "../ash/file_manager/volume_manager_unittest.cc",
     "../ash/file_system_provider/fake_extension_provider.cc",
diff --git a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
index 595a5439..020b40c2 100644
--- a/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/file_manager_private_apitest.cc
@@ -395,13 +395,31 @@
                       .AppendASCII("mount_path1")
                       .AsUTF8Unsafe(),
                   _))
-      .Times(1);
+      .WillOnce(
+          testing::Invoke([](const std::string&,
+                             DiskMountManager::UnmountPathCallback callback) {
+            std::move(callback).Run(chromeos::MOUNT_ERROR_NONE);
+          }))
+      .WillOnce(
+          testing::Invoke([](const std::string&,
+                             DiskMountManager::UnmountPathCallback callback) {
+            std::move(callback).Run(chromeos::MOUNT_ERROR_CANCELLED);
+          }));
   EXPECT_CALL(*disk_mount_manager_mock_,
               UnmountPath(chromeos::CrosDisksClient::GetArchiveMountPoint()
                               .AppendASCII("archive_mount_path")
                               .AsUTF8Unsafe(),
                           _))
-      .Times(1);
+      .WillOnce(
+          testing::Invoke([](const std::string&,
+                             DiskMountManager::UnmountPathCallback callback) {
+            std::move(callback).Run(chromeos::MOUNT_ERROR_NONE);
+          }))
+      .WillOnce(
+          testing::Invoke([](const std::string&,
+                             DiskMountManager::UnmountPathCallback callback) {
+            std::move(callback).Run(chromeos::MOUNT_ERROR_NEED_PASSWORD);
+          }));
 
   ASSERT_TRUE(RunExtensionTest("file_browser/mount_test", {},
                                {.load_as_component = true}))
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc b/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc
index 462bf08..8fe3b832 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_mount.cc
@@ -25,6 +25,7 @@
 #include "chrome/browser/chromeos/extensions/file_manager/private_api_util.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/extensions/api/file_manager_private.h"
+#include "chromeos/dbus/cros_disks/cros_disks_client.h"
 #include "components/drive/event_logger.h"
 #include "components/services/unzip/content/unzip_service.h"
 #include "components/services/unzip/public/cpp/unzip.h"
@@ -51,8 +52,7 @@
   EXTENSION_FUNCTION_VALIDATE(params);
 
   Profile* const profile = Profile::FromBrowserContext(browser_context());
-  drive::EventLogger* logger = file_manager::util::GetLogger(profile);
-  if (logger) {
+  if (drive::EventLogger* logger = file_manager::util::GetLogger(profile)) {
     logger->Log(logging::LOG_INFO, "%s[%d] called. (source: '%s')", name(),
                 request_id(),
                 params->file_url.empty() ? "(none)" : params->file_url.c_str());
@@ -62,9 +62,6 @@
   path_ = file_manager::util::GetLocalPathFromURL(render_frame_host(), profile,
                                                   GURL(params->file_url));
 
-  if (path_.empty())
-    return RespondNow(Error("Invalid path"));
-
   if (auto* notifier =
           file_manager::file_tasks::FileTasksNotifier::GetForProfile(profile)) {
     const scoped_refptr<storage::FileSystemContext> file_system_context =
@@ -138,17 +135,13 @@
 
   Profile* const profile = Profile::FromBrowserContext(browser_context());
 
-  drive::EventLogger* logger = file_manager::util::GetLogger(profile);
-  if (logger) {
+  if (drive::EventLogger* logger = file_manager::util::GetLogger(profile)) {
     logger->Log(logging::LOG_INFO, "%s[%d] called. (source: '%s')", name(),
                 request_id(),
                 params->file_url.empty() ? "(none)" : params->file_url.c_str());
   }
   set_log_on_completion(true);
 
-  if (params->file_url.empty())
-    return RespondNow(Error("Invalid path"));
-
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   base::FilePath path = file_manager::util::GetLocalPathFromURL(
@@ -179,8 +172,7 @@
   EXTENSION_FUNCTION_VALIDATE(params);
 
   Profile* const profile = Profile::FromBrowserContext(browser_context());
-  drive::EventLogger* logger = file_manager::util::GetLogger(profile);
-  if (logger) {
+  if (drive::EventLogger* logger = file_manager::util::GetLogger(profile)) {
     logger->Log(logging::LOG_INFO, "%s[%d] called. (volume_id: '%s')", name(),
                 request_id(), params->volume_id.c_str());
   }
@@ -204,8 +196,9 @@
     case file_manager::VOLUME_TYPE_MOUNTED_ARCHIVE_FILE: {
       DiskMountManager::GetInstance()->UnmountPath(
           volume->mount_path().value(),
-          DiskMountManager::UnmountPathCallback());
-      break;
+          base::BindOnce(
+              &FileManagerPrivateRemoveMountFunction::OnDiskUnmounted, this));
+      return RespondLater();
     }
     case file_manager::VOLUME_TYPE_PROVIDED: {
       auto* service =
@@ -216,27 +209,44 @@
                                    volume->file_system_id())) {
         return RespondNow(Error("Unmount failed"));
       }
-      break;
+      return RespondNow(NoArguments());
     }
     case file_manager::VOLUME_TYPE_CROSTINI:
       file_manager::VolumeManager::Get(profile)->RemoveSshfsCrostiniVolume(
-          volume->mount_path(), base::DoNothing());
-      break;
+          volume->mount_path(),
+          base::BindOnce(
+              &FileManagerPrivateRemoveMountFunction::OnSshFsUnmounted, this));
+      return RespondLater();
     case file_manager::VOLUME_TYPE_SMB:
       ash::smb_client::SmbServiceFactory::Get(profile)->UnmountSmbFs(
           volume->mount_path());
-      break;
+      return RespondNow(NoArguments());
     case file_manager::VOLUME_TYPE_GUEST_OS:
       // TODO(crbug/1293229): Figure out if we need to support unmounting. I'm
       // not actually sure if it's possible to reach here.
       NOTREACHED();
-      break;
+      [[fallthrough]];
     default:
       // Requested unmounting a device which is not unmountable.
       return RespondNow(Error("Invalid volume type"));
   }
+}
 
-  return RespondNow(NoArguments());
+void FileManagerPrivateRemoveMountFunction::OnSshFsUnmounted(bool ok) {
+  if (ok) {
+    return Respond(NoArguments());
+  }
+  return Respond(Error(file_manager_private::ToString(
+      api::file_manager_private::MOUNT_COMPLETED_STATUS_ERROR_UNKNOWN)));
+}
+
+void FileManagerPrivateRemoveMountFunction::OnDiskUnmounted(
+    chromeos::MountError error) {
+  if (error == chromeos::MOUNT_ERROR_NONE) {
+    return Respond(NoArguments());
+  }
+  return Respond(Error(file_manager_private::ToString(
+      file_manager::MountErrorToMountCompletedStatus(error))));
 }
 
 ExtensionFunction::ResponseAction
@@ -260,8 +270,7 @@
     log_string += volume->mount_path().AsUTF8Unsafe();
   }
 
-  drive::EventLogger* logger = file_manager::util::GetLogger(profile);
-  if (logger) {
+  if (drive::EventLogger* logger = file_manager::util::GetLogger(profile)) {
     logger->Log(logging::LOG_INFO,
                 "%s[%d] succeeded. (results: '[%s]', %" PRIuS " mount points)",
                 name(), request_id(), log_string.c_str(), result.size());
diff --git a/chrome/browser/chromeos/extensions/file_manager/private_api_mount.h b/chrome/browser/chromeos/extensions/file_manager/private_api_mount.h
index 040065f7..cc6f96f8 100644
--- a/chrome/browser/chromeos/extensions/file_manager/private_api_mount.h
+++ b/chrome/browser/chromeos/extensions/file_manager/private_api_mount.h
@@ -14,6 +14,7 @@
 #include "chromeos/dbus/cros_disks/cros_disks_client.h"
 #include "components/drive/file_errors.h"
 #include "third_party/ced/src/util/encodings/encodings.h"
+#include "third_party/cros_system_api/dbus/cros-disks/dbus-constants.h"
 
 namespace extensions {
 
@@ -79,6 +80,10 @@
 
   // ExtensionFunction overrides.
   ResponseAction Run() override;
+
+  void OnDiskUnmounted(chromeos::MountError error);
+
+  void OnSshFsUnmounted(bool ok);
 };
 
 // Implements chrome.fileManagerPrivate.getVolumeMetadataList method.
diff --git a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiController.java b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiController.java
index 6123e41..29f00c1 100644
--- a/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiController.java
+++ b/chrome/browser/download/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiController.java
@@ -69,6 +69,12 @@
     // TODO(shaktisahu): Find an alternative way after moving to offline content provider.
     void onNotificationShown(ContentId id, int notificationId);
 
+    /**
+     * Registers a new URL source for which a download interstitial download will be initiated.
+     * @param originalUrl The URL of the download.
+     */
+    void addDownloadInterstitialSource(String originalUrl);
+
     /** OfflineContentProvider.Observer methods. */
     @Override
     void onItemsAdded(List<OfflineItem> items);
diff --git a/chrome/browser/download/bubble/download_bubble_controller.cc b/chrome/browser/download/bubble/download_bubble_controller.cc
index c76f91b..46af2be 100644
--- a/chrome/browser/download/bubble/download_bubble_controller.cc
+++ b/chrome/browser/download/bubble/download_bubble_controller.cc
@@ -188,17 +188,18 @@
                      }),
       offline_items_.end());
   partial_view_ids_.erase(id);
-  display_controller_->OnRemovedItem();
+  display_controller_->OnRemovedItem(id);
 }
 
 void DownloadBubbleUIController::OnDownloadRemoved(
     content::DownloadManager* manager,
     download::DownloadItem* item) {
-  partial_view_ids_.erase(
+  const ContentId& id =
       DownloadItemModel::Wrap(
           item, std::make_unique<DownloadUIModel::BubbleStatusTextBuilder>())
-          ->GetContentId());
-  display_controller_->OnRemovedItem();
+          ->GetContentId();
+  partial_view_ids_.erase(id);
+  display_controller_->OnRemovedItem(id);
 }
 
 void DownloadBubbleUIController::OnItemUpdated(
diff --git a/chrome/browser/download/bubble/download_bubble_controller_unittest.cc b/chrome/browser/download/bubble/download_bubble_controller_unittest.cc
index 2742152..c9ccab39 100644
--- a/chrome/browser/download/bubble/download_bubble_controller_unittest.cc
+++ b/chrome/browser/download/bubble/download_bubble_controller_unittest.cc
@@ -49,7 +49,7 @@
   void MaybeShowButtonWhenCreated() override {}
   MOCK_METHOD1(OnNewItem, void(bool));
   MOCK_METHOD2(OnUpdatedItem, void(bool, bool));
-  MOCK_METHOD0(OnRemovedItem, void());
+  MOCK_METHOD1(OnRemovedItem, void(const ContentId&));
 };
 
 struct DownloadSortingState {
diff --git a/chrome/browser/download/bubble/download_display_controller.cc b/chrome/browser/download/bubble/download_display_controller.cc
index c9aa134..a15030a 100644
--- a/chrome/browser/download/bubble/download_display_controller.cc
+++ b/chrome/browser/download/bubble/download_display_controller.cc
@@ -77,7 +77,15 @@
   UpdateToolbarButtonState();
 }
 
-void DownloadDisplayController::OnRemovedItem() {
+void DownloadDisplayController::OnRemovedItem(const ContentId& id) {
+  std::vector<DownloadUIModelPtr> all_models =
+      bubble_controller_->GetAllItemsToDisplay();
+  // Hide the button if there is only one download item left and that item is
+  // about to be removed.
+  if (all_models.size() == 1 && all_models[0]->GetContentId() == id) {
+    HideToolbarButton();
+    return;
+  }
   UpdateToolbarButtonState();
 }
 
@@ -116,6 +124,10 @@
 
   std::vector<DownloadUIModelPtr> all_models =
       bubble_controller_->GetAllItemsToDisplay();
+  if (all_models.empty()) {
+    HideToolbarButton();
+    return;
+  }
   for (const auto& model : all_models) {
     if (model->GetDangerType() ==
             download::DOWNLOAD_DANGER_TYPE_ASYNC_SCANNING &&
@@ -188,6 +200,9 @@
                                  last_complete_time)) {
     return;
   }
+  if (bubble_controller_->GetAllItemsToDisplay().empty()) {
+    return;
+  }
   // If the last download complete time is less than
   // `kToolbarIconVisibilityTimeInterval` ago, show the button
   // immediately.
diff --git a/chrome/browser/download/bubble/download_display_controller.h b/chrome/browser/download/bubble/download_display_controller.h
index b543773..97f01d7 100644
--- a/chrome/browser/download/bubble/download_display_controller.h
+++ b/chrome/browser/download/bubble/download_display_controller.h
@@ -79,7 +79,7 @@
   // |show_details_if_done| as argument if the partial view should be shown.
   virtual void OnUpdatedItem(bool is_done, bool show_details_if_done);
   // Called from bubble controller when an item is deleted.
-  virtual void OnRemovedItem();
+  virtual void OnRemovedItem(const ContentId& id);
 
   download::AllDownloadItemNotifier& get_download_notifier_for_testing() {
     return download_notifier_;
diff --git a/chrome/browser/download/bubble/download_display_controller_unittest.cc b/chrome/browser/download/bubble/download_display_controller_unittest.cc
index 37834b6..bd6eefa 100644
--- a/chrome/browser/download/bubble/download_display_controller_unittest.cc
+++ b/chrome/browser/download/bubble/download_display_controller_unittest.cc
@@ -22,6 +22,7 @@
 #include "components/download/public/common/download_danger_type.h"
 #include "components/download/public/common/mock_download_item.h"
 #include "components/offline_items_collection/core/offline_item.h"
+#include "content/public/browser/download_item_utils.h"
 #include "content/public/test/browser_task_environment.h"
 #include "content/public/test/mock_download_manager.h"
 #include "content/public/test/test_utils.h"
@@ -30,6 +31,7 @@
 using testing::_;
 using testing::NiceMock;
 using testing::Return;
+using testing::ReturnRef;
 using testing::ReturnRefOfCopy;
 using testing::SetArgPointee;
 
@@ -207,6 +209,8 @@
     EXPECT_CALL(*manager_.get(), GetAllDownloads(_))
         .WillRepeatedly(SetArgPointee<0>(items));
     item(index).AddObserver(&controller().get_download_notifier_for_testing());
+    content::DownloadItemUtils::AttachInfoForTesting(&(item(index)), profile_,
+                                                     nullptr);
     controller().OnNewItem((state == download::DownloadItem::IN_PROGRESS) &&
                            show_details);
   }
@@ -250,6 +254,18 @@
                                show_details_if_done);
   }
 
+  void OnRemovedItem(const ContentId& id) { controller().OnRemovedItem(id); }
+
+  void RemoveLastDownload() {
+    items_.pop_back();
+    std::vector<download::DownloadItem*> items;
+    for (size_t i = 0; i < items_.size(); ++i) {
+      items.push_back(&item(i));
+    }
+    EXPECT_CALL(*manager_.get(), GetAllDownloads(_))
+        .WillRepeatedly(SetArgPointee<0>(items));
+  }
+
   bool VerifyDisplayState(bool shown,
                           bool detail_shown,
                           DownloadIconState icon_state,
@@ -534,7 +550,55 @@
                                  /*is_active=*/true));
 }
 
+TEST_F(DownloadDisplayControllerTest, UpdateToolbarButtonState_OnRemovedItem) {
+  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar.pdf"),
+                   download::DownloadItem::IN_PROGRESS);
+  std::string same_id = "Download 1";
+  std::string different_id = "Download 2";
+  EXPECT_CALL(item(0), GetGuid()).WillRepeatedly(ReturnRef(same_id));
+
+  OnRemovedItem(ContentId("LEGACY_DOWNLOAD", different_id));
+  // The download display is still shown, because the removed download is
+  // different.
+  EXPECT_TRUE(VerifyDisplayState(/*shown=*/true, /*detail_shown=*/true,
+                                 /*icon_state=*/DownloadIconState::kProgress,
+                                 /*is_active=*/true));
+
+  OnRemovedItem(ContentId("LEGACY_DOWNLOAD", same_id));
+  // The download display is hided, because the only item in the download list
+  // is about to be removed.
+  EXPECT_TRUE(VerifyDisplayState(/*shown=*/false, /*detail_shown=*/false,
+                                 /*icon_state=*/DownloadIconState::kProgress,
+                                 /*is_active=*/true));
+}
+
+TEST_F(DownloadDisplayControllerTest,
+       UpdateToolbarButtonState_OnRemovedItemMultipleDownloads) {
+  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar.pdf"),
+                   download::DownloadItem::IN_PROGRESS);
+  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar1.pdf"),
+                   download::DownloadItem::IN_PROGRESS);
+  std::vector<std::string> ids = {"Download 1", "Download 2"};
+  EXPECT_CALL(item(0), GetGuid()).WillRepeatedly(ReturnRef(ids[0]));
+  EXPECT_CALL(item(1), GetGuid()).WillRepeatedly(ReturnRef(ids[1]));
+
+  // The download display is still shown, because there are multiple downloads
+  // in the list.
+  OnRemovedItem(ContentId("LEGACY_DOWNLOAD", ids[0]));
+  EXPECT_TRUE(VerifyDisplayState(/*shown=*/true, /*detail_shown=*/true,
+                                 /*icon_state=*/DownloadIconState::kProgress,
+                                 /*is_active=*/true));
+
+  RemoveLastDownload();
+  OnRemovedItem(ContentId("LEGACY_DOWNLOAD", ids[0]));
+  EXPECT_TRUE(VerifyDisplayState(/*shown=*/false, /*detail_shown=*/false,
+                                 /*icon_state=*/DownloadIconState::kProgress,
+                                 /*is_active=*/true));
+}
+
 TEST_F(DownloadDisplayControllerTest, InitialState_OldLastDownload) {
+  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar.pdf"),
+                   download::DownloadItem::COMPLETE);
   base::Time current_time = base::Time::Now();
   // Set the last complete time to more than 1 day ago.
   DownloadPrefs::FromDownloadManager(&manager())
@@ -548,6 +612,8 @@
 }
 
 TEST_F(DownloadDisplayControllerTest, InitialState_NewLastDownload) {
+  InitDownloadItem(FILE_PATH_LITERAL("/foo/bar.pdf"),
+                   download::DownloadItem::COMPLETE);
   base::Time current_time = base::Time::Now();
   // Set the last complete time to less than 1 day ago.
   DownloadPrefs::FromDownloadManager(&manager())
@@ -568,6 +634,23 @@
                                  /*is_active=*/false));
 }
 
+TEST_F(DownloadDisplayControllerTest,
+       InitialState_NewLastDownloadWithEmptyItem) {
+  base::Time current_time = base::Time::Now();
+  // Set the last complete time to less than 1 day ago.
+  DownloadPrefs::FromDownloadManager(&manager())
+      ->SetLastCompleteTime(current_time - base::Hours(23));
+
+  DownloadDisplayController controller(&display(), profile(),
+                                       &bubble_controller());
+  // Although the last complete time is set, the download display is not shown
+  // because the download item list is empty. This can happen if the download
+  // history is deleted by the user.
+  EXPECT_TRUE(VerifyDisplayState(/*shown=*/false, /*detail_shown=*/false,
+                                 /*icon_state=*/DownloadIconState::kComplete,
+                                 /*is_active=*/false));
+}
+
 TEST_F(DownloadDisplayControllerTest, InitialState_NoLastDownload) {
   DownloadDisplayController controller(&display(), profile(),
                                        &bubble_controller());
diff --git a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java
index de41c8b..b28b1b3 100644
--- a/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java
+++ b/chrome/browser/download/internal/android/java/src/org/chromium/chrome/browser/download/DownloadMessageUiControllerImpl.java
@@ -279,6 +279,9 @@
     // paused items.
     private final Set<ContentId> mIgnoredItems = new HashSet<>();
 
+    // Used to calculate which items are being handled by a download interstitial.
+    private final Set<String> mDownloadInterstitialSources = new HashSet<>();
+
     // The notification IDs associated with the currently tracked completed items. The notification
     // should be removed when the message action button is clicked to open the item.
     private final Map<ContentId, Integer> mNotificationIds = new HashMap<>();
@@ -329,6 +332,15 @@
         mNotificationIds.put(id, notificationId);
     }
 
+    /**
+     * Registers a new URL source for which a download interstitial download will be initiated.
+     * @param originalUrl The URL of the download.
+     */
+    @Override
+    public void addDownloadInterstitialSource(String originalUrl) {
+        mDownloadInterstitialSources.add(originalUrl);
+    }
+
     @Override
     public void onItemsAdded(List<OfflineItem> items) {
         for (OfflineItem item : items) {
@@ -348,6 +360,10 @@
 
     @Override
     public void onItemUpdated(OfflineItem item, UpdateDelta updateDelta) {
+        if (mDownloadInterstitialSources.contains(item.originalUrl)) {
+            mDownloadInterstitialSources.remove(item.originalUrl);
+            mIgnoredItems.add(item.id);
+        }
         if (!isVisibleToUser(item)) return;
 
         if (updateDelta != null && !updateDelta.stateChanged
diff --git a/chrome/browser/enterprise/connectors/analysis/analysis_service_settings.cc b/chrome/browser/enterprise/connectors/analysis/analysis_service_settings.cc
index fed0fe2..8502e737 100644
--- a/chrome/browser/enterprise/connectors/analysis/analysis_service_settings.cc
+++ b/chrome/browser/enterprise/connectors/analysis/analysis_service_settings.cc
@@ -186,6 +186,12 @@
   return element->second.learn_more_url;
 }
 
+absl::optional<bool> AnalysisServiceSettings::GetBypassJustificationRequired(
+    const std::string& tag) {
+  return tags_requiring_justification_.find(tag) !=
+         tags_requiring_justification_.end();
+}
+
 void AnalysisServiceSettings::AddUrlPatternSettings(
     const base::Value& url_settings_value,
     bool enabled,
diff --git a/chrome/browser/enterprise/connectors/analysis/analysis_service_settings.h b/chrome/browser/enterprise/connectors/analysis/analysis_service_settings.h
index 0670ce0..2acaf99 100644
--- a/chrome/browser/enterprise/connectors/analysis/analysis_service_settings.h
+++ b/chrome/browser/enterprise/connectors/analysis/analysis_service_settings.h
@@ -37,6 +37,7 @@
   // settings are invalid or if the message/URL are empty.
   absl::optional<std::u16string> GetCustomMessage(const std::string& tag);
   absl::optional<GURL> GetLearnMoreUrl(const std::string& tag);
+  absl::optional<bool> GetBypassJustificationRequired(const std::string& tag);
 
   std::string service_provider_name() const { return service_provider_name_; }
 
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.cc
index 31efa36..fd87be8 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog.cc
@@ -863,9 +863,10 @@
 
   // Set the color to red initially because a 0 length message is invalid, but
   // the label doesn't have a Color Provider yet when it's created.
-  bypass_justification_text_length_->SetEnabledColor(
-      bypass_justification_text_length_->GetColorProvider()->GetColor(
-          ui::kColorAlertHighSeverity));
+  // TODO(b/232104687): Re-enable once the bug is fixed
+  // bypass_justification_text_length_->SetEnabledColor(
+  //     bypass_justification_text_length_->GetColorProvider()->GetColor(
+  //         ui::kColorAlertHighSeverity));
 }
 
 const gfx::ImageSkia* ContentAnalysisDialog::GetTopImage() const {
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_browsertest.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_browsertest.cc
index f99fd5a..49512c0 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_browsertest.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_dialog_browsertest.cc
@@ -6,6 +6,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
@@ -19,6 +20,7 @@
 #include "chrome/browser/ui/test/test_browser_dialog.h"
 #include "chrome/grit/generated_resources.h"
 #include "chrome/grit/theme_resources.h"
+#include "components/download/public/common/mock_download_item.h"
 #include "components/enterprise/common/proto/connectors.pb.h"
 #include "components/prefs/scoped_user_pref_update.h"
 #include "content/public/test/browser_test.h"
@@ -766,6 +768,9 @@
 
 class ContentAnalysisDialogPlainTests : public InProcessBrowserTest {
  public:
+  ContentAnalysisDialogPlainTests() {
+    scoped_feature_list_.InitAndEnableFeature(kBypassJustificationEnabled);
+  }
   void OpenCallback() { ++times_open_called_; }
 
   void DiscardCallback() { ++times_discard_called_; }
@@ -861,6 +866,7 @@
 
  private:
   raw_ptr<ContentAnalysisDialog> dialog_;
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_F(ContentAnalysisDialogPlainTests, TestCustomMessage) {
@@ -969,32 +975,44 @@
 
 IN_PROC_BROWSER_TEST_F(ContentAnalysisDialogPlainTests,
                        TestWithDownloadsDelegateBypassWarning) {
+  download::MockDownloadItem mock_download_item;
   ContentAnalysisDialog* dialog = CreateContentAnalysisDialog(
       std::make_unique<ContentAnalysisDownloadsDelegate>(
-          u"", u"", GURL(),
+          u"", u"", GURL(), true,
           base::BindOnce(&ContentAnalysisDialogPlainTests::OpenCallback,
                          base::Unretained(this)),
           base::BindOnce(&ContentAnalysisDialogPlainTests::DiscardCallback,
-                         base::Unretained(this))),
+                         base::Unretained(this)),
+          &mock_download_item),
       ContentAnalysisDelegateBase::FinalResult::WARNING);
 
   EXPECT_EQ(0, times_open_called_);
   EXPECT_EQ(0, times_discard_called_);
 
+  std::u16string test_user_justification = u"user's justification for bypass";
+  dialog->GetBypassJustificationTextareaForTesting()->InsertOrReplaceText(
+      test_user_justification);
   dialog->AcceptDialog();
   EXPECT_EQ(1, times_open_called_);
   EXPECT_EQ(0, times_discard_called_);
+  enterprise_connectors::ScanResult* stored_result =
+      static_cast<enterprise_connectors::ScanResult*>(
+          mock_download_item.GetUserData(
+              enterprise_connectors::ScanResult::kKey));
+  ASSERT_TRUE(stored_result);
+  EXPECT_EQ(stored_result->user_justification, test_user_justification);
 }
 
 IN_PROC_BROWSER_TEST_F(ContentAnalysisDialogPlainTests,
                        TestWithDownloadsDelegateDiscardWarning) {
   ContentAnalysisDialog* dialog = CreateContentAnalysisDialog(
       std::make_unique<ContentAnalysisDownloadsDelegate>(
-          u"", u"", GURL(),
+          u"", u"", GURL(), false,
           base::BindOnce(&ContentAnalysisDialogPlainTests::OpenCallback,
                          base::Unretained(this)),
           base::BindOnce(&ContentAnalysisDialogPlainTests::DiscardCallback,
-                         base::Unretained(this))),
+                         base::Unretained(this)),
+          nullptr),
       ContentAnalysisDelegateBase::FinalResult::WARNING);
 
   EXPECT_EQ(0, times_open_called_);
@@ -1009,11 +1027,12 @@
                        TestWithDownloadsDelegateDiscardBlock) {
   ContentAnalysisDialog* dialog = CreateContentAnalysisDialog(
       std::make_unique<ContentAnalysisDownloadsDelegate>(
-          u"", u"", GURL(),
+          u"", u"", GURL(), false,
           base::BindOnce(&ContentAnalysisDialogPlainTests::OpenCallback,
                          base::Unretained(this)),
           base::BindOnce(&ContentAnalysisDialogPlainTests::DiscardCallback,
-                         base::Unretained(this))),
+                         base::Unretained(this)),
+          nullptr),
       ContentAnalysisDelegateBase::FinalResult::FAILURE);
 
   EXPECT_EQ(0, times_open_called_);
@@ -1024,7 +1043,9 @@
   EXPECT_EQ(1, times_discard_called_);
 }
 
-class ContentAnalysysDialogUiTest : public DialogBrowserTest {
+class ContentAnalysysDialogUiTest
+    : public DialogBrowserTest,
+      public testing::WithParamInterface<std::tuple<bool, bool, bool>> {
  public:
   ContentAnalysysDialogUiTest() = default;
   ContentAnalysysDialogUiTest(const ContentAnalysysDialogUiTest&) = delete;
@@ -1032,11 +1053,24 @@
       delete;
   ~ContentAnalysysDialogUiTest() override = default;
 
+  bool custom_message_provided() const { return std::get<0>(GetParam()); }
+  bool custom_url_provided() const { return std::get<1>(GetParam()); }
+  bool bypass_justification_enabled() const { return std::get<2>(GetParam()); }
+
+  std::u16string get_custom_message() {
+    return custom_message_provided() ? u"Admin comment" : u"";
+  }
+
+  GURL get_custom_url() {
+    return custom_url_provided() ? GURL("http://learn-more-url.com/") : GURL();
+  }
+
   // DialogBrowserTest:
   void ShowUi(const std::string& name) override {
     auto delegate = std::make_unique<ContentAnalysisDownloadsDelegate>(
-        u"File Name", u"Admin comment", GURL("http://learn-more-url.com/"),
-        base::DoNothing(), base::DoNothing());
+        u"File Name", get_custom_message(), get_custom_url(),
+        bypass_justification_enabled(), base::DoNothing(), base::DoNothing(),
+        nullptr);
 
     // This ctor ends up calling into constrained_window to show itself, in a
     // way that relinquishes its ownership. Because of this, new it here and
@@ -1044,13 +1078,20 @@
     new ContentAnalysisDialog(
         std::move(delegate),
         browser()->tab_strip_model()->GetActiveWebContents(),
-        safe_browsing::DeepScanAccessPoint::DOWNLOAD, 0,
+        safe_browsing::DeepScanAccessPoint::DOWNLOAD, 1,
         ContentAnalysisDelegateBase::FinalResult::WARNING);
   }
 };
 
-IN_PROC_BROWSER_TEST_F(ContentAnalysysDialogUiTest, InvokeUi_default) {
+IN_PROC_BROWSER_TEST_P(ContentAnalysysDialogUiTest, InvokeUi_default) {
   ShowAndVerifyUi();
 }
 
+INSTANTIATE_TEST_SUITE_P(,
+                         ContentAnalysysDialogUiTest,
+                         testing::Combine(
+                             /*custom_message_exists*/ testing::Bool(),
+                             /*custom_url_exists*/ testing::Bool(),
+                             /*bypass_justification_enabled*/ testing::Bool()));
+
 }  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_downloads_delegate.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_downloads_delegate.cc
index c142d638..3350481 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_downloads_delegate.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_downloads_delegate.cc
@@ -4,7 +4,9 @@
 
 #include "chrome/browser/enterprise/connectors/analysis/content_analysis_downloads_delegate.h"
 
+#include "chrome/browser/enterprise/connectors/common.h"
 #include "chrome/browser/enterprise/connectors/connectors_service.h"
+#include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/enterprise/common/proto/connectors.pb.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -15,18 +17,39 @@
     const std::u16string& filename,
     const std::u16string& custom_message,
     GURL custom_learn_more_url,
+    bool bypass_justification_required,
     base::OnceCallback<void()> open_file_callback,
-    base::OnceCallback<void()> discard_file_callback)
+    base::OnceCallback<void()> discard_file_callback,
+    download::DownloadItem* download_item)
     : filename_(filename),
       custom_message_(custom_message),
       custom_learn_more_url_(custom_learn_more_url),
+      bypass_justification_required_(bypass_justification_required),
       open_file_callback_(std::move(open_file_callback)),
-      discard_file_callback_(std::move(discard_file_callback)) {}
+      discard_file_callback_(std::move(discard_file_callback)),
+      download_item_(download_item) {}
 
 ContentAnalysisDownloadsDelegate::~ContentAnalysisDownloadsDelegate() = default;
 
 void ContentAnalysisDownloadsDelegate::BypassWarnings(
     absl::optional<std::u16string> user_justification) {
+  if (download_item_) {
+    enterprise_connectors::ScanResult* stored_result =
+        static_cast<enterprise_connectors::ScanResult*>(
+            download_item_->GetUserData(
+                enterprise_connectors::ScanResult::kKey));
+
+    if (stored_result) {
+      stored_result->user_justification = user_justification;
+    } else {
+      auto stored_result =
+          std::make_unique<enterprise_connectors::ScanResult>();
+      stored_result->user_justification = user_justification;
+      download_item_->SetUserData(enterprise_connectors::ScanResult::kKey,
+                                  std::move(stored_result));
+    }
+  }
+
   if (open_file_callback_)
     std::move(open_file_callback_).Run();
   ResetCallbacks();
@@ -60,7 +83,10 @@
 }
 
 bool ContentAnalysisDownloadsDelegate::BypassRequiresJustification() const {
-  return false;
+  if (!base::FeatureList::IsEnabled(kBypassJustificationEnabled))
+    return false;
+
+  return bypass_justification_required_;
 }
 
 std::u16string ContentAnalysisDownloadsDelegate::GetBypassJustificationLabel()
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_downloads_delegate.h b/chrome/browser/enterprise/connectors/analysis/content_analysis_downloads_delegate.h
index b861422..4a42439 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_downloads_delegate.h
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_downloads_delegate.h
@@ -8,6 +8,10 @@
 #include "base/callback.h"
 #include "chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_base.h"
 
+namespace download {
+class DownloadItem;
+}
+
 namespace enterprise_connectors {
 
 // A ContentAnalysisDelegateBase implementation meant to be used to display the
@@ -19,8 +23,10 @@
       const std::u16string& filename,
       const std::u16string& custom_message,
       GURL custom_learn_more_url,
+      bool bypass_justification_required,
       base::OnceCallback<void()> open_file_callback,
-      base::OnceCallback<void()> discard_file_callback);
+      base::OnceCallback<void()> discard_file_callback,
+      download::DownloadItem* download_item);
   ~ContentAnalysisDownloadsDelegate() override;
 
   // Called when the user opts to keep the download and open it. Should not be
@@ -50,8 +56,10 @@
   std::u16string filename_;
   std::u16string custom_message_;
   GURL custom_learn_more_url_;
+  bool bypass_justification_required_;
   base::OnceCallback<void()> open_file_callback_;
   base::OnceCallback<void()> discard_file_callback_;
+  download::DownloadItem* download_item_;
 };
 
 }  // namespace enterprise_connectors
diff --git a/chrome/browser/enterprise/connectors/analysis/content_analysis_downloads_delegate_unittest.cc b/chrome/browser/enterprise/connectors/analysis/content_analysis_downloads_delegate_unittest.cc
index 52cb82c..fc541b3b 100644
--- a/chrome/browser/enterprise/connectors/analysis/content_analysis_downloads_delegate_unittest.cc
+++ b/chrome/browser/enterprise/connectors/analysis/content_analysis_downloads_delegate_unittest.cc
@@ -6,6 +6,8 @@
 
 #include <gtest/gtest.h>
 
+#include "components/download/public/common/mock_download_item.h"
+
 namespace enterprise_connectors {
 
 class ContentAnalysisDownloadsDelegateTest : public testing::Test {
@@ -16,22 +18,24 @@
 
   int times_open_called_ = 0;
   int times_discard_called_ = 0;
+  download::MockDownloadItem mock_download_item;
 };
 
 TEST_F(ContentAnalysisDownloadsDelegateTest, TestOpenFile) {
   ContentAnalysisDownloadsDelegate delegate(
-      u"", u"", GURL(),
+      u"", u"", GURL(), true,
       base::BindOnce(&ContentAnalysisDownloadsDelegateTest::OpenCallback,
                      base::Unretained(this)),
       base::BindOnce(&ContentAnalysisDownloadsDelegateTest::DiscardCallback,
-                     base::Unretained(this)));
+                     base::Unretained(this)),
+      &mock_download_item);
 
-  delegate.BypassWarnings(absl::nullopt);
+  delegate.BypassWarnings(u"User's justification");
   EXPECT_EQ(1, times_open_called_);
   EXPECT_EQ(0, times_discard_called_);
 
   // Attempting any action after one has been performed is a no-op.
-  delegate.BypassWarnings(absl::nullopt);
+  delegate.BypassWarnings(u"User's justification");
   EXPECT_EQ(1, times_open_called_);
   EXPECT_EQ(0, times_discard_called_);
 
@@ -46,11 +50,12 @@
 
 TEST_F(ContentAnalysisDownloadsDelegateTest, TestDiscardFileWarning) {
   ContentAnalysisDownloadsDelegate delegate(
-      u"", u"", GURL(),
+      u"", u"", GURL(), true,
       base::BindOnce(&ContentAnalysisDownloadsDelegateTest::OpenCallback,
                      base::Unretained(this)),
       base::BindOnce(&ContentAnalysisDownloadsDelegateTest::DiscardCallback,
-                     base::Unretained(this)));
+                     base::Unretained(this)),
+      &mock_download_item);
 
   delegate.Cancel(true);
   EXPECT_EQ(0, times_open_called_);
@@ -72,11 +77,12 @@
 
 TEST_F(ContentAnalysisDownloadsDelegateTest, TestDiscardFileBlock) {
   ContentAnalysisDownloadsDelegate delegate(
-      u"", u"", GURL(),
+      u"", u"", GURL(), true,
       base::BindOnce(&ContentAnalysisDownloadsDelegateTest::OpenCallback,
                      base::Unretained(this)),
       base::BindOnce(&ContentAnalysisDownloadsDelegateTest::DiscardCallback,
-                     base::Unretained(this)));
+                     base::Unretained(this)),
+      &mock_download_item);
 
   delegate.Cancel(false);
   EXPECT_EQ(0, times_open_called_);
@@ -98,11 +104,12 @@
 
 TEST_F(ContentAnalysisDownloadsDelegateTest, TestNoMessageOrUrlReturnsNullOpt) {
   ContentAnalysisDownloadsDelegate delegate(
-      u"", u"", GURL(),
+      u"", u"", GURL(), true,
       base::BindOnce(&ContentAnalysisDownloadsDelegateTest::OpenCallback,
                      base::Unretained(this)),
       base::BindOnce(&ContentAnalysisDownloadsDelegateTest::DiscardCallback,
-                     base::Unretained(this)));
+                     base::Unretained(this)),
+      &mock_download_item);
 
   EXPECT_FALSE(delegate.GetCustomMessage());
   EXPECT_FALSE(delegate.GetCustomLearnMoreUrl());
@@ -110,11 +117,12 @@
 
 TEST_F(ContentAnalysisDownloadsDelegateTest, TestGetMessageAndUrl) {
   ContentAnalysisDownloadsDelegate delegate(
-      u"foo.txt", u"Message", GURL("http://www.example.com"),
+      u"foo.txt", u"Message", GURL("http://www.example.com"), true,
       base::BindOnce(&ContentAnalysisDownloadsDelegateTest::OpenCallback,
                      base::Unretained(this)),
       base::BindOnce(&ContentAnalysisDownloadsDelegateTest::DiscardCallback,
-                     base::Unretained(this)));
+                     base::Unretained(this)),
+      nullptr);
 
   EXPECT_TRUE(delegate.GetCustomMessage());
   EXPECT_TRUE(delegate.GetCustomLearnMoreUrl());
diff --git a/chrome/browser/enterprise/connectors/common.cc b/chrome/browser/enterprise/connectors/common.cc
index bc724ec..170a513 100644
--- a/chrome/browser/enterprise/connectors/common.cc
+++ b/chrome/browser/enterprise/connectors/common.cc
@@ -158,6 +158,7 @@
 FileMetadata::~FileMetadata() = default;
 
 const char ScanResult::kKey[] = "enterprise_connectors.scan_result_key";
+ScanResult::ScanResult() = default;
 ScanResult::ScanResult(FileMetadata metadata) {
   file_metadata.push_back(std::move(metadata));
 }
diff --git a/chrome/browser/enterprise/connectors/common.h b/chrome/browser/enterprise/connectors/common.h
index 16c4c7e..65c707b 100644
--- a/chrome/browser/enterprise/connectors/common.h
+++ b/chrome/browser/enterprise/connectors/common.h
@@ -185,11 +185,13 @@
 // User data class to persist scanning results for multiple files corresponding
 // to a single base::SupportsUserData object.
 struct ScanResult : public base::SupportsUserData::Data {
+  ScanResult();
   explicit ScanResult(FileMetadata metadata);
   ~ScanResult() override;
   static const char kKey[];
 
   std::vector<FileMetadata> file_metadata;
+  absl::optional<std::u16string> user_justification;
 };
 
 // User data to persist a save package's final callback allowing/denying
diff --git a/chrome/browser/enterprise/connectors/connectors_manager.cc b/chrome/browser/enterprise/connectors/connectors_manager.cc
index a07996a9..d2d17ea 100644
--- a/chrome/browser/enterprise/connectors/connectors_manager.cc
+++ b/chrome/browser/enterprise/connectors/connectors_manager.cc
@@ -242,6 +242,23 @@
   return absl::nullopt;
 }
 
+absl::optional<bool> ConnectorsManager::GetBypassJustificationRequired(
+    AnalysisConnector connector,
+    const std::string& tag) {
+  if (IsConnectorEnabled(connector)) {
+    if (analysis_connector_settings_.count(connector) == 0)
+      CacheAnalysisConnectorPolicy(connector);
+
+    if (analysis_connector_settings_.count(connector) &&
+        !analysis_connector_settings_.at(connector).empty()) {
+      return analysis_connector_settings_.at(connector)
+          .at(0)
+          .GetBypassJustificationRequired(tag);
+    }
+  }
+  return absl::nullopt;
+}
+
 std::vector<std::string> ConnectorsManager::GetAnalysisServiceProviderNames(
     AnalysisConnector connector) {
   if (IsConnectorEnabled(connector)) {
diff --git a/chrome/browser/enterprise/connectors/connectors_manager.h b/chrome/browser/enterprise/connectors/connectors_manager.h
index 7d73b22..9077eb4 100644
--- a/chrome/browser/enterprise/connectors/connectors_manager.h
+++ b/chrome/browser/enterprise/connectors/connectors_manager.h
@@ -68,6 +68,9 @@
                                                   const std::string& tag);
   absl::optional<GURL> GetLearnMoreUrl(AnalysisConnector connector,
                                        const std::string& tag);
+  absl::optional<bool> GetBypassJustificationRequired(
+      AnalysisConnector connector,
+      const std::string& tag);
 
   std::vector<std::string> GetAnalysisServiceProviderNames(
       AnalysisConnector connector);
diff --git a/chrome/browser/enterprise/connectors/connectors_service.cc b/chrome/browser/enterprise/connectors/connectors_service.cc
index 2284208c0..00bb07c 100644
--- a/chrome/browser/enterprise/connectors/connectors_service.cc
+++ b/chrome/browser/enterprise/connectors/connectors_service.cc
@@ -369,6 +369,15 @@
   return connectors_manager_->GetLearnMoreUrl(connector, tag);
 }
 
+absl::optional<bool> ConnectorsService::GetBypassJustificationRequired(
+    AnalysisConnector connector,
+    const std::string& tag) {
+  if (!ConnectorsEnabled())
+    return absl::nullopt;
+
+  return connectors_manager_->GetBypassJustificationRequired(connector, tag);
+}
+
 bool ConnectorsService::HasCustomInfoToDisplay(AnalysisConnector connector,
                                                const std::string& tag) {
   return GetCustomMessage(connector, tag) || GetLearnMoreUrl(connector, tag);
diff --git a/chrome/browser/enterprise/connectors/connectors_service.h b/chrome/browser/enterprise/connectors/connectors_service.h
index eb4360f0..95e07e8 100644
--- a/chrome/browser/enterprise/connectors/connectors_service.h
+++ b/chrome/browser/enterprise/connectors/connectors_service.h
@@ -67,6 +67,9 @@
                                                   const std::string& tag);
   absl::optional<GURL> GetLearnMoreUrl(AnalysisConnector connector,
                                        const std::string& tag);
+  absl::optional<bool> GetBypassJustificationRequired(
+      AnalysisConnector connector,
+      const std::string& tag);
   bool HasCustomInfoToDisplay(AnalysisConnector connector,
                               const std::string& tag);
 
diff --git a/chrome/browser/extensions/background_script_executor_browsertest.cc b/chrome/browser/extensions/background_script_executor_browsertest.cc
index d3e569f..715b8bb 100644
--- a/chrome/browser/extensions/background_script_executor_browsertest.cc
+++ b/chrome/browser/extensions/background_script_executor_browsertest.cc
@@ -67,7 +67,14 @@
           "background": {"scripts": ["background.js"]},
           "version": "0.1"
         })";
-  constexpr char kBackgroundScript[] = R"(self.myTestFlag = 'HELLO!';)";
+
+  constexpr char kBackgroundScript[] = R"(
+    function createResult() {
+      return {
+        testFlag: 'flag',
+        userGesture: chrome.test.isProcessingUserGesture(),
+      };
+    })";
 
   TestExtensionDir test_dir;
   test_dir.WriteManifest(kManifest);
@@ -76,38 +83,59 @@
   ASSERT_TRUE(extension);
 
   {
+    // Synchronous result with no user gesture.
+    // NOTE: This test has to come first. User gestures are timed, so once a
+    // script executes with a user gesture, it affects subsequent injections
+    // because the gesture is still considered active.
+    // (This is okay because we really only need to check once each for user
+    // gesture; if we wanted to do more involved testing, we'll need to pull
+    // these two tests apart or otherwise flush the gesture state.)
+    base::Value value = BackgroundScriptExecutor::ExecuteScript(
+        profile(), extension->id(),
+        "chrome.test.sendScriptResult(createResult());",
+        BackgroundScriptExecutor::ResultCapture::kSendScriptResult,
+        browsertest_util::ScriptUserActivation::kDontActivate);
+    EXPECT_THAT(value, base::test::IsJson(
+                           R"({"testFlag":"flag","userGesture":false})"));
+  }
+
+  {
     // Synchronous result.
     base::Value value = BackgroundScriptExecutor::ExecuteScript(
-        profile(), extension->id(), "chrome.test.sendScriptResult(myTestFlag);",
+        profile(), extension->id(),
+        "chrome.test.sendScriptResult(createResult());",
         BackgroundScriptExecutor::ResultCapture::kSendScriptResult,
         browsertest_util::ScriptUserActivation::kActivate);
-    EXPECT_THAT(value, base::test::IsJson(R"("HELLO!")"));
+    EXPECT_THAT(
+        value, base::test::IsJson(R"({"testFlag":"flag","userGesture":true})"));
   }
 
   {
     // Asynchronous result with sendScriptResult().
     static constexpr char kScript[] =
         R"(setTimeout(() => {
-             chrome.test.sendScriptResult(myTestFlag);
+             chrome.test.sendScriptResult(createResult());
            }, 0);)";
     base::Value value = BackgroundScriptExecutor::ExecuteScript(
         profile(), extension->id(), kScript,
         BackgroundScriptExecutor::ResultCapture::kSendScriptResult,
         browsertest_util::ScriptUserActivation::kActivate);
-    EXPECT_THAT(value, base::test::IsJson(R"("HELLO!")"));
+    EXPECT_THAT(
+        value, base::test::IsJson(R"({"testFlag":"flag","userGesture":true})"));
   }
 
   {
     // Asynchronous result with domAutomationController.send().
     static constexpr char kScript[] =
         R"(setTimeout(() => {
-             window.domAutomationController.send(myTestFlag);
+             window.domAutomationController.send(createResult());
            }, 0);)";
     base::Value value = BackgroundScriptExecutor::ExecuteScript(
         profile(), extension->id(), kScript,
         BackgroundScriptExecutor::ResultCapture::kWindowDomAutomationController,
         browsertest_util::ScriptUserActivation::kActivate);
-    EXPECT_THAT(value, base::test::IsJson(R"("HELLO!")"));
+    EXPECT_THAT(
+        value, base::test::IsJson(R"({"testFlag":"flag","userGesture":true})"));
   }
 }
 
diff --git a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedMainMenuItem.java b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedMainMenuItem.java
index bb3404e..244771c 100644
--- a/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedMainMenuItem.java
+++ b/chrome/browser/feed/android/java/src/org/chromium/chrome/browser/feed/webfeed/WebFeedMainMenuItem.java
@@ -123,7 +123,7 @@
             initializeFavicon(result);
             initializeText(result);
             initializeChipView(result);
-            initializeCrowButton();
+            initializeCrowButton(result);
 
             if (mChipView != null && mTab.isShowingErrorPage()) {
                 mChipView.setEnabled(false);
@@ -166,9 +166,11 @@
         }
     }
 
-    private void initializeCrowButton() {
+    private void initializeCrowButton(WebFeedMetadata webFeedMetadata) {
+        boolean isFollowing = webFeedMetadata != null
+                && webFeedMetadata.subscriptionStatus == WebFeedSubscriptionStatus.SUBSCRIBED;
         if (mCrowButtonDelegate.isEnabledForSite(mUrl)) {
-            showCrowButton();
+            showCrowButton(isFollowing);
         }
     }
 
@@ -268,14 +270,14 @@
         chipView.setVisibility(View.VISIBLE);
     }
 
-    private void showCrowButton() {
+    private void showCrowButton(boolean isFollowing) {
         mCrowButton.getPrimaryTextView().setText(mCrowButtonDelegate.getButtonText());
         mCrowButton.setOnClickListener((view) -> {
             if (mTab == null) return;
             RecordUserAction.record("Crow.LaunchCustomTab.AppMenu");
             Activity activity = mTab.getWindowAndroid().getActivity().get();
             mCrowButtonDelegate.requestCanonicalUrl(mTab, (canonicalUrl) -> {
-                mCrowButtonDelegate.launchCustomTab(activity, mUrl, canonicalUrl);
+                mCrowButtonDelegate.launchCustomTab(activity, mUrl, canonicalUrl, isFollowing);
             });
         });
         RecordUserAction.record("Crow.EntryPointShown.AppMenu");
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 1042432..981a5a2 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -690,6 +690,11 @@
     "expiry_milestone" : 103
   },
   {
+    "name": "calendar-view-debug-mode",
+    "owners": [ "rtinkoff" ],
+    "expiry_milestone": 113
+  },
+  {
     "name": "canvas-2d-layers",
     "owners": [ "alisalin", "fserb", "juanmihd", "yiyix" ],
     "expiry_milestone": 100
@@ -861,7 +866,7 @@
   {
     "name": "color-provider-redirection-for-theme-provider",
     "owners": [ "//ui/color/OWNERS" ],
-    "expiry_milestone": 102
+    "expiry_milestone": 110
   },
   {
     "name": "colr-v1-fonts",
@@ -5203,7 +5208,7 @@
     "name": "restore-session-from-cache",
     "owners": [ "justincohen", "gambard", "bling-flags@google.com" ],
     // Needed for manual testing of native session restore flow on iOS.
-    "expiry_milestone": 101
+    "expiry_milestone": 110
   },
   {
     "name": "restrict-gamepad-access",
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 8298631..022aaa0 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -4242,6 +4242,15 @@
     "Show Monthly Calendar View with Google Calendar events to increase "
     "productivity by helping users view their schedules more quickly.";
 
+const char kCalendarModelDebugModeName[] = "Monthly Calendar Model Debug Mode";
+const char kCalendarModelDebugModeDescription[] =
+    "Debug mode for Monthly Calendar Model. This helps a lot in diagnosing any "
+    "bugs in the calendar's event fetching/caching functionality. WARNING: DO "
+    "NOT enable this flag unless you're OK with information about your "
+    "calendar events, such as start/end times and summaries, being dumped to "
+    "the system logs, where they are potentially visible to all users of the "
+    "device.";
+
 const char kCaptureSelfieCamName[] = "Enable selfie camera in screen capture";
 const char kCaptureSelfieCamDescription[] =
     "Enables the ability to record the selected camera feed along with screen "
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 35798a90..9076c32 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2425,6 +2425,9 @@
 extern const char kCalendarViewName[];
 extern const char kCalendarViewDescription[];
 
+extern const char kCalendarModelDebugModeName[];
+extern const char kCalendarModelDebugModeDescription[];
+
 extern const char kCaptureSelfieCamName[];
 extern const char kCaptureSelfieCamDescription[];
 
diff --git a/chrome/browser/lacros/account_manager/account_manager_util.cc b/chrome/browser/lacros/account_manager/account_manager_util.cc
index 7e46743..74debe1d 100644
--- a/chrome/browser/lacros/account_manager/account_manager_util.cc
+++ b/chrome/browser/lacros/account_manager/account_manager_util.cc
@@ -36,28 +36,7 @@
   std::move(callback).Run(result);
 }
 
-void GetAccountsAvailableAsPrimaryImpl(
-    ProfileAttributesStorage* storage,
-    AccountProfileMapper::ListAccountsCallback callback,
-    const std::map<base::FilePath, std::vector<account_manager::Account>>&
-        accounts_map) {
-  // Collect all primary syncing accounts.
-  std::vector<std::string> syncing_gaia_ids_temp;
-  for (ProfileAttributesEntry* entry : storage->GetAllProfilesAttributes()) {
-    // Skip if not syncing.
-    if (!entry->IsAuthenticated())
-      continue;
-    DCHECK(!entry->GetGAIAId().empty());
-    syncing_gaia_ids_temp.push_back(entry->GetGAIAId());
-  }
-  // Insert them all at once to avoid O(N^2) complexity.
-  base::flat_set<std::string> syncing_gaia_ids(
-      std::move(syncing_gaia_ids_temp));
-
-  GetAccountsNotDenylisted(accounts_map, syncing_gaia_ids, std::move(callback));
-}
-
-void GetAccountsAvailableAsSecondaryImpl(
+void GetAllAvailableAccountsImpl(
     const base::FilePath& profile_path,
     AccountProfileMapper::ListAccountsCallback callback,
     const std::map<base::FilePath, std::vector<account_manager::Account>>&
@@ -80,23 +59,12 @@
 
 }  // namespace
 
-void GetAccountsAvailableAsPrimary(
-    AccountProfileMapper* mapper,
-    ProfileAttributesStorage* storage,
-    AccountProfileMapper::ListAccountsCallback callback) {
-  DCHECK(mapper);
-  DCHECK(storage);
-  DCHECK(callback);
-  mapper->GetAccountsMap(base::BindOnce(&GetAccountsAvailableAsPrimaryImpl,
-                                        storage, std::move(callback)));
-}
-
-void GetAccountsAvailableAsSecondary(
+void GetAllAvailableAccounts(
     AccountProfileMapper* mapper,
     const base::FilePath& profile_path,
     AccountProfileMapper::ListAccountsCallback callback) {
   DCHECK(mapper);
   DCHECK(callback);
-  mapper->GetAccountsMap(base::BindOnce(&GetAccountsAvailableAsSecondaryImpl,
+  mapper->GetAccountsMap(base::BindOnce(&GetAllAvailableAccountsImpl,
                                         profile_path, std::move(callback)));
 }
diff --git a/chrome/browser/lacros/account_manager/account_manager_util.h b/chrome/browser/lacros/account_manager/account_manager_util.h
index 16f65193..7fa1d7d2 100644
--- a/chrome/browser/lacros/account_manager/account_manager_util.h
+++ b/chrome/browser/lacros/account_manager/account_manager_util.h
@@ -7,19 +7,6 @@
 
 #include "chrome/browser/lacros/account_manager/account_profile_mapper.h"
 
-class ProfileAttributesStorage;
-
-// Lists accounts that are available as primary accounts for a new profile. This
-// passes back all accounts in the OS that are not used as syncing accounts in
-// some other profile. The accounts are returned in an arbitrary order. The only
-// async part of this function is retrieving accounts from `mapper`, i.e.
-// `callback` gets called iff `mapper` does not get deleted before completion of
-// the task.
-void GetAccountsAvailableAsPrimary(
-    AccountProfileMapper* mapper,
-    ProfileAttributesStorage* storage,
-    AccountProfileMapper::ListAccountsCallback callback);
-
 // Lists accounts that are available as secondary accounts for profile with
 // `profile_path`. This passes back all accounts in the OS, excluding the
 // accounts that are already present in the given profile. The accounts are
@@ -28,7 +15,7 @@
 // async part of this function is retrieving accounts from `mapper`, i.e.
 // `callback` gets called iff `mapper` does not get deleted before completion of
 // the task.
-void GetAccountsAvailableAsSecondary(
+void GetAllAvailableAccounts(
     AccountProfileMapper* mapper,
     const base::FilePath& profile_path,
     AccountProfileMapper::ListAccountsCallback callback);
diff --git a/chrome/browser/lacros/account_manager/account_manager_util_unittest.cc b/chrome/browser/lacros/account_manager/account_manager_util_unittest.cc
index 439ea19a..46f4450 100644
--- a/chrome/browser/lacros/account_manager/account_manager_util_unittest.cc
+++ b/chrome/browser/lacros/account_manager/account_manager_util_unittest.cc
@@ -145,41 +145,7 @@
   base::FilePath main_path_;
 };
 
-TEST_F(AccountManagerUtilTest, GetAccountsAvailableAsPrimary) {
-  base::FilePath other_path = GetProfilePath("Other");
-  std::unique_ptr<AccountProfileMapper> mapper =
-      CreateMapper({{main_path(), {"A"}}, {other_path, {"B", "C"}}});
-
-  base::MockRepeatingCallback<void(const std::vector<Account>&)> mock_callback;
-
-  // All the non-syncing accounts are returned.
-  EXPECT_CALL(mock_callback,
-              Run(testing::UnorderedElementsAre(
-                  Field(&Account::key, AccountKey{"A", kGaiaType}),
-                  Field(&Account::key, AccountKey{"B", kGaiaType}),
-                  Field(&Account::key, AccountKey{"C", kGaiaType}))));
-  GetAccountsAvailableAsPrimary(mapper.get(), attributes_storage(),
-                                mock_callback.Get());
-  testing::Mock::VerifyAndClearExpectations(&mock_callback);
-
-  // Check that only the non-syncing accounts are returned.
-  SetPrimaryAccountForProfile(other_path, "B");
-  EXPECT_CALL(mock_callback,
-              Run(testing::UnorderedElementsAre(
-                  Field(&Account::key, AccountKey{"A", kGaiaType}),
-                  Field(&Account::key, AccountKey{"C", kGaiaType}))));
-  GetAccountsAvailableAsPrimary(mapper.get(), attributes_storage(),
-                                mock_callback.Get());
-  testing::Mock::VerifyAndClearExpectations(&mock_callback);
-
-  SetPrimaryAccountForProfile(main_path(), "A");
-  EXPECT_CALL(mock_callback, Run(testing::UnorderedElementsAre(Field(
-                                 &Account::key, AccountKey{"C", kGaiaType}))));
-  GetAccountsAvailableAsPrimary(mapper.get(), attributes_storage(),
-                                mock_callback.Get());
-}
-
-TEST_F(AccountManagerUtilTest, GetAccountsAvailableAsSecondary) {
+TEST_F(AccountManagerUtilTest, GetAllAvailableAccounts) {
   base::FilePath second_path = GetProfilePath("Second");
   base::FilePath third_path = GetProfilePath("Third");
   base::FilePath unassigned = base::FilePath();
@@ -198,8 +164,7 @@
                   Field(&Account::key, AccountKey{"C", kGaiaType}),
                   Field(&Account::key, AccountKey{"D", kGaiaType}),
                   Field(&Account::key, AccountKey{"E", kGaiaType}))));
-  GetAccountsAvailableAsSecondary(mapper.get(), main_path(),
-                                  mock_callback.Get());
+  GetAllAvailableAccounts(mapper.get(), main_path(), mock_callback.Get());
   testing::Mock::VerifyAndClearExpectations(&mock_callback);
 
   EXPECT_CALL(mock_callback,
@@ -207,8 +172,7 @@
                   Field(&Account::key, AccountKey{"A", kGaiaType}),
                   Field(&Account::key, AccountKey{"D", kGaiaType}),
                   Field(&Account::key, AccountKey{"E", kGaiaType}))));
-  GetAccountsAvailableAsSecondary(mapper.get(), second_path,
-                                  mock_callback.Get());
+  GetAllAvailableAccounts(mapper.get(), second_path, mock_callback.Get());
   testing::Mock::VerifyAndClearExpectations(&mock_callback);
 
   // Syncing status does not change anything here.
@@ -220,8 +184,7 @@
                   Field(&Account::key, AccountKey{"C", kGaiaType}),
                   Field(&Account::key, AccountKey{"D", kGaiaType}),
                   Field(&Account::key, AccountKey{"E", kGaiaType}))));
-  GetAccountsAvailableAsSecondary(mapper.get(), main_path(),
-                                  mock_callback.Get());
+  GetAllAvailableAccounts(mapper.get(), main_path(), mock_callback.Get());
   testing::Mock::VerifyAndClearExpectations(&mock_callback);
 
   EXPECT_CALL(mock_callback,
@@ -229,8 +192,7 @@
                   Field(&Account::key, AccountKey{"A", kGaiaType}),
                   Field(&Account::key, AccountKey{"D", kGaiaType}),
                   Field(&Account::key, AccountKey{"E", kGaiaType}))));
-  GetAccountsAvailableAsSecondary(mapper.get(), second_path,
-                                  mock_callback.Get());
+  GetAllAvailableAccounts(mapper.get(), second_path, mock_callback.Get());
   testing::Mock::VerifyAndClearExpectations(&mock_callback);
 
   // Non existing profile path or empty profile path returns all accounts.
@@ -243,10 +205,8 @@
                   Field(&Account::key, AccountKey{"D", kGaiaType}),
                   Field(&Account::key, AccountKey{"E", kGaiaType}))))
       .Times(2);
-  GetAccountsAvailableAsSecondary(mapper.get(), non_existing_path,
-                                  mock_callback.Get());
-  GetAccountsAvailableAsSecondary(mapper.get(), base::FilePath(),
-                                  mock_callback.Get());
+  GetAllAvailableAccounts(mapper.get(), non_existing_path, mock_callback.Get());
+  GetAllAvailableAccounts(mapper.get(), base::FilePath(), mock_callback.Get());
   testing::Mock::VerifyAndClearExpectations(&mock_callback);
 }
 
@@ -265,16 +225,14 @@
               Run(testing::UnorderedElementsAre(
                   Field(&Account::key, AccountKey{"C", kGaiaType}),
                   Field(&Account::key, AccountKey{"E", kGaiaType}))));
-  GetAccountsAvailableAsSecondary(mapper.get(), main_path(),
-                                  mock_callback.Get());
+  GetAllAvailableAccounts(mapper.get(), main_path(), mock_callback.Get());
   testing::Mock::VerifyAndClearExpectations(&mock_callback);
 
   EXPECT_CALL(mock_callback,
               Run(testing::UnorderedElementsAre(
                   Field(&Account::key, AccountKey{"A", kGaiaType}),
                   Field(&Account::key, AccountKey{"E", kGaiaType}))));
-  GetAccountsAvailableAsSecondary(mapper.get(), second_path,
-                                  mock_callback.Get());
+  GetAllAvailableAccounts(mapper.get(), second_path, mock_callback.Get());
   testing::Mock::VerifyAndClearExpectations(&mock_callback);
 
   // Syncing status does not change anything here.
@@ -284,15 +242,13 @@
               Run(testing::UnorderedElementsAre(
                   Field(&Account::key, AccountKey{"C", kGaiaType}),
                   Field(&Account::key, AccountKey{"E", kGaiaType}))));
-  GetAccountsAvailableAsSecondary(mapper.get(), main_path(),
-                                  mock_callback.Get());
+  GetAllAvailableAccounts(mapper.get(), main_path(), mock_callback.Get());
   testing::Mock::VerifyAndClearExpectations(&mock_callback);
 
   EXPECT_CALL(mock_callback,
               Run(testing::UnorderedElementsAre(
                   Field(&Account::key, AccountKey{"A", kGaiaType}),
                   Field(&Account::key, AccountKey{"E", kGaiaType}))));
-  GetAccountsAvailableAsSecondary(mapper.get(), second_path,
-                                  mock_callback.Get());
+  GetAllAvailableAccounts(mapper.get(), second_path, mock_callback.Get());
   testing::Mock::VerifyAndClearExpectations(&mock_callback);
 }
diff --git a/chrome/browser/lacros/account_manager/signin_helper_lacros.cc b/chrome/browser/lacros/account_manager/signin_helper_lacros.cc
index 40cee41..fa2eebf 100644
--- a/chrome/browser/lacros/account_manager/signin_helper_lacros.cc
+++ b/chrome/browser/lacros/account_manager/signin_helper_lacros.cc
@@ -32,7 +32,7 @@
             consistency_cookie_manager->CreateScopedAccountUpdate());
   }
 
-  GetAccountsAvailableAsSecondary(
+  GetAllAvailableAccounts(
       account_profile_mapper, profile_path,
       base::BindOnce(&SigninHelperLacros::OnAccountsAvailableAsSecondaryFetched,
                      weak_factory_.GetWeakPtr()));
diff --git a/chrome/browser/metrics/chrome_metrics_service_accessor.h b/chrome/browser/metrics/chrome_metrics_service_accessor.h
index b124e8b..d9a2253 100644
--- a/chrome/browser/metrics/chrome_metrics_service_accessor.h
+++ b/chrome/browser/metrics/chrome_metrics_service_accessor.h
@@ -181,6 +181,7 @@
   friend class metrics::UkmConsentParamBrowserTest;
   friend class ClonedInstallClientIdResetBrowserTest;
   friend class metrics::ChromeOSPerUserMetricsBrowserTestBase;
+  friend class SampledOutClientIdSavedBrowserTest;
   FRIEND_TEST_ALL_PREFIXES(ChromeMetricsServiceAccessorTest,
                            MetricsReportingEnabled);
   FRIEND_TEST_ALL_PREFIXES(ChromeMetricsServicesManagerClientTest,
diff --git a/chrome/browser/metrics/chrome_metrics_services_manager_client.cc b/chrome/browser/metrics/chrome_metrics_services_manager_client.cc
index 4ce9128..b75a50d 100644
--- a/chrome/browser/metrics/chrome_metrics_services_manager_client.cc
+++ b/chrome/browser/metrics/chrome_metrics_services_manager_client.cc
@@ -29,6 +29,7 @@
 #include "chrome/common/chrome_switches.h"
 #include "chrome/installer/util/google_update_settings.h"
 #include "components/metrics/enabled_state_provider.h"
+#include "components/metrics/metrics_pref_names.h"
 #include "components/metrics/metrics_state_manager.h"
 #include "components/prefs/pref_service.h"
 #include "components/variations/service/variations_service.h"
@@ -76,14 +77,24 @@
 const base::Feature kMetricsReportingFeature{"MetricsReporting",
                                              base::FEATURE_ENABLED_BY_DEFAULT};
 
+#if BUILDFLAG(IS_ANDROID)
+// Same as |kMetricsReportingFeature|, but this feature is associated with a
+// different trial, which has different sampling rates. This is due to a bug
+// in which the old sampling rate was not being applied correctly. In order for
+// the fix to not affect the overall sampling rate, this new feature was
+// created. See crbug/1306481.
+const base::Feature kPostFREFixMetricsReportingFeature{
+    "PostFREFixMetricsReporting", base::FEATURE_ENABLED_BY_DEFAULT};
+#endif  // BUILDFLAG(IS_ANDROID)
+
+// Name of the variations param that defines the sampling rate.
+const char kRateParamName[] = "sampling_rate_per_mille";
+
 }  // namespace internal
 }  // namespace metrics
 
 namespace {
 
-// Name of the variations param that defines the sampling rate.
-const char kRateParamName[] = "sampling_rate_per_mille";
-
 // Posts |GoogleUpdateSettings::StoreMetricsClientInfo| on blocking pool thread
 // because it needs access to IO and cannot work from UI thread.
 void PostStoreMetricsClientInfo(const metrics::ClientInfo& client_info) {
@@ -99,15 +110,42 @@
                               int rate,
                               base::FieldTrial* trial) {
   std::map<std::string, std::string> params = {
-      {kRateParamName, base::NumberToString(rate)}};
+      {metrics::internal::kRateParamName, base::NumberToString(rate)}};
   variations::AssociateVariationParams(trial->trial_name(), group_name, params);
   trial->AppendGroup(group_name, rate);
 }
 
+#if BUILDFLAG(IS_ANDROID)
+// Returns true if we should use the new sampling trial and feature to determine
+// sampling. See the comment on |kUsePostFREFixSamplingTrial| for more details.
+bool ShouldUsePostFREFixSamplingTrial(PrefService* local_state) {
+  return local_state->GetBoolean(metrics::prefs::kUsePostFREFixSamplingTrial);
+}
+
+bool ShouldUsePostFREFixSamplingTrial() {
+  // We check for g_browser_process and local_state() because some unit tests
+  // may reach this point without creating a test browser process and/or local
+  // state.
+  // TODO(crbug/1321823): Fix the unit tests so that we do not need to check for
+  // g_browser_process and local_state().
+  return g_browser_process && g_browser_process->local_state() &&
+         ShouldUsePostFREFixSamplingTrial(g_browser_process->local_state());
+}
+#endif  // BUILDFLAG(IS_ANDROID)
+
 // Implementation of IsClientInSample() that takes a PrefService param.
 bool IsClientInSampleImpl(PrefService* local_state) {
-  // Test the MetricsReporting feature for all users to ensure that the trial
-  // is reported.
+  // Test the MetricsReporting or PostFREFixMetricsReporting feature (depending
+  // on the |kUsePostFREFixSamplingTrial| pref and platform) for all users to
+  // ensure that the trial is reported. See the comment on
+  // |kUsePostFREFixSamplingTrial| for more details on why there are two
+  // different features.
+#if BUILDFLAG(IS_ANDROID)
+  if (ShouldUsePostFREFixSamplingTrial(local_state)) {
+    return base::FeatureList::IsEnabled(
+        metrics::internal::kPostFREFixMetricsReportingFeature);
+  }
+#endif  // BUILDFLAG(IS_ANDROID)
   return base::FeatureList::IsEnabled(
       metrics::internal::kMetricsReportingFeature);
 }
@@ -192,10 +230,24 @@
   // The trial name must be kept in sync with the server config controlling
   // sampling. If they don't match, then clients will be shuffled into different
   // groups when the server config takes over from the fallback trial.
-  static const char kTrialName[] = "MetricsAndCrashSampling";
+  std::string trial_name = "MetricsAndCrashSampling";
+  // The name of the feature used to control sampling.
+  std::string feature_name = metrics::internal::kMetricsReportingFeature.name;
+
+  bool use_post_fre_fix_sampling_trial = false;
+#if BUILDFLAG(IS_ANDROID)
+  // Depending on the |kUsePostFREFixSamplingTrial| pref, we may apply a
+  // different sampling trial and rate.
+  if (ShouldUsePostFREFixSamplingTrial()) {
+    use_post_fre_fix_sampling_trial = true;
+    trial_name = "PostFREFixMetricsAndCrashSampling";
+    feature_name = metrics::internal::kPostFREFixMetricsReportingFeature.name;
+  }
+#endif  // BUILDFLAG(IS_ANDROID)
+
   scoped_refptr<base::FieldTrial> trial(
       base::FieldTrialList::FactoryGetFieldTrial(
-          kTrialName, 1000, "Default", base::FieldTrial::ONE_TIME_RANDOMIZED,
+          trial_name, 1000, "Default", base::FieldTrial::ONE_TIME_RANDOMIZED,
           nullptr));
 
   // On all channels except stable, we sample out at a minimal rate to ensure
@@ -203,26 +255,32 @@
   int sampled_in_rate = 990;
   int sampled_out_rate = 10;
   if (channel == version_info::Channel::STABLE) {
-    sampled_in_rate = 100;
-    sampled_out_rate = 900;
+    if (use_post_fre_fix_sampling_trial) {
+      // See crbug/1306481 for details on why the new sampling rate is 19%.
+      sampled_in_rate = 190;
+      sampled_out_rate = 810;
+    } else {
+      sampled_in_rate = 100;
+      sampled_out_rate = 900;
+    }
   }
 
   // Like the trial name, the order that these two groups are added to the trial
   // must be kept in sync with the order that they appear in the server config.
-  // For future sanity purposes, the desired order is:
-  // OutOfReportingSample, InReportingSample
+  // The desired order is: OutOfReportingSample, InReportingSample.
 
-  static const char kSampledOutGroup[] = "OutOfReportingSample";
+  const char kSampledOutGroup[] = "OutOfReportingSample";
   AppendSamplingTrialGroup(kSampledOutGroup, sampled_out_rate, trial.get());
 
-  static const char kInSampleGroup[] = "InReportingSample";
+  const char kInSampleGroup[] = "InReportingSample";
   AppendSamplingTrialGroup(kInSampleGroup, sampled_in_rate, trial.get());
 
-  // Setup the feature. This must be done after all groups are added since
+  // Set up the feature. This must be done after all groups are added since
   // GetGroupNameWithoutActivation() will finalize the group choice.
   const std::string& group_name = trial->GetGroupNameWithoutActivation();
+
   feature_list->RegisterFieldTrialOverride(
-      metrics::internal::kMetricsReportingFeature.name,
+      feature_name,
       group_name == kSampledOutGroup
           ? base::FeatureList::OVERRIDE_DISABLE_FEATURE
           : base::FeatureList::OVERRIDE_ENABLE_FEATURE,
@@ -236,8 +294,16 @@
 
 // static
 bool ChromeMetricsServicesManagerClient::GetSamplingRatePerMille(int* rate) {
+#if BUILDFLAG(IS_ANDROID)
+  const base::Feature& feature =
+      ShouldUsePostFREFixSamplingTrial()
+          ? metrics::internal::kPostFREFixMetricsReportingFeature
+          : metrics::internal::kMetricsReportingFeature;
+#else
+  const base::Feature& feature = metrics::internal::kMetricsReportingFeature;
+#endif  // BUILDFLAG(IS_ANDROID)
   std::string rate_str = variations::GetVariationParamValueByFeature(
-      metrics::internal::kMetricsReportingFeature, kRateParamName);
+      feature, metrics::internal::kRateParamName);
   if (rate_str.empty())
     return false;
 
diff --git a/chrome/browser/metrics/chrome_metrics_services_manager_client.h b/chrome/browser/metrics/chrome_metrics_services_manager_client.h
index 5f3ccf3..f16b42e8 100644
--- a/chrome/browser/metrics/chrome_metrics_services_manager_client.h
+++ b/chrome/browser/metrics/chrome_metrics_services_manager_client.h
@@ -28,8 +28,12 @@
 namespace internal {
 // TODO(crbug.com/1068796): Replace kMetricsReportingFeature with a better name.
 extern const base::Feature kMetricsReportingFeature;
-}
-}
+#if BUILDFLAG(IS_ANDROID)
+extern const base::Feature kPostFREFixMetricsReportingFeature;
+#endif  // BUILDFLAG(IS_ANDROID)
+extern const char kRateParamName[];
+}  // namespace internal
+}  // namespace metrics
 
 namespace version_info {
 enum class Channel;
@@ -56,7 +60,10 @@
   // have first-run variations support. This should only be called when there is
   // no existing field trial controlling the sampling feature, and on the
   // correct platform. |channel| will affect the sampling rates that are
-  // applied. Stable will be sampled at 10%, other channels at 99%.
+  // applied. Stable will be sampled at 10%, other channels at 99%. On Android
+  // Chrome, Stable will be sampled at 19% if the |kUsePostFREFixSamplingTrial|
+  // pref is set to true. See the comment on |kUsePostFREFixSamplingTrial| for
+  // more details.
   static void CreateFallbackSamplingTrial(version_info::Channel channel,
                                           base::FeatureList* feature_list);
 
diff --git a/chrome/browser/metrics/chrome_metrics_services_manager_client_unittest.cc b/chrome/browser/metrics/chrome_metrics_services_manager_client_unittest.cc
index d2b5d590..1f92909f 100644
--- a/chrome/browser/metrics/chrome_metrics_services_manager_client_unittest.cc
+++ b/chrome/browser/metrics/chrome_metrics_services_manager_client_unittest.cc
@@ -6,8 +6,12 @@
 
 #include "base/base_switches.h"
 #include "base/command_line.h"
+#include "base/containers/contains.h"
+#include "base/metrics/field_trial.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
 #include "chrome/browser/metrics/chrome_metrics_service_client.h"
+#include "chrome/test/base/testing_browser_process.h"
 #include "components/metrics/enabled_state_provider.h"
 #include "components/metrics/metrics_pref_names.h"
 #include "components/metrics/metrics_reporting_default_state.h"
@@ -15,6 +19,7 @@
 #include "components/metrics/metrics_state_manager.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/testing_pref_service.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
@@ -22,19 +27,47 @@
 #include "content/public/test/browser_task_environment.h"
 #endif
 
-TEST(ChromeMetricsServicesManagerClientTest, ForceTrialsDisablesReporting) {
-  TestingPrefServiceSimple local_state;
+using ::testing::NotNull;
 
-  metrics::RegisterMetricsReportingStatePrefs(local_state.registry());
+class ChromeMetricsServicesManagerClientTest : public testing::Test {
+ public:
+  ChromeMetricsServicesManagerClientTest() = default;
 
+  ChromeMetricsServicesManagerClientTest(
+      const ChromeMetricsServicesManagerClientTest&) = delete;
+  ChromeMetricsServicesManagerClientTest& operator=(
+      const ChromeMetricsServicesManagerClientTest&) = delete;
+
+  ~ChromeMetricsServicesManagerClientTest() override = default;
+
+  void SetUp() override {
+    // Set up Local State prefs.
+    TestingBrowserProcess::GetGlobal()->SetLocalState(&local_state_);
+    ChromeMetricsServiceClient::RegisterPrefs(local_state()->registry());
+  }
+
+  void TearDown() override {
+    TestingBrowserProcess::GetGlobal()->SetLocalState(nullptr);
+  }
+
+  TestingPrefServiceSimple* local_state() { return &local_state_; }
+
+ private:
+  TestingPrefServiceSimple local_state_;
+};
+
+using CreateFallbackSamplingTrialTest = ChromeMetricsServicesManagerClientTest;
+using IsClientInSampleTest = ChromeMetricsServicesManagerClientTest;
+
+TEST_F(ChromeMetricsServicesManagerClientTest, ForceTrialsDisablesReporting) {
   // First, test with UMA reporting setting defaulting to off.
-  local_state.registry()->RegisterBooleanPref(
+  local_state()->registry()->RegisterBooleanPref(
       metrics::prefs::kMetricsReportingEnabled, false);
   // Force the pref to be used, even in unofficial builds.
   ChromeMetricsServiceAccessor::SetForceIsMetricsReportingEnabledPrefLookup(
       true);
 
-  ChromeMetricsServicesManagerClient client(&local_state);
+  ChromeMetricsServicesManagerClient client(local_state());
   const metrics::EnabledStateProvider& provider =
       client.GetEnabledStateProviderForTesting();
   metrics_services_manager::MetricsServicesManagerClient* base_client = &client;
@@ -49,7 +82,7 @@
   EXPECT_FALSE(provider.IsReportingEnabled());
 
   // Set the pref to true.
-  local_state.SetBoolean(metrics::prefs::kMetricsReportingEnabled, true);
+  local_state()->SetBoolean(metrics::prefs::kMetricsReportingEnabled, true);
 
   // The provider and client APIs should agree.
   EXPECT_EQ(provider.IsConsentGiven(), base_client->IsMetricsConsentGiven());
@@ -75,20 +108,18 @@
   EXPECT_FALSE(provider.IsReportingEnabled());
 }
 
-TEST(ChromeMetricsServicesManagerClientTest, PopulateStartupVisibility) {
+TEST_F(ChromeMetricsServicesManagerClientTest, PopulateStartupVisibility) {
 #if BUILDFLAG(IS_CHROMEOS_LACROS)
   // Set up ScopedLacrosServiceTestHelper needed for Lacros.
   content::BrowserTaskEnvironment task_environment;
   chromeos::ScopedLacrosServiceTestHelper helper;
 #endif
 
-  // Set up Local State prefs.
-  TestingPrefServiceSimple local_state;
-  ChromeMetricsServiceClient::RegisterPrefs(local_state.registry());
-  local_state.registry()->RegisterBooleanPref(
+  // Register the kMetricsReportingEnabled pref.
+  local_state()->registry()->RegisterBooleanPref(
       metrics::prefs::kMetricsReportingEnabled, false);
 
-  ChromeMetricsServicesManagerClient client(&local_state);
+  ChromeMetricsServicesManagerClient client(local_state());
   metrics::MetricsStateManager* metrics_state_manager =
       client.GetMetricsStateManagerForTesting();
 
@@ -96,3 +127,177 @@
   EXPECT_TRUE(metrics_state_manager->is_foreground_session() ||
               metrics_state_manager->is_background_session());
 }
+
+// Verifies that CreateFallBackSamplingTrial() uses the MetricsAndCrashSampling
+// sampling trial if the |kUsePostFREFixSamplingTrial| pref is not set. This is
+// the case if 1) this is a non-Android platform, or 2) this is an Android
+// client that is not using the new sampling trial. This also verifies that the
+// param |kRateParamName| is correctly set, depending on which group we are
+// assigned to during the test (not deterministic).
+TEST_F(CreateFallbackSamplingTrialTest, UsesMetricsAndCrashSamplingTrial) {
+#if BUILDFLAG(IS_ANDROID)
+  ASSERT_FALSE(
+      local_state()->GetBoolean(metrics::prefs::kUsePostFREFixSamplingTrial));
+#endif
+
+  // Initially, neither sampling trial should exist.
+  ASSERT_FALSE(base::FieldTrialList::TrialExists("MetricsAndCrashSampling"));
+  ASSERT_FALSE(
+      base::FieldTrialList::TrialExists("PostFREFixMetricsAndCrashSampling"));
+
+  // Create the fallback sampling trial.
+  auto feature_list = std::make_unique<base::FeatureList>();
+  ChromeMetricsServicesManagerClient::CreateFallbackSamplingTrial(
+      version_info::Channel::STABLE, feature_list.get());
+
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatureList(std::move(feature_list));
+
+  // Since the |kUsePostFREFixSamplingTrial| pref was not set to true, the
+  // MetricsAndCrashSampling sampling trial should be registered.
+  EXPECT_TRUE(base::FieldTrialList::TrialExists("MetricsAndCrashSampling"));
+  EXPECT_FALSE(
+      base::FieldTrialList::TrialExists("PostFREFixMetricsAndCrashSampling"));
+
+  // The MetricsReporting feature should be associated with the
+  // MetricsAndCrashSampling trial, and its state should be overridden.
+  base::FieldTrial* associated_trial = base::FeatureList::GetFieldTrial(
+      metrics::internal::kMetricsReportingFeature);
+  EXPECT_THAT(associated_trial, NotNull());
+  EXPECT_EQ("MetricsAndCrashSampling", associated_trial->trial_name());
+  EXPECT_TRUE(base::FeatureList::GetStateIfOverridden(
+                  metrics::internal::kMetricsReportingFeature)
+                  .has_value());
+
+  // Verify that we are either in the "OutOfReportingSample" or
+  // "InReportingSample" group, and verify that the sampling rate is what we
+  // expect (10% sampled in rate or 90% sampled out rate for Stable).
+  // TODO(crbug/1322904): Maybe make
+  // ChromeMetricsServicesManagerClient::GetSamplingRatePerMille return the
+  // sampling rate even when sampled out, so that we can replace the code below
+  // with ChromeMetricsServicesManagerClient::GetSamplingRatePerMille.
+  const std::string group_name = associated_trial->group_name();
+  ASSERT_TRUE(group_name == "OutOfReportingSample" ||
+              group_name == "InReportingSample");
+  base::FieldTrialParams params;
+  ASSERT_TRUE(
+      base::GetFieldTrialParams(associated_trial->trial_name(), &params));
+  ASSERT_TRUE(base::Contains(params, metrics::internal::kRateParamName));
+  int sampling_rate_per_mille;
+  ASSERT_TRUE(base::StringToInt(params[metrics::internal::kRateParamName],
+                                &sampling_rate_per_mille));
+  EXPECT_EQ(group_name == "OutOfReportingSample" ? 900 : 100,
+            sampling_rate_per_mille);
+}
+
+#if BUILDFLAG(IS_ANDROID)
+// Verifies that CreateFallBackSamplingTrial() uses the post-FRE-fix sampling
+// trial (PostFREFixMetricsAndCrashSampling) if the
+// |kUsePostFREFixSamplingTrial| pref is set. This also verifies that the param
+// |kRateParamName| is correctly set, depending on which group we are assigned
+// to during the test (not deterministic).
+TEST_F(CreateFallbackSamplingTrialTest, UsesPostFREFixTrialWhenPrefSet) {
+  // Set the |kUsePostFREFixSamplingTrial| pref to true.
+  local_state()->SetBoolean(metrics::prefs::kUsePostFREFixSamplingTrial, true);
+
+  // Initially, neither sampling trial should exist.
+  ASSERT_FALSE(base::FieldTrialList::TrialExists("MetricsAndCrashSampling"));
+  ASSERT_FALSE(
+      base::FieldTrialList::TrialExists("PostFREFixMetricsAndCrashSampling"));
+
+  // Create the fallback sampling trial.
+  auto feature_list = std::make_unique<base::FeatureList>();
+  ChromeMetricsServicesManagerClient::CreateFallbackSamplingTrial(
+      version_info::Channel::STABLE, feature_list.get());
+
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitWithFeatureList(std::move(feature_list));
+
+  // Since the |kUsePostFREFixSamplingTrial| pref was set to true, the
+  // post-FRE-fix sampling trial (PostFREFixMetricsAndCrashSampling) should be
+  // registered.
+  EXPECT_FALSE(base::FieldTrialList::TrialExists("MetricsAndCrashSampling"));
+  EXPECT_TRUE(
+      base::FieldTrialList::TrialExists("PostFREFixMetricsAndCrashSampling"));
+
+  // The PostFREFixMetricsReporting feature should be associated with the
+  // PostFREFixMetricsAndCrashSampling trial, and its state should be
+  // overridden.
+  base::FieldTrial* associated_trial = base::FeatureList::GetFieldTrial(
+      metrics::internal::kPostFREFixMetricsReportingFeature);
+  EXPECT_THAT(associated_trial, NotNull());
+  EXPECT_EQ("PostFREFixMetricsAndCrashSampling",
+            associated_trial->trial_name());
+  EXPECT_TRUE(base::FeatureList::GetStateIfOverridden(
+                  metrics::internal::kPostFREFixMetricsReportingFeature)
+                  .has_value());
+
+  // Verify that we are either in the "OutOfReportingSample" or
+  // "InReportingSample" group, and verify that the sampling rate is what we
+  // expect (19% sampled in rate or 81% sampled out rate for Stable).
+  // TODO(crbug/1322904): Maybe make
+  // ChromeMetricsServicesManagerClient::GetSamplingRatePerMille return the
+  // sampling rate even when sampled out, so that we can replace the code below
+  // with ChromeMetricsServicesManagerClient::GetSamplingRatePerMille.
+  const std::string group_name = associated_trial->group_name();
+  ASSERT_TRUE(group_name == "OutOfReportingSample" ||
+              group_name == "InReportingSample");
+  base::FieldTrialParams params;
+  ASSERT_TRUE(
+      base::GetFieldTrialParams(associated_trial->trial_name(), &params));
+  ASSERT_TRUE(base::Contains(params, metrics::internal::kRateParamName));
+  int sampling_rate_per_mille;
+  ASSERT_TRUE(base::StringToInt(params[metrics::internal::kRateParamName],
+                                &sampling_rate_per_mille));
+  EXPECT_EQ(group_name == "OutOfReportingSample" ? 810 : 190,
+            sampling_rate_per_mille);
+}
+#endif  // BUILDFLAG(IS_ANDROID)
+
+// Verifies that IsClientInSample() uses the "MetricsReporting" sampling
+// feature to determine sampling if the |kUsePostFREFixSamplingTrial| pref is
+// not set. This is the case if 1) this is a non-Android platform, or 2) this is
+// an Android client that is not using the new sampling trial.
+TEST_F(IsClientInSampleTest, UsesMetricsReportingFeature) {
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitAndDisableFeature(
+        metrics::internal::kMetricsReportingFeature);
+    // The client should not be considered sampled in.
+    EXPECT_FALSE(ChromeMetricsServicesManagerClient::IsClientInSample());
+  }
+
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitAndEnableFeature(
+        metrics::internal::kMetricsReportingFeature);
+    // The client should be considered sampled in.
+    EXPECT_TRUE(ChromeMetricsServicesManagerClient::IsClientInSample());
+  }
+}
+
+#if BUILDFLAG(IS_ANDROID)
+// Verifies that IsClientInSample() uses the post-FRE-fix sampling
+// feature to determine sampling if the |kUsePostFREFixSamplingTrial| pref is
+// set.
+TEST_F(IsClientInSampleTest, UsesPostFREFixFeatureWhenPrefSet) {
+  // Set the |kUsePostFREFixSamplingTrial| pref to true.
+  local_state()->SetBoolean(metrics::prefs::kUsePostFREFixSamplingTrial, true);
+
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitAndDisableFeature(
+        metrics::internal::kPostFREFixMetricsReportingFeature);
+    // The client should not be considered sampled in.
+    EXPECT_FALSE(ChromeMetricsServicesManagerClient::IsClientInSample());
+  }
+
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitAndEnableFeature(
+        metrics::internal::kPostFREFixMetricsReportingFeature);
+    // The client should be considered sampled in.
+    EXPECT_TRUE(ChromeMetricsServicesManagerClient::IsClientInSample());
+  }
+}
+#endif  // BUILDFLAG(IS_ANDROID)
diff --git a/chrome/browser/metrics/metrics_reporting_state.cc b/chrome/browser/metrics/metrics_reporting_state.cc
index 38dcfad3..ee1cadad 100644
--- a/chrome/browser/metrics/metrics_reporting_state.cc
+++ b/chrome/browser/metrics/metrics_reporting_state.cc
@@ -150,6 +150,22 @@
     }
     return;
   }
+#if BUILDFLAG(IS_ANDROID)
+  // When a user disables metrics reporting on Android Chrome, the new
+  // sampling trial should be used to determine whether the client is sampled
+  // in or out (if the user ever re-enables metrics reporting).
+  //
+  // Existing metrics-reporting-enabled clients (i.e. the users without this
+  // pref set) do not use the new sampling trial; they continue to use
+  // MetricsAndCrashSampling. However, if such a user disables metrics
+  // reporting and later re-enables it, they will start using the new trial.
+  //
+  // See crbug/1306481 and the comment above |kUsePostFREFixSamplingTrial| in
+  // components/metrics/metrics_pref_names.cc for more details.
+  g_browser_process->local_state()->SetBoolean(
+      metrics::prefs::kUsePostFREFixSamplingTrial, true);
+#endif  // BUILDFLAG(IS_ANDROID)
+
   // Clear the client id and low entropy sources pref when opting out.
   // Note: This will not affect the running state (e.g. field trial
   // randomization), as the pref is only read on startup.
diff --git a/chrome/browser/metrics/sampled_out_client_id_saved_browsertest.cc b/chrome/browser/metrics/sampled_out_client_id_saved_browsertest.cc
new file mode 100644
index 0000000..c84079e9
--- /dev/null
+++ b/chrome/browser/metrics/sampled_out_client_id_saved_browsertest.cc
@@ -0,0 +1,150 @@
+// Copyright 2022 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/run_loop.h"
+#include "base/test/scoped_feature_list.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
+#include "chrome/browser/metrics/chrome_metrics_services_manager_client.h"
+#include "chrome/browser/metrics/metrics_reporting_state.h"
+#include "components/metrics/metrics_pref_names.h"
+#include "components/metrics/metrics_service.h"
+#include "components/metrics_services_manager/metrics_services_manager.h"
+#include "content/public/test/browser_test.h"
+
+#if BUILDFLAG(IS_ANDROID)
+#include "chrome/test/base/android/android_browser_test.h"
+#else
+#include "chrome/test/base/in_process_browser_test.h"
+#endif  // BUILDFLAG(IS_ANDROID)
+
+namespace {
+
+// Callback from changing whether reporting is enabled.
+void OnMetricsReportingStateChanged(bool* new_state_ptr,
+                                    base::OnceClosure run_loop_closure,
+                                    bool new_state) {
+  *new_state_ptr = new_state;
+  std::move(run_loop_closure).Run();
+}
+
+// Changes the metrics reporting state to |enabled|. Returns the actual state
+// metrics reporting was changed to after completion.
+bool ChangeMetricsReporting(bool enabled) {
+  bool value_after_change;
+  base::RunLoop run_loop;
+  ChangeMetricsReportingStateWithReply(
+      enabled, base::BindOnce(OnMetricsReportingStateChanged,
+                              &value_after_change, run_loop.QuitClosure()));
+  run_loop.Run();
+  return value_after_change;
+}
+
+}  // namespace
+
+class SampledOutClientIdSavedBrowserTest : public PlatformBrowserTest {
+ public:
+  SampledOutClientIdSavedBrowserTest() = default;
+
+  SampledOutClientIdSavedBrowserTest(
+      const SampledOutClientIdSavedBrowserTest&) = delete;
+  SampledOutClientIdSavedBrowserTest& operator=(
+      const SampledOutClientIdSavedBrowserTest&) = delete;
+
+  ~SampledOutClientIdSavedBrowserTest() override = default;
+
+  void SetUp() override {
+    // Because metrics reporting is disabled in non-Chrome-branded builds,
+    // IsMetricsReportingEnabled() always returns false. Enable it here for
+    // test consistency between Chromium and Chrome builds, otherwise
+    // ChangeMetricsReportingStateWithReply() will not have the intended effects
+    // for non-Chrome-branded builds.
+    ChromeMetricsServiceAccessor::SetForceIsMetricsReportingEnabledPrefLookup(
+        true);
+
+    // Disable |kMetricsReportingFeature| to simulate being sampled out. For
+    // Android Chrome, we instead disable |kPostFREFixMetricsReportingFeature|
+    // since that is the feature used to verify sampling for clients that newly
+    // enable metrics reporting.
+#if BUILDFLAG(IS_ANDROID)
+    feature_list_.InitAndDisableFeature(
+        metrics::internal::kPostFREFixMetricsReportingFeature);
+#else
+    feature_list_.InitAndDisableFeature(
+        metrics::internal::kMetricsReportingFeature);
+#endif  // BUILDFLAG(IS_ANDROID)
+
+    PlatformBrowserTest::SetUp();
+  }
+
+  metrics::MetricsService* metrics_service() {
+    return g_browser_process->GetMetricsServicesManager()->GetMetricsService();
+  }
+
+  PrefService* local_state() { return g_browser_process->local_state(); }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Verifies that a client ID is written to Local State if metrics reporting is
+// turned on—even when the user is sampled out. For Android Chrome, this also
+// verifies that clients that have ever went through any of these situations
+// should use the post-FRE-fix sampling trial/feature to determine sampling:
+// 1) On start up, we determined that they had not consented to metrics
+//    reporting (including first run users), or,
+// 2) They disabled metrics reporting.
+IN_PROC_BROWSER_TEST_F(SampledOutClientIdSavedBrowserTest, ClientIdSaved) {
+  // Verify that the client ID is initially empty.
+  ASSERT_TRUE(metrics_service()->GetClientId().empty());
+  ASSERT_TRUE(
+      local_state()->GetString(metrics::prefs::kMetricsClientID).empty());
+
+#if BUILDFLAG(IS_ANDROID)
+  // On Android Chrome, since we have not yet consented to metrics reporting,
+  // the new sampling trial should be used to verify sampling.
+  EXPECT_TRUE(
+      local_state()->GetBoolean(metrics::prefs::kUsePostFREFixSamplingTrial));
+#endif  // BUILDFLAG(IS_ANDROID)
+
+  // Verify that we are considered sampled out.
+  EXPECT_FALSE(ChromeMetricsServicesManagerClient::IsClientInSample());
+
+  // Enable metrics reporting, and verify that it was successful.
+  ASSERT_TRUE(ChangeMetricsReporting(true));
+  ASSERT_TRUE(
+      local_state()->GetBoolean(metrics::prefs::kMetricsReportingEnabled));
+
+  // Verify that we are still considered sampled out.
+  EXPECT_FALSE(ChromeMetricsServicesManagerClient::IsClientInSample());
+
+  // Verify that we are neither recording nor uploading metrics. This also
+  // verifies that we are sampled out according to the metrics code, since
+  // recording and reporting will not be enabled only if we were sampled out.
+  EXPECT_FALSE(metrics_service()->recording_active());
+  EXPECT_FALSE(metrics_service()->reporting_active());
+
+  // Verify that the client ID is set and in the Local State.
+  std::string client_id = metrics_service()->GetClientId();
+  EXPECT_FALSE(client_id.empty());
+  EXPECT_EQ(client_id,
+            local_state()->GetString(metrics::prefs::kMetricsClientID));
+
+#if BUILDFLAG(IS_ANDROID)
+  // Set the pref that dictates whether the new sampling trial should be used to
+  // false so that we can verify that upon disabling metrics reporting, this
+  // pref is again set to true.
+  local_state()->SetBoolean(metrics::prefs::kUsePostFREFixSamplingTrial, false);
+
+  // Disable metrics reporting, and verify that it was successful.
+  ASSERT_FALSE(ChangeMetricsReporting(false));
+  ASSERT_FALSE(
+      local_state()->GetBoolean(metrics::prefs::kMetricsReportingEnabled));
+
+  // Verify that the pref dictating whether we use new sampling trial should be
+  // used is set to true.
+  EXPECT_TRUE(
+      local_state()->GetBoolean(metrics::prefs::kUsePostFREFixSamplingTrial));
+#endif  // BUILDFLAG(IS_ANDROID)
+}
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
index eddea976..79346fb 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
+++ b/chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc
@@ -4000,6 +4000,12 @@
         return false;
       }
 
+      if (credentials_proto.has_hidden_ssid() &&
+          credentials_proto.hidden_ssid()) {
+        NS_LOG(WARNING) << __func__ << ": Network is hidden";
+        return false;
+      }
+
       std::string wifi_password(credentials_proto.password());
       wifi_credentials.set_wifi_password(wifi_password);
 
diff --git a/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc b/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc
index 9af7b2f..2dbf133 100644
--- a/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc
+++ b/chrome/browser/nearby_sharing/nearby_sharing_service_impl_unittest.cc
@@ -3371,6 +3371,21 @@
        AcceptValidShareTarget_WifiProtoPasswordEmpty) {
   sharing::nearby::WifiCredentials credentials_proto;
   credentials_proto.set_password("");
+  credentials_proto.set_hidden_ssid(false);
+  const std::string& proto_string = credentials_proto.SerializeAsString();
+
+  ReceiveBadWifiPayload(location::nearby::connections::mojom::Payload::New(
+      kWifiCredentialsPayloadId,
+      location::nearby::connections::mojom::PayloadContent::NewBytes(
+          location::nearby::connections::mojom::BytesPayload::New(
+              std::vector<uint8_t>(proto_string.begin(),
+                                   proto_string.end())))));
+}
+
+TEST_P(NearbySharingServiceImplTest, AcceptValidShareTarget_HiddenNetwork) {
+  sharing::nearby::WifiCredentials credentials_proto;
+  credentials_proto.set_password(kWifiPassword);
+  credentials_proto.set_hidden_ssid(true);
   const std::string& proto_string = credentials_proto.SerializeAsString();
 
   ReceiveBadWifiPayload(location::nearby::connections::mojom::Payload::New(
diff --git a/chrome/browser/nearby_sharing/wifi_network_configuration/wifi_network_configuration_handler.cc b/chrome/browser/nearby_sharing/wifi_network_configuration/wifi_network_configuration_handler.cc
index 7baa528..3da9e81 100644
--- a/chrome/browser/nearby_sharing/wifi_network_configuration/wifi_network_configuration_handler.cc
+++ b/chrome/browser/nearby_sharing/wifi_network_configuration/wifi_network_configuration_handler.cc
@@ -44,7 +44,7 @@
       wifi_credentials_attachment.security_type());
   wifi->ssid = wifi_credentials_attachment.ssid();
   wifi->hidden_ssid =
-      chromeos::network_config::mojom::HiddenSsidMode::kAutomatic;
+      chromeos::network_config::mojom::HiddenSsidMode::kDisabled;
 
   auto config = chromeos::network_config::mojom::ConfigProperties::New();
   config->type_config =
diff --git a/chrome/browser/nearby_sharing/wifi_network_configuration/wifi_network_configuration_handler_unittest.cc b/chrome/browser/nearby_sharing/wifi_network_configuration/wifi_network_configuration_handler_unittest.cc
index c328ef02..628aa11 100644
--- a/chrome/browser/nearby_sharing/wifi_network_configuration/wifi_network_configuration_handler_unittest.cc
+++ b/chrome/browser/nearby_sharing/wifi_network_configuration/wifi_network_configuration_handler_unittest.cc
@@ -109,6 +109,10 @@
   EXPECT_EQ(kTestSsid, fake_cros_network_config.last_properties()
                            ->type_config->get_wifi()
                            ->ssid);
+  EXPECT_EQ(chromeos::network_config::mojom::HiddenSsidMode::kDisabled,
+            fake_cros_network_config.last_properties()
+                ->type_config->get_wifi()
+                ->hidden_ssid);
   EXPECT_EQ(1u, fake_cros_network_config.num_configure_network_calls());
 }
 
diff --git a/chrome/browser/optimization_guide/page_content_annotations_service_browsertest.cc b/chrome/browser/optimization_guide/page_content_annotations_service_browsertest.cc
index 0475c22..aaab618 100644
--- a/chrome/browser/optimization_guide/page_content_annotations_service_browsertest.cc
+++ b/chrome/browser/optimization_guide/page_content_annotations_service_browsertest.cc
@@ -128,7 +128,8 @@
  public:
   PageContentAnnotationsServiceValidationBrowserTest() {
     scoped_feature_list_.InitWithFeatures(
-        {features::kOptimizationHints, features::kBatchAnnotationsValidation},
+        {features::kOptimizationHints,
+         features::kPageContentAnnotationsValidation},
         {features::kPageContentAnnotations});
   }
 
@@ -907,34 +908,6 @@
       "OptimizationGuide.ModelExecutor.ExecutionStatus.PageVisibility", 2);
 }
 
-class PageContentAnnotationsServiceValidationTest
-    : public PageContentAnnotationsServiceBrowserTest {
- public:
-  PageContentAnnotationsServiceValidationTest() {
-    scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        features::kBatchAnnotationsValidation, {
-                                                   {"startup_delay", "5"},
-                                                   {"batch_size", "10"},
-                                               });
-  }
-  ~PageContentAnnotationsServiceValidationTest() override = default;
-
- private:
-  base::test::ScopedFeatureList scoped_feature_list_;
-};
-
-IN_PROC_BROWSER_TEST_F(PageContentAnnotationsServiceValidationTest,
-                       StartsValidation) {
-  base::HistogramTester histogram_tester;
-
-  RetryForHistogramUntilCountReached(
-      &histogram_tester,
-      "OptimizationGuide.PageContentAnnotationsService.ValidationRun", 1);
-
-  histogram_tester.ExpectUniqueSample(
-      "OptimizationGuide.PageContentAnnotationsService.ValidationRun", 10, 1);
-}
-
 #endif  // BUILDFLAG(BUILD_WITH_TFLITE_LIB)
 
 }  // namespace optimization_guide
diff --git a/chrome/browser/optimization_guide/page_content_annotations_service_factory.cc b/chrome/browser/optimization_guide/page_content_annotations_service_factory.cc
index 694d357d..e95eff3 100644
--- a/chrome/browser/optimization_guide/page_content_annotations_service_factory.cc
+++ b/chrome/browser/optimization_guide/page_content_annotations_service_factory.cc
@@ -27,12 +27,10 @@
 bool ShouldEnablePageContentAnnotations() {
   // Allow for the validation experiment and/or the Topics experiment to enable
   // the PCAService without need to enable both features.
-  if (!optimization_guide::features::IsPageContentAnnotationEnabled() &&
-      !optimization_guide::features::BatchAnnotationsValidationEnabled() &&
-      !base::FeatureList::IsEnabled(blink::features::kBrowsingTopics)) {
-    return false;
-  }
-  return true;
+  return optimization_guide::features::IsPageContentAnnotationEnabled() ||
+         base::FeatureList::IsEnabled(
+             optimization_guide::features::kPageContentAnnotationsValidation) ||
+         base::FeatureList::IsEnabled(blink::features::kBrowsingTopics);
 }
 
 }  // namespace
diff --git a/chrome/browser/password_manager/android/password_manager_settings_service_android_impl.cc b/chrome/browser/password_manager/android/password_manager_settings_service_android_impl.cc
index 46f5d579..fd5188f6 100644
--- a/chrome/browser/password_manager/android/password_manager_settings_service_android_impl.cc
+++ b/chrome/browser/password_manager/android/password_manager_settings_service_android_impl.cc
@@ -15,6 +15,7 @@
 #include "components/password_manager/core/common/password_manager_features.h"
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/pref_service.h"
+#include "components/signin/public/base/signin_pref_names.h"
 #include "components/signin/public/identity_manager/account_info.h"
 #include "components/sync/base/user_selectable_type.h"
 #include "components/sync/driver/sync_service.h"
@@ -85,6 +86,8 @@
   lifecycle_helper_->RegisterObserver(base::BindRepeating(
       &PasswordManagerSettingsServiceAndroidImpl::OnChromeForegrounded,
       weak_ptr_factory_.GetWeakPtr()));
+  is_password_sync_enabled_ = IsPasswordSyncEnabled(sync_service);
+  sync_service->AddObserver(this);
 }
 
 // Constructor for tests
@@ -108,6 +111,8 @@
   lifecycle_helper_->RegisterObserver(base::BindRepeating(
       &PasswordManagerSettingsServiceAndroidImpl::OnChromeForegrounded,
       weak_ptr_factory_.GetWeakPtr()));
+  is_password_sync_enabled_ = IsPasswordSyncEnabled(sync_service);
+  sync_service->AddObserver(this);
 }
 
 PasswordManagerSettingsServiceAndroidImpl::
@@ -143,14 +148,19 @@
 void PasswordManagerSettingsServiceAndroidImpl::OnChromeForegrounded() {
   if (!IsPasswordSyncEnabled(sync_service_))
     return;
-  // TODO(crbug.com/1289700): Request the settings from the backend.
+
+  RequestSettingsFromBackend();
 }
 
 void PasswordManagerSettingsServiceAndroidImpl::OnSettingValueFetched(
-    password_manager::PasswordManagerSetting setting,
+    PasswordManagerSetting setting,
     bool value) {
-  if (!IsPasswordSyncEnabled(sync_service_))
+  UpdateSettingFetchState(setting);
+  if (!fetch_after_sync_status_change_in_progress_ &&
+      !IsPasswordSyncEnabled(sync_service_)) {
     return;
+  }
+
   const PrefService::Preference* android_pref =
       GetGMSPrefFromSetting(pref_service_, setting);
   pref_service_->SetBoolean(android_pref->name(), value);
@@ -170,8 +180,10 @@
 void PasswordManagerSettingsServiceAndroidImpl::OnSettingValueAbsent(
     password_manager::PasswordManagerSetting setting) {
   DCHECK(bridge_);
+  UpdateSettingFetchState(setting);
   if (!IsPasswordSyncEnabled(sync_service_))
     return;
+
   const PrefService::Preference* pref =
       GetGMSPrefFromSetting(pref_service_, setting);
 
@@ -202,13 +214,54 @@
   if (!IsPasswordSyncEnabled(sync_service_))
     return;
 
+  DumpChromePrefsIntoGMSPrefs();
+}
+
+void PasswordManagerSettingsServiceAndroidImpl::OnStateChanged(
+    syncer::SyncService* sync) {
+  // Return early if the setting didn't change.
+  if (IsPasswordSyncEnabled(sync) == is_password_sync_enabled_) {
+    return;
+  }
+
+  if (IsPasswordSyncEnabled(sync))
+    DumpChromePrefsIntoGMSPrefs();
+
+  // Fetch settings from the backend to align values stored in GMS Core and
+  // Chrome.
+  is_password_sync_enabled_ = IsPasswordSyncEnabled(sync);
+  fetch_after_sync_status_change_in_progress_ = true;
+  for (PasswordManagerSetting setting : kAllPasswordSettings)
+    awaited_settings_.insert(setting);
+  RequestSettingsFromBackend();
+}
+
+void PasswordManagerSettingsServiceAndroidImpl::RequestSettingsFromBackend() {
+  for (PasswordManagerSetting setting : kAllPasswordSettings) {
+    bridge_->GetPasswordSettingValue(
+        PasswordSettingsUpdaterAndroidBridge::SyncingAccount(
+            pref_service_->GetString(::prefs::kGoogleServicesLastUsername)),
+        setting);
+  }
+}
+
+void PasswordManagerSettingsServiceAndroidImpl::UpdateSettingFetchState(
+    PasswordManagerSetting received_setting) {
+  if (!fetch_after_sync_status_change_in_progress_)
+    return;
+
+  awaited_settings_.erase(received_setting);
+  if (awaited_settings_.empty())
+    fetch_after_sync_status_change_in_progress_ = false;
+}
+
+void PasswordManagerSettingsServiceAndroidImpl::DumpChromePrefsIntoGMSPrefs() {
   for (PasswordManagerSetting setting : kAllPasswordSettings) {
     const PrefService::Preference* regular_pref =
         GetRegularPrefFromSetting(pref_service_, setting);
 
-    if (!pref_service_->GetUserPrefValue(regular_pref->name())) {
+    if (!pref_service_->GetUserPrefValue(regular_pref->name()))
       continue;
-    }
 
     const PrefService::Preference* gms_pref =
         GetGMSPrefFromSetting(pref_service_, setting);
diff --git a/chrome/browser/password_manager/android/password_manager_settings_service_android_impl.h b/chrome/browser/password_manager/android/password_manager_settings_service_android_impl.h
index dde706a..02c5e0ab 100644
--- a/chrome/browser/password_manager/android/password_manager_settings_service_android_impl.h
+++ b/chrome/browser/password_manager/android/password_manager_settings_service_android_impl.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/containers/flat_set.h"
 #include "base/memory/raw_ptr.h"
 #include "chrome/browser/password_manager/android/password_manager_lifecycle_helper.h"
 #include "chrome/browser/password_manager/android/password_settings_updater_android_bridge.h"
@@ -22,7 +23,8 @@
 // the possibility of communicating with GMS.
 class PasswordManagerSettingsServiceAndroidImpl
     : public PasswordManagerSettingsService,
-      public password_manager::PasswordSettingsUpdaterAndroidBridge::Consumer {
+      public password_manager::PasswordSettingsUpdaterAndroidBridge::Consumer,
+      public syncer::SyncServiceObserver {
  public:
   PasswordManagerSettingsServiceAndroidImpl(PrefService* pref_service,
                                             syncer::SyncService* sync_service);
@@ -66,6 +68,21 @@
   // to migrate again.
   void MigratePrefsIfNeeded();
 
+  // syncer::SyncServiceObserver implementation
+  void OnStateChanged(syncer::SyncService* sync) override;
+
+  // Asynchronously fetch password settings.
+  void RequestSettingsFromBackend();
+
+  // Updates information about the current setting fetch after receiving
+  // a reply from the backend.
+  void UpdateSettingFetchState(
+      password_manager::PasswordManagerSetting received_setting);
+
+  // Copies the values of chrome prefs that have user-set values into the
+  // GMS prefs.
+  void DumpChromePrefsIntoGMSPrefs();
+
   // Pref service used to read and write password manager user prefs.
   raw_ptr<PrefService> pref_service_ = nullptr;
 
@@ -81,6 +98,17 @@
   // can request settings values from Google Mobile Services.
   std::unique_ptr<PasswordManagerLifecycleHelper> lifecycle_helper_;
 
+  // Cached value of the password sync setting.
+  bool is_password_sync_enabled_ = false;
+
+  // True if settings were requested from the backend after password sync
+  // setting was changed, and the fetch is still in progress.
+  bool fetch_after_sync_status_change_in_progress_ = false;
+
+  // Settings requested from the backend after a sunc status change, but not
+  // fetched yet.
+  base::flat_set<password_manager::PasswordManagerSetting> awaited_settings_;
+
   base::WeakPtrFactory<PasswordManagerSettingsServiceAndroidImpl>
       weak_ptr_factory_{this};
 };
diff --git a/chrome/browser/password_manager/android/password_manager_settings_service_android_impl_unittest.cc b/chrome/browser/password_manager/android/password_manager_settings_service_android_impl_unittest.cc
index feefb2c..4636f04 100644
--- a/chrome/browser/password_manager/android/password_manager_settings_service_android_impl_unittest.cc
+++ b/chrome/browser/password_manager/android/password_manager_settings_service_android_impl_unittest.cc
@@ -14,6 +14,7 @@
 #include "components/password_manager/core/common/password_manager_pref_names.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/testing_pref_service.h"
+#include "components/signin/public/base/signin_pref_names.h"
 #include "components/sync/base/user_selectable_type.h"
 #include "components/sync/driver/sync_user_settings.h"
 #include "components/sync/driver/test_sync_service.h"
@@ -52,14 +53,21 @@
   PasswordManagerSettingsServiceAndroidImplTest();
   ~PasswordManagerSettingsServiceAndroidImplTest() override;
 
+  void InitializeSettingsService(bool password_sync_enabled,
+                                 bool setting_sync_enabled);
+
   std::unique_ptr<PasswordManagerSettingsServiceAndroidImpl> CreateNewService();
+
   std::unique_ptr<PasswordManagerSettingsServiceAndroidImpl>
   GetServiceWithoutBackend();
+
   void SetPasswordsSync(bool enabled);
   void SetSettingsSync(bool enabled);
 
   void AssertInitialMigrationDidntChangePrefs();
 
+  void ExpectSettingsRetrievalFromBackend();
+
   PasswordSettingsUpdaterAndroidBridge::Consumer* updater_bridge_consumer() {
     return settings_service_.get();
   }
@@ -88,9 +96,16 @@
   RegisterPrefs();
   sync_account_info_.email = kTestAccount;
   test_sync_service_.SetAccountInfo(sync_account_info_);
-  SetPasswordsSync(true);
-  SetSettingsSync(true);
+}
 
+PasswordManagerSettingsServiceAndroidImplTest::
+    ~PasswordManagerSettingsServiceAndroidImplTest() {
+  testing::Mock::VerifyAndClearExpectations(mock_bridge_);
+}
+
+void PasswordManagerSettingsServiceAndroidImplTest::InitializeSettingsService(
+    bool password_sync_enabled,
+    bool setting_sync_enabled) {
   std::unique_ptr<MockPasswordSettingsUpdaterBridge> bridge =
       std::make_unique<MockPasswordSettingsUpdaterBridge>();
   mock_bridge_ = bridge.get();
@@ -99,6 +114,8 @@
       std::make_unique<FakePasswordManagerLifecycleHelper>();
   fake_lifecycle_helper_ = lifecycle_helper.get();
 
+  SetPasswordsSync(password_sync_enabled);
+  SetSettingsSync(setting_sync_enabled);
   settings_service_ =
       std::make_unique<PasswordManagerSettingsServiceAndroidImpl>(
           base::PassKey<class PasswordManagerSettingsServiceAndroidImplTest>(),
@@ -106,11 +123,6 @@
           std::move(lifecycle_helper));
 }
 
-PasswordManagerSettingsServiceAndroidImplTest::
-    ~PasswordManagerSettingsServiceAndroidImplTest() {
-  testing::Mock::VerifyAndClearExpectations(mock_bridge_);
-}
-
 std::unique_ptr<PasswordManagerSettingsServiceAndroidImpl>
 PasswordManagerSettingsServiceAndroidImplTest::GetServiceWithoutBackend() {
   return std::make_unique<PasswordManagerSettingsServiceAndroidImpl>(
@@ -156,6 +168,8 @@
                                                          selected_sync_types);
 }
 
+// TODO(crbug.com/1324648): Get rid of this method by not instantiating the
+// service when it's not needed from the beginning of the test.
 void PasswordManagerSettingsServiceAndroidImplTest::
     AssertInitialMigrationDidntChangePrefs() {
   // A migration already happened when the service was created in the test
@@ -172,6 +186,20 @@
       password_manager::prefs::kSettingsMigratedToUPM));
 }
 
+void PasswordManagerSettingsServiceAndroidImplTest::
+    ExpectSettingsRetrievalFromBackend() {
+  EXPECT_CALL(*bridge(),
+              GetPasswordSettingValue(
+                  Eq(PasswordSettingsUpdaterAndroidBridge::SyncingAccount(
+                      kTestAccount)),
+                  Eq(PasswordManagerSetting::kOfferToSavePasswords)));
+  EXPECT_CALL(*bridge(),
+              GetPasswordSettingValue(
+                  Eq(PasswordSettingsUpdaterAndroidBridge::SyncingAccount(
+                      kTestAccount)),
+                  Eq(PasswordManagerSetting::kAutoSignIn)));
+}
+
 void PasswordManagerSettingsServiceAndroidImplTest::RegisterPrefs() {
   test_pref_service_.registry()->RegisterBooleanPref(
       password_manager::prefs::kCredentialsEnableService, true);
@@ -181,13 +209,16 @@
       password_manager::prefs::kOfferToSavePasswordsEnabledGMS, true);
   test_pref_service_.registry()->RegisterBooleanPref(
       password_manager::prefs::kAutoSignInEnabledGMS, true);
-
+  test_pref_service_.registry()->RegisterStringPref(
+      ::prefs::kGoogleServicesLastUsername, kTestAccount);
   test_pref_service_.registry()->RegisterBooleanPref(
       password_manager::prefs::kSettingsMigratedToUPM, false);
 }
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        TestNoAdditionalMigration) {
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/true);
   AssertInitialMigrationDidntChangePrefs();
 
   // No additional migration should happen if the migration pref wasn't reset.
@@ -206,6 +237,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        TestNewMigrationIfPrefUndoneSyncOff) {
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/true);
   AssertInitialMigrationDidntChangePrefs();
 
   // Reset the migration pref.
@@ -233,6 +266,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        TestNewMigrationIfPrefUndoneSyncOn) {
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/true);
   AssertInitialMigrationDidntChangePrefs();
 
   // Reset the migration pref.
@@ -256,6 +291,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        TestNewMigrationManagedPrefSetValue) {
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/true);
   AssertInitialMigrationDidntChangePrefs();
 
   // Reset the migration pref.
@@ -284,6 +321,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        TestNewMigrationManagedPrefDefaultValue) {
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/true);
   AssertInitialMigrationDidntChangePrefs();
 
   // Reset the migration pref.
@@ -309,6 +348,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        OnSaveSettingFetchSyncingBoth) {
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/true);
   ASSERT_TRUE(pref_service()->GetBoolean(
       password_manager::prefs::kCredentialsEnableService));
   ASSERT_TRUE(pref_service()->GetBoolean(
@@ -325,7 +366,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        OnSaveSettingFetchNotSyncingSettings) {
-  SetSettingsSync(/*enabled=*/false);
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/false);
   ASSERT_TRUE(pref_service()->GetBoolean(
       password_manager::prefs::kCredentialsEnableService));
   ASSERT_TRUE(pref_service()->GetBoolean(
@@ -342,7 +384,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        OnSaveSettingFetchNotSyncingPasswords) {
-  SetPasswordsSync(/*enabled=*/false);
+  InitializeSettingsService(/*password_sync_enabled=*/false,
+                            /*setting_sync_enabled=*/true);
   ASSERT_TRUE(pref_service()->GetBoolean(
       password_manager::prefs::kCredentialsEnableService));
   ASSERT_TRUE(pref_service()->GetBoolean(
@@ -359,6 +402,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        OnAutoSignInSettingFetchSyncingBoth) {
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/true);
   ASSERT_TRUE(pref_service()->GetBoolean(
       password_manager::prefs::kCredentialsEnableAutosignin));
   ASSERT_TRUE(pref_service()->GetBoolean(
@@ -375,7 +420,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        OnAutoSignInFetchNotSyncingSettings) {
-  SetSettingsSync(/*enabled=*/false);
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/false);
   ASSERT_TRUE(pref_service()->GetBoolean(
       password_manager::prefs::kCredentialsEnableAutosignin));
   ASSERT_TRUE(pref_service()->GetBoolean(
@@ -392,7 +438,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        OnAutoSignInFetchNotSyncingPasswords) {
-  SetPasswordsSync(/*enabled=*/false);
+  InitializeSettingsService(/*password_sync_enabled=*/false,
+                            /*setting_sync_enabled=*/true);
   ASSERT_TRUE(pref_service()->GetBoolean(
       password_manager::prefs::kCredentialsEnableAutosignin));
   ASSERT_TRUE(pref_service()->GetBoolean(
@@ -409,6 +456,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        OnSaveSettingAbsentDefaultSyncing) {
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/true);
   EXPECT_CALL(*bridge(), SetPasswordSettingValue(_, _, _)).Times(0);
   updater_bridge_consumer()->OnSettingValueAbsent(
       PasswordManagerSetting::kOfferToSavePasswords);
@@ -416,6 +465,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        OnSaveSettingAbsentSetValueSyncing) {
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/true);
   pref_service()->SetUserPref(
       password_manager::prefs::kOfferToSavePasswordsEnabledGMS,
       base::Value(false));
@@ -430,7 +481,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        OnSaveSettingAbsentSetValueNotSyncing) {
-  SetPasswordsSync(/*enabled=*/false);
+  InitializeSettingsService(/*password_sync_enabled=*/false,
+                            /*setting_sync_enabled=*/true);
   pref_service()->SetUserPref(
       password_manager::prefs::kOfferToSavePasswordsEnabledGMS,
       base::Value(false));
@@ -441,6 +493,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        OnAutoSignInAbsentDefaultSyncing) {
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/true);
   EXPECT_CALL(*bridge(), SetPasswordSettingValue(_, _, _)).Times(0);
   updater_bridge_consumer()->OnSettingValueAbsent(
       PasswordManagerSetting::kAutoSignIn);
@@ -448,6 +502,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        OnAutoSignInAbsentSetValueSyncing) {
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/true);
   pref_service()->SetUserPref(password_manager::prefs::kAutoSignInEnabledGMS,
                               base::Value(false));
   EXPECT_CALL(*bridge(),
@@ -461,7 +517,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        OnAutoSignInAbsentSetValueNotSyncing) {
-  SetPasswordsSync(/*enabled=*/false);
+  InitializeSettingsService(/*password_sync_enabled=*/false,
+                            /*setting_sync_enabled=*/true);
   pref_service()->SetUserPref(password_manager::prefs::kAutoSignInEnabledGMS,
                               base::Value(false));
   EXPECT_CALL(*bridge(), SetPasswordSettingValue(_, _, _)).Times(0);
@@ -469,9 +526,176 @@
       PasswordManagerSetting::kAutoSignIn);
 }
 
+// Checks that general syncable prefs are dumped into the android-only GMS
+// prefs before settings are requested when sync is enabled.
+TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
+       PasswordSyncEnablingPrefsMoving) {
+  InitializeSettingsService(/*password_sync_enabled=*/false,
+                            /*setting_sync_enabled=*/false);
+  pref_service()->SetUserPref(
+      password_manager::prefs::kCredentialsEnableService, base::Value(false));
+  pref_service()->SetUserPref(
+      password_manager::prefs::kCredentialsEnableAutosignin,
+      base::Value(false));
+  ASSERT_TRUE(pref_service()->GetBoolean(
+      password_manager::prefs::kOfferToSavePasswordsEnabledGMS));
+  ASSERT_TRUE(pref_service()->GetBoolean(
+      password_manager::prefs::kAutoSignInEnabledGMS));
+
+  SetPasswordsSync(/*enabled=*/true);
+  sync_service()->FireStateChanged();
+
+  EXPECT_FALSE(pref_service()->GetBoolean(
+      password_manager::prefs::kOfferToSavePasswordsEnabledGMS));
+  EXPECT_FALSE(pref_service()->GetBoolean(
+      password_manager::prefs::kAutoSignInEnabledGMS));
+}
+
+TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
+       PasswordSyncEnablingGMSSettingAbsentChromeSettingDefault) {
+  InitializeSettingsService(/*password_sync_enabled=*/false,
+                            /*setting_sync_enabled=*/false);
+  ASSERT_TRUE(pref_service()->GetBoolean(
+      password_manager::prefs::kCredentialsEnableAutosignin));
+  ASSERT_TRUE(pref_service()->GetBoolean(
+      password_manager::prefs::kAutoSignInEnabledGMS));
+
+  // Settings should be requested from GMS Core on sync state change.
+  ExpectSettingsRetrievalFromBackend();
+  SetPasswordsSync(/*enabled=*/true);
+  sync_service()->FireStateChanged();
+
+  // If there is no user setting stored both in GMS Core and in Chrome,
+  // no setting should be changed in GMS Core.
+  EXPECT_CALL(*bridge(), SetPasswordSettingValue).Times(0);
+  updater_bridge_consumer()->OnSettingValueAbsent(
+      PasswordManagerSetting::kAutoSignIn);
+
+  EXPECT_TRUE(pref_service()->GetBoolean(
+      password_manager::prefs::kCredentialsEnableAutosignin));
+  EXPECT_TRUE(pref_service()->GetBoolean(
+      password_manager::prefs::kAutoSignInEnabledGMS));
+}
+
+TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
+       PasswordSyncEnablingGMSSettingAbsentChromeHasUserSetting) {
+  InitializeSettingsService(/*password_sync_enabled=*/false,
+                            /*setting_sync_enabled=*/false);
+  pref_service()->SetBoolean(
+      password_manager::prefs::kCredentialsEnableAutosignin, false);
+  ASSERT_TRUE(pref_service()->GetBoolean(
+      password_manager::prefs::kAutoSignInEnabledGMS));
+
+  // Settings should be requested from GMS Core on sync state change.
+  ExpectSettingsRetrievalFromBackend();
+  SetPasswordsSync(/*enabled=*/true);
+  sync_service()->FireStateChanged();
+
+  // If there is no user setting stored in GMS Core, Chrome setting should be
+  // set in it.
+  EXPECT_CALL(*bridge(),
+              SetPasswordSettingValue(
+                  Eq(PasswordSettingsUpdaterAndroidBridge::SyncingAccount(
+                      kTestAccount)),
+                  Eq(PasswordManagerSetting::kAutoSignIn), false));
+  updater_bridge_consumer()->OnSettingValueAbsent(
+      PasswordManagerSetting::kAutoSignIn);
+
+  EXPECT_FALSE(pref_service()->GetBoolean(
+      password_manager::prefs::kCredentialsEnableAutosignin));
+  EXPECT_FALSE(pref_service()->GetBoolean(
+      password_manager::prefs::kAutoSignInEnabledGMS));
+}
+
+TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
+       PasswordSyncEnablingGMSHasSetting) {
+  InitializeSettingsService(/*password_sync_enabled=*/false,
+                            /*setting_sync_enabled=*/false);
+  ASSERT_TRUE(pref_service()->GetBoolean(
+      password_manager::prefs::kCredentialsEnableService));
+  ASSERT_TRUE(pref_service()->GetBoolean(
+      password_manager::prefs::kOfferToSavePasswordsEnabledGMS));
+
+  // Settings should be requested from GMS Core on sync state change.
+  ExpectSettingsRetrievalFromBackend();
+  SetPasswordsSync(/*enabled=*/true);
+  sync_service()->FireStateChanged();
+
+  // If the setting in Chrome differs from the setting in GMS Core, GMS Core
+  // setting is stored in prefs and used.
+  EXPECT_CALL(*bridge(),
+              SetPasswordSettingValue(
+                  _, Eq(PasswordManagerSetting::kOfferToSavePasswords), _))
+      .Times(0);
+  updater_bridge_consumer()->OnSettingValueFetched(
+      PasswordManagerSetting::kOfferToSavePasswords, /*value=*/false);
+
+  EXPECT_FALSE(pref_service()->GetBoolean(
+      password_manager::prefs::kCredentialsEnableService));
+  EXPECT_FALSE(pref_service()->GetBoolean(
+      password_manager::prefs::kOfferToSavePasswordsEnabledGMS));
+}
+
+TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
+       PasswordSyncDisablingGMSSettingAbsent) {
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/false);
+  ASSERT_TRUE(pref_service()->GetBoolean(
+      password_manager::prefs::kCredentialsEnableAutosignin));
+  ASSERT_TRUE(pref_service()->GetBoolean(
+      password_manager::prefs::kAutoSignInEnabledGMS));
+
+  // Settings should be requested from GMS Core on sync state change.
+  ExpectSettingsRetrievalFromBackend();
+  SetPasswordsSync(/*enabled=*/false);
+  sync_service()->FireStateChanged();
+
+  // If there is no user setting stored in GMS Core, nothing should happen.
+  EXPECT_CALL(*bridge(), SetPasswordSettingValue(
+                             _, Eq(PasswordManagerSetting::kAutoSignIn), _))
+      .Times(0);
+  updater_bridge_consumer()->OnSettingValueAbsent(
+      PasswordManagerSetting::kAutoSignIn);
+
+  EXPECT_TRUE(pref_service()->GetBoolean(
+      password_manager::prefs::kCredentialsEnableAutosignin));
+  EXPECT_TRUE(pref_service()->GetBoolean(
+      password_manager::prefs::kAutoSignInEnabledGMS));
+}
+
+TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
+       PasswordSyncDisablingGMSHasSetting) {
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/false);
+  ASSERT_TRUE(pref_service()->GetBoolean(
+      password_manager::prefs::kCredentialsEnableService));
+  ASSERT_TRUE(pref_service()->GetBoolean(
+      password_manager::prefs::kOfferToSavePasswordsEnabledGMS));
+
+  // Settings should be requested from GMS Core on sync state change.
+  ExpectSettingsRetrievalFromBackend();
+  SetPasswordsSync(/*enabled=*/false);
+  sync_service()->FireStateChanged();
+
+  // If the setting in Chrome differs from the setting in GMS Core, GMS Core
+  // setting is stored in prefs and used.
+  EXPECT_CALL(*bridge(),
+              SetPasswordSettingValue(
+                  _, Eq(PasswordManagerSetting::kOfferToSavePasswords), _))
+      .Times(0);
+  updater_bridge_consumer()->OnSettingValueFetched(
+      PasswordManagerSetting::kOfferToSavePasswords, /*value=*/false);
+
+  EXPECT_FALSE(pref_service()->GetBoolean(
+      password_manager::prefs::kCredentialsEnableService));
+  EXPECT_FALSE(pref_service()->GetBoolean(
+      password_manager::prefs::kOfferToSavePasswordsEnabledGMS));
+}
+
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        SavePasswordsSettingNotSyncing) {
-  SetPasswordsSync(/*enabled=*/false);
+  InitializeSettingsService(/*password_sync_enabled=*/false,
+                            /*setting_sync_enabled=*/true);
   pref_service()->SetUserPref(
       password_manager::prefs::kCredentialsEnableService, base::Value(true));
   pref_service()->SetUserPref(
@@ -483,7 +707,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        SavePasswordsSettingSyncingManaged) {
-  SetPasswordsSync(/*enabled=*/true);
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/true);
   pref_service()->SetManagedPref(
       password_manager::prefs::kCredentialsEnableService, base::Value(false));
   pref_service()->SetUserPref(
@@ -511,7 +736,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        SavePasswordsSettingSyncingNotManaged) {
-  SetPasswordsSync(/*enabled=*/true);
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/true);
   pref_service()->SetUserPref(
       password_manager::prefs::kCredentialsEnableService, base::Value(true));
   pref_service()->SetUserPref(
@@ -523,7 +749,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        AutoSignInSettingNotSyncing) {
-  SetPasswordsSync(/*enabled=*/false);
+  InitializeSettingsService(/*password_sync_enabled=*/false,
+                            /*setting_sync_enabled=*/true);
   pref_service()->SetUserPref(
       password_manager::prefs::kCredentialsEnableAutosignin, base::Value(true));
   pref_service()->SetUserPref(password_manager::prefs::kAutoSignInEnabledGMS,
@@ -534,7 +761,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        AutoSignInSettingSyncingManaged) {
-  SetPasswordsSync(/*enabled=*/true);
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/true);
   pref_service()->SetManagedPref(
       password_manager::prefs::kCredentialsEnableAutosignin,
       base::Value(false));
@@ -561,7 +789,8 @@
 
 TEST_F(PasswordManagerSettingsServiceAndroidImplTest,
        AutoSignInSettingSyncingNotManaged) {
-  SetPasswordsSync(/*enabled=*/true);
+  InitializeSettingsService(/*password_sync_enabled=*/true,
+                            /*setting_sync_enabled=*/true);
   pref_service()->SetUserPref(
       password_manager::prefs::kCredentialsEnableAutosignin, base::Value(true));
   pref_service()->SetUserPref(password_manager::prefs::kAutoSignInEnabledGMS,
diff --git a/chrome/browser/platform_keys/extension_key_permissions_service.cc b/chrome/browser/platform_keys/extension_key_permissions_service.cc
index 8601b60..a60b712 100644
--- a/chrome/browser/platform_keys/extension_key_permissions_service.cc
+++ b/chrome/browser/platform_keys/extension_key_permissions_service.cc
@@ -366,25 +366,24 @@
 
 std::unique_ptr<base::Value>
 ExtensionKeyPermissionsService::KeyEntriesToState() {
-  std::unique_ptr<base::ListValue> new_state(new base::ListValue);
+  base::Value::List new_state;
   for (const KeyEntry& entry : state_store_entries_) {
     // Drop entries that the extension doesn't have any permissions for anymore.
     if (!entry.sign_once && !entry.sign_unlimited)
       continue;
 
-    std::unique_ptr<base::DictionaryValue> new_entry(new base::DictionaryValue);
-    new_entry->SetKey(kStateStoreSPKI, base::Value(entry.spki_b64));
+    base::Value::Dict new_entry;
+    new_entry.Set(kStateStoreSPKI, entry.spki_b64);
     // Omit writing default values, namely |false|.
     if (entry.sign_once) {
-      new_entry->SetKey(kStateStoreSignOnce, base::Value(entry.sign_once));
+      new_entry.Set(kStateStoreSignOnce, entry.sign_once);
     }
     if (entry.sign_unlimited) {
-      new_entry->SetKey(kStateStoreSignUnlimited,
-                        base::Value(entry.sign_unlimited));
+      new_entry.Set(kStateStoreSignUnlimited, entry.sign_unlimited);
     }
-    new_state->Append(std::move(new_entry));
+    new_state.Append(std::move(new_entry));
   }
-  return std::move(new_state);
+  return std::make_unique<base::Value>(std::move(new_state));
 }
 
 // static
diff --git a/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.cc b/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.cc
index e61610d..12caa20 100644
--- a/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.cc
+++ b/chrome/browser/policy/messaging_layer/upload/record_upload_request_builder.cc
@@ -44,7 +44,7 @@
 constexpr char kEncryptedRecordListKey[] = "encryptedRecord";
 constexpr char kAttachEncryptionSettingsKey[] = "attachEncryptionSettings";
 
-// EncrypedRecordDictionaryBuilder strings
+// EncryptedRecordDictionaryBuilder strings
 constexpr char kEncryptedWrappedRecord[] = "encryptedWrappedRecord";
 constexpr char kSequenceInformationKey[] = "sequenceInformation";
 constexpr char kEncryptionInfoKey[] = "encryptionInfo";
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn
index 54cbe1dc..937b198 100644
--- a/chrome/browser/resources/BUILD.gn
+++ b/chrome/browser/resources/BUILD.gn
@@ -144,9 +144,6 @@
     if (is_win || is_android || is_linux || is_chromeos) {
       deps += [ "sandbox_internals:closure_compile" ]
     }
-    if (is_linux || is_chromeos) {
-      deps += [ "webui_js_error:closure_compile" ]
-    }
     if (is_chromeos_ash) {
       deps += [
         "chromeos:closure_compile",
diff --git a/chrome/browser/resources/access_code_cast/access_code_cast.html b/chrome/browser/resources/access_code_cast/access_code_cast.html
index 91afd54..f0de970 100644
--- a/chrome/browser/resources/access_code_cast/access_code_cast.html
+++ b/chrome/browser/resources/access_code_cast/access_code_cast.html
@@ -34,6 +34,25 @@
     background-color: transparent;
   }
 
+  #error-message-container {
+    min-height: 16px;
+  }
+
+  #remembered-device-footnote {
+    align-items: center;
+    display: flex;
+    font-size: 12px;
+    justify-content: center;
+  }
+
+  #remembered-device-icon {
+    align-self: flex-start;
+    flex-shrink: 0;
+    height: 20px;
+    padding-inline-end: 16px;
+    width: 20px;
+  }
+
   a[href] {
     color: var(--cr-link-color);
     text-decoration: none;
@@ -80,7 +99,18 @@
     <div id="qrInputView">
       <div>Camera input view</div>
     </div>
-    <c2c-error-message id="errorMessage"></c2c-error-message>
+    <div id="error-message-container">
+      <c2c-error-message id="errorMessage"></c2c-error-message>
+    </div>
+    <div class="space-1"></div>
+    <template is="dom-if" if="[[rememberDevices]]">
+      <div id="remembered-device-footnote">
+        <iron-icon icon="cr:domain" id="remembered-device-icon"></iron-icon>
+        <div id="remembered-device-content">
+          [[managedFootnote]]
+        </div>
+      </div>
+    </template>
   </div>
   <div slot="button-container">
     <cr-button on-click="close" class="cancel-button">$i18n{cancel}</cr-button>
diff --git a/chrome/browser/resources/access_code_cast/access_code_cast.ts b/chrome/browser/resources/access_code_cast/access_code_cast.ts
index b3bbf80..13f2138 100644
--- a/chrome/browser/resources/access_code_cast/access_code_cast.ts
+++ b/chrome/browser/resources/access_code_cast/access_code_cast.ts
@@ -14,10 +14,11 @@
 import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+import {PluralStringProxyImpl} from 'chrome://resources/js/plural_string_proxy.js';
 import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
 import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-
 import {AddSinkResultCode, CastDiscoveryMethod, PageCallbackRouter} from './access_code_cast.mojom-webui.js';
 import {BrowserProxy} from './browser_proxy.js';
 import {PasscodeInputElement} from './passcode_input/passcode_input.js';
@@ -44,6 +45,12 @@
 const AccessCodeCastElementBase =
     WebUIListenerMixin(I18nMixin(PolymerElement));
 
+const ECMASCRIPT_EPOCH_START_YEAR = 1970;
+const SECONDS_PER_DAY = 86400;
+const SECONDS_PER_HOUR = 3600;
+const SECONDS_PER_MONTH = 2592000;
+const SECONDS_PER_YEAR = 31536000;
+
 export class AccessCodeCastElement extends AccessCodeCastElementBase {
   static get is() {
     return 'access-code-cast-app';
@@ -72,12 +79,15 @@
   private router: PageCallbackRouter;
 
   private static readonly ACCESS_CODE_LENGTH = 6;
+
   private accessCode: string;
   private canCast: boolean;
   private inputLabel: string;
   private state: PageState;
   private submitDisabled: boolean;
   private qrScannerEnabled: boolean;
+  private rememberDevices: boolean;
+  private managedFootnote: string;
 
   constructor() {
     super();
@@ -85,6 +95,9 @@
     this.router = BrowserProxy.getInstance().callbackRouter;
     this.inputLabel = this.i18n('inputLabel');
 
+    this.createManagedFootnote(
+        loadTimeData.getInteger('rememberedDeviceDuration'));
+
     this.accessCode = '';
     BrowserProxy.getInstance().isQrScanningAvailable().then((available) => {
       this.qrScannerEnabled = available;
@@ -168,10 +181,57 @@
     this.close();
   }
 
+  async createManagedFootnote(duration: number) {
+    if (duration === 0) {
+      return;
+    }
+
+
+    // Handle the cases from the policy enum.
+    if (duration === SECONDS_PER_HOUR) {
+      return this.makeFootnote('managedFootnoteHours', 1);
+    } else if (duration === SECONDS_PER_DAY) {
+      return this.makeFootnote('managedFootnoteDays', 1);
+    } else if (duration === SECONDS_PER_MONTH) {
+      return this.makeFootnote('managedFootnoteMonths', 1);
+    } else if (duration === SECONDS_PER_YEAR) {
+      return this.makeFootnote('managedFootnoteYears', 1);
+    }
+
+    // Handle the general case.
+    const durationAsDate = new Date(duration * 1000);
+    // ECMAscript epoch starts at 1970.
+    if (durationAsDate.getUTCFullYear() - ECMASCRIPT_EPOCH_START_YEAR > 0) {
+      return this.makeFootnote('managedFootnoteYears',
+          durationAsDate.getUTCFullYear() - ECMASCRIPT_EPOCH_START_YEAR);
+    // Months are zero indexed.
+    } else if (durationAsDate.getUTCMonth() > 0) {
+      return this.makeFootnote('managedFootnoteMonths',
+          durationAsDate.getUTCMonth());
+    // Dates start at 1.
+    } else if (durationAsDate.getUTCDate() - 1 > 0) {
+      return this.makeFootnote('managedFootnoteDays',
+          durationAsDate.getUTCDate() - 1);
+    // Hours start at 0.
+    } else if (durationAsDate.getUTCHours() > 0) {
+      return this.makeFootnote('managedFootnoteHours',
+          durationAsDate.getUTCHours());
+    // The given duration is either minutes, seconds, or a negative time. These
+    // are not valid so we should not show the managed footnote.
+    }
+
+    this.rememberDevices = false;
+    return;
+  }
+
   setAccessCodeForTest(value: string) {
     this.accessCode = value;
   }
 
+  getManagedFootnoteForTest() {
+    return this.managedFootnote;
+  }
+
   private castStateChange() {
     this.submitDisabled = !this.canCast ||
         this.accessCode.length !== AccessCodeCastElement.ACCESS_CODE_LENGTH;
@@ -231,6 +291,12 @@
     const castResult = await BrowserProxy.getInstance().handler.castToSink();
     return castResult.resultCode as RouteRequestResultCode;
   }
+
+  private async makeFootnote(messageName: string, value: number) {
+    const proxy = PluralStringProxyImpl.getInstance();
+    this.managedFootnote = await proxy.getPluralString(messageName, value);
+    this.rememberDevices = true;
+  }
 }
 
 declare global {
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/panel/i_search.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/panel/i_search.js
index 0e94f94..907100d6 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/panel/i_search.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/panel/i_search.js
@@ -29,10 +29,6 @@
 
     /** @private {number} */
     this.callbackId_ = 0;
-
-    // Global exports.
-    /** Exported for the panel script. */
-    ChromeVox = chrome.extension.getBackgroundPage()['ChromeVox'];
   }
 
   /** @param {?ISearchHandler} handler */
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.js b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.js
index 68b7ebd..ef245c6 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.js
@@ -300,10 +300,6 @@
       Panel.clearMenus();
       Panel.pendingCallback_ = null;
 
-      // Save the ChromeVox range (on the non-ChromeVox Menu UI first).
-      const bkgnd = chrome.extension.getBackgroundPage();
-      const range = bkgnd.ChromeVoxState.instance.getCurrentRange();
-      const node = range ? range.start.node : null;
       const eventSourceState = await BackgroundBridge.EventSourceState.get();
       const touchScreen =
           (eventSourceState === EventSourceType.TOUCH_GESTURE ||
@@ -1000,9 +996,7 @@
    * Open the ChromeVox Options.
    */
   static onOptions() {
-    const bkgnd =
-        chrome.extension.getBackgroundPage()['ChromeVoxState']['instance'];
-    bkgnd['showOptionsPage']();
+    chrome.runtime.openOptionsPage();
     Panel.setMode(PanelMode.COLLAPSED);
   }
 
diff --git a/chrome/browser/resources/feedback_webui/BUILD.gn b/chrome/browser/resources/feedback_webui/BUILD.gn
index 08c9ea8..9543f34 100644
--- a/chrome/browser/resources/feedback_webui/BUILD.gn
+++ b/chrome/browser/resources/feedback_webui/BUILD.gn
@@ -11,6 +11,7 @@
 
 # Note: No need to pass these CSS files to preprocess_if_expr() for now.
 css_files = [
+  "css/common.css",
   "css/feedback.css",
   "css/feedback_shared_styles.css",
   "css/feedback_shared_vars.css",
diff --git a/ui/webui/resources/css/apps/common.css b/chrome/browser/resources/feedback_webui/css/common.css
similarity index 100%
rename from ui/webui/resources/css/apps/common.css
rename to chrome/browser/resources/feedback_webui/css/common.css
diff --git a/chrome/browser/resources/feedback_webui/html/default.html b/chrome/browser/resources/feedback_webui/html/default.html
index da852fe3..6582e7a 100644
--- a/chrome/browser/resources/feedback_webui/html/default.html
+++ b/chrome/browser/resources/feedback_webui/html/default.html
@@ -5,8 +5,7 @@
   <meta charset="utf-8">
   <meta name="color-scheme" content="light dark">
   <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
-  <link rel="stylesheet" href="chrome://resources/css/apps/common.css"></link>
-  </link>
+  <link rel="stylesheet" href="../css/common.css">
   <link rel="stylesheet" href="../css/feedback.css">
   <link rel="stylesheet" href="../css/feedback_shared_styles.css">
 
diff --git a/chrome/browser/resources/print_preview/BUILD.gn b/chrome/browser/resources/print_preview/BUILD.gn
index 74bd7d38..0f60507 100644
--- a/chrome/browser/resources/print_preview/BUILD.gn
+++ b/chrome/browser/resources/print_preview/BUILD.gn
@@ -7,7 +7,7 @@
 import("//printing/buildflags/buildflags.gni")
 import("//tools/grit/grit_rule.gni")
 import("//tools/grit/preprocess_if_expr.gni")
-import("//tools/polymer/html_to_js.gni")
+import("//tools/polymer/css_to_wrapper.gni")
 import("//tools/polymer/html_to_wrapper.gni")
 import("//tools/typescript/ts_library.gni")
 import("//ui/webui/resources/tools/generate_grd.gni")
@@ -56,12 +56,12 @@
   out_grd = "$target_gen_dir/${grd_prefix}_resources.grd"
 }
 
-html_to_js("css_wrapper_files") {
-  js_files = css_wrapper_files
+css_to_wrapper("css_wrapper_files") {
+  in_files = css_files
 }
 
 html_to_wrapper("html_wrapper_files") {
-  in_files = html_files
+  in_files = html_files + icons_html_files
 }
 
 preprocess_if_expr("preprocess") {
diff --git a/chrome/browser/resources/print_preview/print_preview.gni b/chrome/browser/resources/print_preview/print_preview.gni
index b5b20ad9..616aa52 100644
--- a/chrome/browser/resources/print_preview/print_preview.gni
+++ b/chrome/browser/resources/print_preview/print_preview.gni
@@ -58,9 +58,11 @@
   html_files += [ string_replace(f, ".ts", ".html") ]
 }
 
+icons_html_files = [ "ui/icons.html" ]
+
 # Files that are generated by html_to_wrapper().
 html_wrapper_files = []
-foreach(f, html_files) {
+foreach(f, html_files + icons_html_files) {
   html_wrapper_files += [ f + ".ts" ]
 }
 
@@ -99,12 +101,18 @@
   ]
 }
 
-css_wrapper_files = [
-  "ui/destination_dialog_css.ts",
-  "ui/destination_list_item_css.ts",
-  "ui/destination_select_css.ts",
-  "ui/icons.ts",
-  "ui/print_preview_shared_css.ts",
-  "ui/print_preview_vars_css.ts",
-  "ui/throbber_css.ts",
+# Files that are passed as input to css_to_wrapper().
+css_files = [
+  "ui/destination_dialog_style.css",
+  "ui/destination_list_item_style.css",
+  "ui/destination_select_style.css",
+  "ui/print_preview_shared.css",
+  "ui/print_preview_vars.css",
+  "ui/throbber.css",
 ]
+
+# Files that are generated by css_to_wrapper().
+css_wrapper_files = []
+foreach(f, css_files) {
+  css_wrapper_files += [ f + ".ts" ]
+}
diff --git a/chrome/browser/resources/print_preview/ui/advanced_options_settings.ts b/chrome/browser/resources/print_preview/ui/advanced_options_settings.ts
index e4467cf6..a16f4552 100644
--- a/chrome/browser/resources/print_preview/ui/advanced_options_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/advanced_options_settings.ts
@@ -4,7 +4,7 @@
 
 import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
 import './advanced_settings_dialog.js';
-import './print_preview_shared_css.js';
+import './print_preview_shared.css.js';
 import './settings_section.js';
 
 import {CrButtonElement} from 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
diff --git a/chrome/browser/resources/print_preview/ui/advanced_settings_dialog.ts b/chrome/browser/resources/print_preview/ui/advanced_settings_dialog.ts
index e7679bc..ea1b1efe 100644
--- a/chrome/browser/resources/print_preview/ui/advanced_settings_dialog.ts
+++ b/chrome/browser/resources/print_preview/ui/advanced_settings_dialog.ts
@@ -7,8 +7,8 @@
 import 'chrome://resources/cr_elements/hidden_style_css.m.js';
 import './advanced_settings_item.js';
 import './print_preview_search_box.js';
-import './print_preview_shared_css.js';
-import './print_preview_vars_css.js';
+import './print_preview_shared.css.js';
+import './print_preview_vars.css.js';
 import '../strings.m.js';
 
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
diff --git a/chrome/browser/resources/print_preview/ui/advanced_settings_item.ts b/chrome/browser/resources/print_preview/ui/advanced_settings_item.ts
index 1dc8f99..8596e68d 100644
--- a/chrome/browser/resources/print_preview/ui/advanced_settings_item.ts
+++ b/chrome/browser/resources/print_preview/ui/advanced_settings_item.ts
@@ -8,7 +8,7 @@
 import 'chrome://resources/cr_elements/search_highlight_style.css.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/cr_elements/md_select_css.m.js';
-import './print_preview_shared_css.js';
+import './print_preview_shared.css.js';
 
 import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
 import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
diff --git a/chrome/browser/resources/print_preview/ui/app.ts b/chrome/browser/resources/print_preview/ui/app.ts
index 0ecb605..cb3c9825 100644
--- a/chrome/browser/resources/print_preview/ui/app.ts
+++ b/chrome/browser/resources/print_preview/ui/app.ts
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
-import './print_preview_vars_css.js';
+import './print_preview_vars.css.js';
 import '../strings.m.js';
 import '../data/document_info.js';
 import './sidebar.js';
diff --git a/chrome/browser/resources/print_preview/ui/color_settings.ts b/chrome/browser/resources/print_preview/ui/color_settings.ts
index 9ad006a..1bb26fd 100644
--- a/chrome/browser/resources/print_preview/ui/color_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/color_settings.ts
@@ -3,12 +3,12 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/md_select_css.m.js';
-import './print_preview_shared_css.js';
+import './print_preview_shared.css.js';
 import './settings_section.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {getTemplate} from './color_settings.html.js';
 
+import {getTemplate} from './color_settings.html.js';
 import {SelectMixin} from './select_mixin.js';
 import {SettingsMixin} from './settings_mixin.js';
 
diff --git a/chrome/browser/resources/print_preview/ui/copies_settings.ts b/chrome/browser/resources/print_preview/ui/copies_settings.ts
index 6085ece7..19cdc1b 100644
--- a/chrome/browser/resources/print_preview/ui/copies_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/copies_settings.ts
@@ -4,7 +4,7 @@
 
 import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
 import './number_settings_section.js';
-import './print_preview_shared_css.js';
+import './print_preview_shared.css.js';
 
 import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
diff --git a/chrome/browser/resources/print_preview/ui/destination_dialog.html b/chrome/browser/resources/print_preview/ui/destination_dialog.html
index 5ea55a097..5d16589 100644
--- a/chrome/browser/resources/print_preview/ui/destination_dialog.html
+++ b/chrome/browser/resources/print_preview/ui/destination_dialog.html
@@ -1,4 +1,4 @@
-<style include="destination-dialog">
+<style include="destination-dialog-style">
 </style>
 <cr-dialog id="dialog" on-close="onCloseOrCancel_">
   <div slot="title" id="header">$i18n{destinationSearchTitle}</div>
diff --git a/chrome/browser/resources/print_preview/ui/destination_dialog.ts b/chrome/browser/resources/print_preview/ui/destination_dialog.ts
index 64ededd..02bd432 100644
--- a/chrome/browser/resources/print_preview/ui/destination_dialog.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_dialog.ts
@@ -9,13 +9,13 @@
 import 'chrome://resources/cr_elements/icons.m.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import '../print_preview_utils.js';
-import './destination_dialog_css.js';
+import './destination_dialog_style.css.js';
 import './destination_list.js';
 import './print_preview_search_box.js';
-import './print_preview_shared_css.js';
-import './print_preview_vars_css.js';
+import './print_preview_shared.css.js';
+import './print_preview_vars.css.js';
 import '../strings.m.js';
-import './throbber_css.js';
+import './throbber.css.js';
 import './destination_list_item.js';
 
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
diff --git a/chrome/browser/resources/print_preview/ui/destination_dialog_cros.html b/chrome/browser/resources/print_preview/ui/destination_dialog_cros.html
index ed93aeb..63483b0 100644
--- a/chrome/browser/resources/print_preview/ui/destination_dialog_cros.html
+++ b/chrome/browser/resources/print_preview/ui/destination_dialog_cros.html
@@ -1,4 +1,4 @@
-<style include="destination-dialog">
+<style include="destination-dialog-style">
   .form-row {
     align-items: center;
     column-gap: 18px;
diff --git a/chrome/browser/resources/print_preview/ui/destination_dialog_cros.ts b/chrome/browser/resources/print_preview/ui/destination_dialog_cros.ts
index a164805..fc24c23 100644
--- a/chrome/browser/resources/print_preview/ui/destination_dialog_cros.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_dialog_cros.ts
@@ -10,14 +10,14 @@
 import 'chrome://resources/cr_elements/icons.m.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import '../print_preview_utils.js';
-import './destination_dialog_css.js';
+import './destination_dialog_style.css.js';
 import './destination_list.js';
 import './print_preview_search_box.js';
-import './print_preview_shared_css.js';
-import './print_preview_vars_css.js';
+import './print_preview_shared.css.js';
+import './print_preview_vars.css.js';
 import './provisional_destination_resolver.js';
 import '../strings.m.js';
-import './throbber_css.js';
+import './throbber.css.js';
 import './destination_list_item_cros.js';
 
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
diff --git a/chrome/browser/resources/print_preview/ui/destination_dialog_css.html b/chrome/browser/resources/print_preview/ui/destination_dialog_css.html
deleted file mode 100644
index 3df915b..0000000
--- a/chrome/browser/resources/print_preview/ui/destination_dialog_css.html
+++ /dev/null
@@ -1,53 +0,0 @@
-<template>
-  <style include="print-preview-shared cr-hidden-style throbber">
-    :host-context([dir=rtl]) #manageIcon {
-      transform: scaleX(-1);
-    }
-
-    #dialog::part(dialog) {
-      height: calc(100vh - 2 * var(--print-preview-dialog-margin));
-      max-width: 640px;
-      width:  calc(100vw - 2 * var(--print-preview-dialog-margin));
-    }
-
-    #dialog::part(wrapper) {
-      height: calc(100vh - 2 * var(--print-preview-dialog-margin));
-    }
-
-    #dialog::part(body-container) {
-      flex: 1;
-    }
-
-    print-preview-search-box {
-      margin-bottom: 16px;
-      margin-top: 6px;
-    }
-
-    cr-dialog [slot=body] {
-      display: flex;
-      flex-direction: column;
-      height: 100%;
-    }
-
-    div[slot='button-container'] {
-      justify-content: space-between;
-    }
-
-    cr-button {
-      font-size: calc(12 / 13 * 1em);
-    }
-
-    .cancel-button {
-      margin-inline-end: 0;
-    }
-
-    cr-button iron-icon {
-      --iron-icon-fill-color: currentColor;
-      margin-inline-start: 8px;
-    }
-
-    #warning-message {
-      color: var(--cr-primary-text-color);
-    }
-  </style>
-</template>
diff --git a/chrome/browser/resources/print_preview/ui/destination_dialog_css.ts b/chrome/browser/resources/print_preview/ui/destination_dialog_css.ts
deleted file mode 100644
index 63ec60e..0000000
--- a/chrome/browser/resources/print_preview/ui/destination_dialog_css.ts
+++ /dev/null
@@ -1,12 +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.
-
-import 'chrome://resources/cr_elements/shared_style_css.m.js';
-import 'chrome://resources/cr_elements/shared_vars_css.m.js';
-
-import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-const styleMod = document.createElement('dom-module');
-styleMod.innerHTML = `{__html_template__}`;
-styleMod.register('destination-dialog');
diff --git a/chrome/browser/resources/print_preview/ui/destination_dialog_style.css b/chrome/browser/resources/print_preview/ui/destination_dialog_style.css
new file mode 100644
index 0000000..2130560d
--- /dev/null
+++ b/chrome/browser/resources/print_preview/ui/destination_dialog_style.css
@@ -0,0 +1,62 @@
+/* Copyright 2022 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. */
+
+/* #css_wrapper_metadata_start
+ * #type=style
+ * #import=chrome://resources/cr_elements/hidden_style_css.m.js
+ * #import=chrome://resources/cr_elements/shared_style_css.m.js
+ * #import=chrome://resources/cr_elements/shared_vars_css.m.js
+ * #import=./throbber.css.js
+ * #include=print-preview-shared cr-hidden-style throbber
+ * #css_wrapper_metadata_end */
+
+:host-context([dir=rtl]) #manageIcon {
+  transform: scaleX(-1);
+}
+
+#dialog::part(dialog) {
+  height: calc(100vh - 2 * var(--print-preview-dialog-margin));
+  max-width: 640px;
+  width:  calc(100vw - 2 * var(--print-preview-dialog-margin));
+}
+
+#dialog::part(wrapper) {
+  height: calc(100vh - 2 * var(--print-preview-dialog-margin));
+}
+
+#dialog::part(body-container) {
+  flex: 1;
+}
+
+print-preview-search-box {
+  margin-bottom: 16px;
+  margin-top: 6px;
+}
+
+cr-dialog [slot=body] {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+}
+
+div[slot='button-container'] {
+  justify-content: space-between;
+}
+
+cr-button {
+  font-size: calc(12 / 13 * 1em);
+}
+
+.cancel-button {
+  margin-inline-end: 0;
+}
+
+cr-button iron-icon {
+  --iron-icon-fill-color: currentColor;
+  margin-inline-start: 8px;
+}
+
+#warning-message {
+  color: var(--cr-primary-text-color);
+}
diff --git a/chrome/browser/resources/print_preview/ui/destination_dropdown_cros.ts b/chrome/browser/resources/print_preview/ui/destination_dropdown_cros.ts
index a30b514..a431cdf 100644
--- a/chrome/browser/resources/print_preview/ui/destination_dropdown_cros.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_dropdown_cros.ts
@@ -8,7 +8,7 @@
 import 'chrome://resources/polymer/v3_0/iron-dropdown/iron-dropdown.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import 'chrome://resources/polymer/v3_0/iron-media-query/iron-media-query.js';
-import './print_preview_vars_css.js';
+import './print_preview_vars.css.js';
 
 import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/print_preview/ui/destination_list.ts b/chrome/browser/resources/print_preview/ui/destination_list.ts
index 77087a7..5124dd9 100644
--- a/chrome/browser/resources/print_preview/ui/destination_list.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_list.ts
@@ -11,9 +11,9 @@
 // <if expr="chromeos_ash or chromeos_lacros">
 import './destination_list_item_cros.js';
 // </if>
-import './print_preview_vars_css.js';
+import './print_preview_vars.css.js';
 import '../strings.m.js';
-import './throbber_css.js';
+import './throbber.css.js';
 
 import {ListPropertyUpdateMixin} from 'chrome://resources/js/list_property_update_mixin.js';
 import {IronListElement} from 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
diff --git a/chrome/browser/resources/print_preview/ui/destination_list_item.ts b/chrome/browser/resources/print_preview/ui/destination_list_item.ts
index fe2f2b1..f0e97acb 100644
--- a/chrome/browser/resources/print_preview/ui/destination_list_item.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_list_item.ts
@@ -4,8 +4,8 @@
 
 import 'chrome://resources/cr_elements/icons.m.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
-import './icons.js';
-import './destination_list_item_css.js';
+import './icons.html.js';
+import './destination_list_item_style.css.js';
 import '../strings.m.js';
 
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
@@ -13,6 +13,7 @@
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Destination} from '../data/destination.js';
+
 import {getTemplate} from './destination_list_item.html.js';
 import {updateHighlights} from './highlight_utils.js';
 
diff --git a/chrome/browser/resources/print_preview/ui/destination_list_item_cros.ts b/chrome/browser/resources/print_preview/ui/destination_list_item_cros.ts
index 71093423..5f4f037 100644
--- a/chrome/browser/resources/print_preview/ui/destination_list_item_cros.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_list_item_cros.ts
@@ -6,8 +6,8 @@
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import 'chrome://resources/polymer/v3_0/iron-media-query/iron-media-query.js';
-import './destination_list_item_css.js';
-import './icons.js';
+import './destination_list_item_style.css.js';
+import './icons.html.js';
 import '../strings.m.js';
 
 import {assert} from 'chrome://resources/js/assert_ts.js';
@@ -16,8 +16,7 @@
 import {removeHighlights} from 'chrome://resources/js/search_highlight_utils.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
-import {Destination} from '../data/destination.js';
-import {DestinationOrigin} from '../data/destination.js';
+import {Destination, DestinationOrigin} from '../data/destination.js';
 import {ERROR_STRING_KEY_MAP, getPrinterStatusIcon, PrinterStatusReason} from '../data/printer_status_cros.js';
 
 import {getTemplate} from './destination_list_item_cros.html.js';
diff --git a/chrome/browser/resources/print_preview/ui/destination_list_item_css.html b/chrome/browser/resources/print_preview/ui/destination_list_item_css.html
deleted file mode 100644
index bac95c6..0000000
--- a/chrome/browser/resources/print_preview/ui/destination_list_item_css.html
+++ /dev/null
@@ -1,68 +0,0 @@
-<template>
-  <style include="cr-hidden-style">
-    :host {
-      align-items: center;
-      cursor: default;
-      display: flex;
-      font-size: calc(12/13 * 1em);
-      min-height: var(--destination-item-height);
-      opacity: .87;
-      padding-inline-end: 2px;
-      padding-inline-start: 0;
-      vertical-align: middle;
-    }
-
-    :host > * {
-      align-items: center;
-      color: var(--cr-secondary-text-color);
-      font-size: calc(10/12 * 1em);
-      overflow: hidden;
-      text-overflow: ellipsis;
-      vertical-align: middle;
-      white-space: nowrap;
-    }
-
-    :host > span {
-      margin-inline-start: 1em;
-    }
-
-    iron-icon {
-      --icon-margin: calc((var(--search-icon-size) - var(--iron-icon-width))/2);
-      fill: var(--google-grey-600);
-      flex: 0;
-      height: var(--iron-icon-height);
-      margin-inline-end: var(--icon-margin);
-      margin-inline-start: var(--icon-margin);
-      min-width: var(--iron-icon-width);
-      transition: opacity 150ms;
-    }
-
-    @media (prefers-color-scheme: dark) {
-      iron-icon {
-        fill: var(--google-grey-500);
-      }
-    }
-
-    :host .name {
-      color: var(--cr-primary-text-color);
-      font-size: 1em;
-      margin-inline-start: 0;
-      /* Matches cr-input-padding-start */
-      padding-inline-start: 8px;
-    }
-
-    .extension-controlled-indicator {
-      display: flex;
-      flex: 1;
-      justify-content: flex-end;
-      min-width: 150px;
-      padding-inline-end: 8px;
-    }
-
-    .extension-icon {
-      height: 24px;
-      margin-inline-start: 1em;
-      width: 24px;
-    }
-  </style>
-</template>
diff --git a/chrome/browser/resources/print_preview/ui/destination_list_item_css.ts b/chrome/browser/resources/print_preview/ui/destination_list_item_css.ts
deleted file mode 100644
index def9a2c7..0000000
--- a/chrome/browser/resources/print_preview/ui/destination_list_item_css.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright 2022 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 'chrome://resources/cr_elements/hidden_style_css.m.js';
-import 'chrome://resources/cr_elements/shared_style_css.m.js';
-import 'chrome://resources/cr_elements/shared_vars_css.m.js';
-
-import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import './print_preview_vars_css.js';
-
-const styleMod = document.createElement('dom-module');
-styleMod.innerHTML = `{__html_template__}`;
-styleMod.register('destination-list-item-style');
diff --git a/chrome/browser/resources/print_preview/ui/destination_list_item_style.css b/chrome/browser/resources/print_preview/ui/destination_list_item_style.css
new file mode 100644
index 0000000..f7bbd6e7
--- /dev/null
+++ b/chrome/browser/resources/print_preview/ui/destination_list_item_style.css
@@ -0,0 +1,76 @@
+/* Copyright 2022 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. */
+
+/* #css_wrapper_metadata_start
+ * #type=style
+ * #import=chrome://resources/cr_elements/hidden_style_css.m.js
+ * #import=chrome://resources/cr_elements/shared_style_css.m.js
+ * #import=chrome://resources/cr_elements/shared_vars_css.m.js
+ * #include=cr-hidden-style
+ * #css_wrapper_metadata_end */
+
+:host {
+  align-items: center;
+  cursor: default;
+  display: flex;
+  font-size: calc(12/13 * 1em);
+  min-height: var(--destination-item-height);
+  opacity: .87;
+  padding-inline-end: 2px;
+  padding-inline-start: 0;
+  vertical-align: middle;
+}
+
+:host > * {
+  align-items: center;
+  color: var(--cr-secondary-text-color);
+  font-size: calc(10/12 * 1em);
+  overflow: hidden;
+  text-overflow: ellipsis;
+  vertical-align: middle;
+  white-space: nowrap;
+}
+
+:host > span {
+  margin-inline-start: 1em;
+}
+
+iron-icon {
+  --icon-margin: calc((var(--search-icon-size) - var(--iron-icon-width))/2);
+  fill: var(--google-grey-600);
+  flex: 0;
+  height: var(--iron-icon-height);
+  margin-inline-end: var(--icon-margin);
+  margin-inline-start: var(--icon-margin);
+  min-width: var(--iron-icon-width);
+  transition: opacity 150ms;
+}
+
+@media (prefers-color-scheme: dark) {
+  iron-icon {
+    fill: var(--google-grey-500);
+  }
+}
+
+:host .name {
+  color: var(--cr-primary-text-color);
+  font-size: 1em;
+  margin-inline-start: 0;
+  /* Matches cr-input-padding-start */
+  padding-inline-start: 8px;
+}
+
+.extension-controlled-indicator {
+  display: flex;
+  flex: 1;
+  justify-content: flex-end;
+  min-width: 150px;
+  padding-inline-end: 8px;
+}
+
+.extension-icon {
+  height: 24px;
+  margin-inline-start: 1em;
+  width: 24px;
+}
diff --git a/chrome/browser/resources/print_preview/ui/destination_select.html b/chrome/browser/resources/print_preview/ui/destination_select.html
index 4136b468..1e10984 100644
--- a/chrome/browser/resources/print_preview/ui/destination_select.html
+++ b/chrome/browser/resources/print_preview/ui/destination_select.html
@@ -1,4 +1,4 @@
-<style include="print-preview-shared throbber md-select destination-select
+<style include="print-preview-shared throbber md-select destination-select-style
     cr-hidden-style">
 </style>
 <print-preview-settings-section>
diff --git a/chrome/browser/resources/print_preview/ui/destination_select.ts b/chrome/browser/resources/print_preview/ui/destination_select.ts
index 6de4bd7..91ab76c9 100644
--- a/chrome/browser/resources/print_preview/ui/destination_select.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_select.ts
@@ -13,10 +13,10 @@
 import 'chrome://resources/cr_elements/md_select_css.m.js';
 import 'chrome://resources/js/util.m.js';
 import 'chrome://resources/polymer/v3_0/iron-iconset-svg/iron-iconset-svg.js';
-import './destination_select_css.js';
-import './icons.js';
-import './print_preview_shared_css.js';
-import './throbber_css.js';
+import './destination_select_style.css.js';
+import './icons.html.js';
+import './print_preview_shared.css.js';
+import './throbber.css.js';
 import '../strings.m.js';
 
 import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
diff --git a/chrome/browser/resources/print_preview/ui/destination_select_cros.html b/chrome/browser/resources/print_preview/ui/destination_select_cros.html
index be0d1b7..3337f38c 100644
--- a/chrome/browser/resources/print_preview/ui/destination_select_cros.html
+++ b/chrome/browser/resources/print_preview/ui/destination_select_cros.html
@@ -1,4 +1,4 @@
-<style include="print-preview-shared throbber destination-select cr-hidden-style">
+<style include="print-preview-shared throbber destination-select-style cr-hidden-style">
   :host([is-current-destination-cros-local_]) #statusText {
     color: var(--google-red-600);
     font-size: calc(10 / 13 * 1em);
diff --git a/chrome/browser/resources/print_preview/ui/destination_select_cros.ts b/chrome/browser/resources/print_preview/ui/destination_select_cros.ts
index 56e9d17..70affd0 100644
--- a/chrome/browser/resources/print_preview/ui/destination_select_cros.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_select_cros.ts
@@ -9,10 +9,10 @@
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
 import 'chrome://resources/polymer/v3_0/iron-media-query/iron-media-query.js';
 import './destination_dropdown_cros.js';
-import './destination_select_css.js';
-import './icons.js';
-import './print_preview_shared_css.js';
-import './throbber_css.js';
+import './destination_select_style.css.js';
+import './icons.html.js';
+import './print_preview_shared.css.js';
+import './throbber.css.js';
 import '../strings.m.js';
 
 import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
diff --git a/chrome/browser/resources/print_preview/ui/destination_select_css.html b/chrome/browser/resources/print_preview/ui/destination_select_css.html
deleted file mode 100644
index 19fc6427..0000000
--- a/chrome/browser/resources/print_preview/ui/destination_select_css.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<template>
-  <style include="cr-shared-style">
-    :host {
-      --printer-icon-side-padding: 4px;
-      --printer-icon-size: 20px;
-    }
-
-    select.md-select {
-      background-position: var(--printer-icon-side-padding) center,
-          calc(100% - var(--md-select-side-padding)) center;
-      background-size: var(--printer-icon-size), var(--md-arrow-width);
-      padding-inline-start: 32px;
-    }
-
-    :host-context([dir=rtl]) .md-select {
-      background-position-x: calc(100% - var(--printer-icon-side-padding)),
-          var(--md-select-side-padding);
-    }
-
-    .throbber-container {
-      align-items: center;
-      display: flex;
-      overflow: hidden;
-    }
-
-    .destination-additional-info,
-    .destination-additional-info div {
-      height: 100%;
-      min-height: 0;
-    }
-
-    .destination-status {
-      color: var(--cr-secondary-text-color);
-      font-size: calc(12/13 * 1em);
-      padding: 4px 0;
-    }
-  </style>
-</template>
diff --git a/chrome/browser/resources/print_preview/ui/destination_select_css.ts b/chrome/browser/resources/print_preview/ui/destination_select_css.ts
deleted file mode 100644
index 114a912..0000000
--- a/chrome/browser/resources/print_preview/ui/destination_select_css.ts
+++ /dev/null
@@ -1,12 +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.
-
-import 'chrome://resources/cr_elements/shared_style_css.m.js';
-import 'chrome://resources/cr_elements/shared_vars_css.m.js';
-
-import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-const styleMod = document.createElement('dom-module');
-styleMod.innerHTML = `{__html_template__}`;
-styleMod.register('destination-select');
diff --git a/chrome/browser/resources/print_preview/ui/destination_select_style.css b/chrome/browser/resources/print_preview/ui/destination_select_style.css
new file mode 100644
index 0000000..1a15b1ed
--- /dev/null
+++ b/chrome/browser/resources/print_preview/ui/destination_select_style.css
@@ -0,0 +1,45 @@
+/* Copyright 2022 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. */
+
+/* #css_wrapper_metadata_start
+ * #type=style
+ * #import=chrome://resources/cr_elements/shared_style_css.m.js
+ * #import=chrome://resources/cr_elements/shared_vars_css.m.js
+ * #include=cr-shared-style
+ * #css_wrapper_metadata_end */
+
+:host {
+  --printer-icon-side-padding: 4px;
+  --printer-icon-size: 20px;
+}
+
+select.md-select {
+  background-position: var(--printer-icon-side-padding) center,
+      calc(100% - var(--md-select-side-padding)) center;
+  background-size: var(--printer-icon-size), var(--md-arrow-width);
+  padding-inline-start: 32px;
+}
+
+:host-context([dir=rtl]) .md-select {
+  background-position-x: calc(100% - var(--printer-icon-side-padding)),
+      var(--md-select-side-padding);
+}
+
+.throbber-container {
+  align-items: center;
+  display: flex;
+  overflow: hidden;
+}
+
+.destination-additional-info,
+.destination-additional-info div {
+  height: 100%;
+  min-height: 0;
+}
+
+.destination-status {
+  color: var(--cr-secondary-text-color);
+  font-size: calc(12/13 * 1em);
+  padding: 4px 0;
+}
diff --git a/chrome/browser/resources/print_preview/ui/destination_settings.ts b/chrome/browser/resources/print_preview/ui/destination_settings.ts
index 8d8ca29f..fcb216dc 100644
--- a/chrome/browser/resources/print_preview/ui/destination_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/destination_settings.ts
@@ -17,9 +17,9 @@
 // <if expr="chromeos_ash or chromeos_lacros">
 import './destination_select_cros.js';
 // </if>
-import './print_preview_shared_css.js';
-import './print_preview_vars_css.js';
-import './throbber_css.js';
+import './print_preview_shared.css.js';
+import './print_preview_vars.css.js';
+import './throbber.css.js';
 import './settings_section.js';
 import '../strings.m.js';
 
diff --git a/chrome/browser/resources/print_preview/ui/dpi_settings.ts b/chrome/browser/resources/print_preview/ui/dpi_settings.ts
index d1eab8f..a8930b9 100644
--- a/chrome/browser/resources/print_preview/ui/dpi_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/dpi_settings.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 './print_preview_shared_css.js';
+import './print_preview_shared.css.js';
 import './settings_section.js';
 import '../strings.m.js';
 import './settings_select.js';
diff --git a/chrome/browser/resources/print_preview/ui/duplex_settings.ts b/chrome/browser/resources/print_preview/ui/duplex_settings.ts
index b8beaff..e4aee956 100644
--- a/chrome/browser/resources/print_preview/ui/duplex_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/duplex_settings.ts
@@ -7,8 +7,8 @@
 import 'chrome://resources/cr_elements/md_select_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-iconset-svg/iron-iconset-svg.js';
 import 'chrome://resources/polymer/v3_0/iron-meta/iron-meta.js';
-import './icons.js';
-import './print_preview_shared_css.js';
+import './icons.html.js';
+import './print_preview_shared.css.js';
 import './settings_section.js';
 
 import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
diff --git a/chrome/browser/resources/print_preview/ui/header.ts b/chrome/browser/resources/print_preview/ui/header.ts
index 15bff00..457c208f 100644
--- a/chrome/browser/resources/print_preview/ui/header.ts
+++ b/chrome/browser/resources/print_preview/ui/header.ts
@@ -4,8 +4,8 @@
 
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
-import './icons.js';
-import './print_preview_vars_css.js';
+import './icons.html.js';
+import './print_preview_vars.css.js';
 import '../strings.m.js';
 
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
diff --git a/chrome/browser/resources/print_preview/ui/icons.ts b/chrome/browser/resources/print_preview/ui/icons.ts
deleted file mode 100644
index 500ccf6..0000000
--- a/chrome/browser/resources/print_preview/ui/icons.ts
+++ /dev/null
@@ -1,10 +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.
-
-import 'chrome://resources/polymer/v3_0/iron-iconset-svg/iron-iconset-svg.js';
-
-import {html} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-const template = html`{__html_template__}`;
-document.head.appendChild(template.content);
diff --git a/chrome/browser/resources/print_preview/ui/layout_settings.ts b/chrome/browser/resources/print_preview/ui/layout_settings.ts
index bc6345b..8622334c 100644
--- a/chrome/browser/resources/print_preview/ui/layout_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/layout_settings.ts
@@ -3,12 +3,12 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/md_select_css.m.js';
-import './print_preview_shared_css.js';
+import './print_preview_shared.css.js';
 import './settings_section.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {getTemplate} from './layout_settings.html.js';
 
+import {getTemplate} from './layout_settings.html.js';
 import {SelectMixin} from './select_mixin.js';
 import {SettingsMixin} from './settings_mixin.js';
 
diff --git a/chrome/browser/resources/print_preview/ui/link_container.ts b/chrome/browser/resources/print_preview/ui/link_container.ts
index 6d22770..318990c 100644
--- a/chrome/browser/resources/print_preview/ui/link_container.ts
+++ b/chrome/browser/resources/print_preview/ui/link_container.ts
@@ -5,8 +5,8 @@
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
 import 'chrome://resources/cr_elements/hidden_style_css.m.js';
 import 'chrome://resources/cr_elements/shared_style_css.m.js';
-import './print_preview_vars_css.js';
-import './throbber_css.js';
+import './print_preview_vars.css.js';
+import './throbber.css.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
diff --git a/chrome/browser/resources/print_preview/ui/margins_settings.ts b/chrome/browser/resources/print_preview/ui/margins_settings.ts
index c5d8c02..223a42b 100644
--- a/chrome/browser/resources/print_preview/ui/margins_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/margins_settings.ts
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/md_select_css.m.js';
-import './print_preview_shared_css.js';
+import './print_preview_shared.css.js';
 import './settings_section.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/print_preview/ui/media_size_settings.ts b/chrome/browser/resources/print_preview/ui/media_size_settings.ts
index 90653e5..46f5d1e8 100644
--- a/chrome/browser/resources/print_preview/ui/media_size_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/media_size_settings.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 './print_preview_shared_css.js';
+import './print_preview_shared.css.js';
 import './settings_section.js';
 import './settings_select.js';
 
diff --git a/chrome/browser/resources/print_preview/ui/number_settings_section.ts b/chrome/browser/resources/print_preview/ui/number_settings_section.ts
index a1a4fcec..0950ee5 100644
--- a/chrome/browser/resources/print_preview/ui/number_settings_section.ts
+++ b/chrome/browser/resources/print_preview/ui/number_settings_section.ts
@@ -3,8 +3,8 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
-import './print_preview_shared_css.js';
-import './print_preview_vars_css.js';
+import './print_preview_shared.css.js';
+import './print_preview_vars.css.js';
 import './settings_section.js';
 
 import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
diff --git a/chrome/browser/resources/print_preview/ui/other_options_settings.ts b/chrome/browser/resources/print_preview/ui/other_options_settings.ts
index bca0594..bd0ec6b9 100644
--- a/chrome/browser/resources/print_preview/ui/other_options_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/other_options_settings.ts
@@ -4,15 +4,15 @@
 
 import 'chrome://resources/cr_elements/hidden_style_css.m.js';
 import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
-import './print_preview_shared_css.js';
+import './print_preview_shared.css.js';
 import './settings_section.js';
 import '../strings.m.js';
 
 import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
 import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
 import {DomRepeatEvent, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {getTemplate} from './other_options_settings.html.js';
 
+import {getTemplate} from './other_options_settings.html.js';
 import {SettingsMixin} from './settings_mixin.js';
 
 type CheckboxOption = {
diff --git a/chrome/browser/resources/print_preview/ui/pages_per_sheet_settings.ts b/chrome/browser/resources/print_preview/ui/pages_per_sheet_settings.ts
index 44eef11..906cd7d 100644
--- a/chrome/browser/resources/print_preview/ui/pages_per_sheet_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/pages_per_sheet_settings.ts
@@ -3,12 +3,12 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/md_select_css.m.js';
-import './print_preview_shared_css.js';
+import './print_preview_shared.css.js';
 import './settings_section.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {getTemplate} from './pages_per_sheet_settings.html.js';
 
+import {getTemplate} from './pages_per_sheet_settings.html.js';
 import {SelectMixin} from './select_mixin.js';
 import {SettingsMixin} from './settings_mixin.js';
 
diff --git a/chrome/browser/resources/print_preview/ui/pages_settings.ts b/chrome/browser/resources/print_preview/ui/pages_settings.ts
index 24f0d9e..8ed6736 100644
--- a/chrome/browser/resources/print_preview/ui/pages_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/pages_settings.ts
@@ -5,7 +5,7 @@
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import 'chrome://resources/cr_elements/md_select_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-collapse/iron-collapse.js';
-import './print_preview_shared_css.js';
+import './print_preview_shared.css.js';
 import './settings_section.js';
 import '../strings.m.js';
 
diff --git a/chrome/browser/resources/print_preview/ui/pin_settings.ts b/chrome/browser/resources/print_preview/ui/pin_settings.ts
index 7e098c2..40ac96f 100644
--- a/chrome/browser/resources/print_preview/ui/pin_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/pin_settings.ts
@@ -5,7 +5,7 @@
 import 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import 'chrome://resources/polymer/v3_0/iron-collapse/iron-collapse.js';
-import './print_preview_shared_css.js';
+import './print_preview_shared.css.js';
 import './settings_section.js';
 
 import {CrCheckboxElement} from 'chrome://resources/cr_elements/cr_checkbox/cr_checkbox.m.js';
diff --git a/chrome/browser/resources/print_preview/ui/preview_area.ts b/chrome/browser/resources/print_preview/ui/preview_area.ts
index 1f27872..00877b5 100644
--- a/chrome/browser/resources/print_preview/ui/preview_area.ts
+++ b/chrome/browser/resources/print_preview/ui/preview_area.ts
@@ -4,7 +4,7 @@
 
 import 'chrome://resources/cr_elements/hidden_style_css.m.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
-import './print_preview_vars_css.js';
+import './print_preview_vars.css.js';
 import '../strings.m.js';
 
 import {I18nMixin} from 'chrome://resources/js/i18n_mixin.js';
diff --git a/chrome/browser/resources/print_preview/ui/print_preview_search_box.ts b/chrome/browser/resources/print_preview/ui/print_preview_search_box.ts
index 2ef74ea..8e225fd3 100644
--- a/chrome/browser/resources/print_preview/ui/print_preview_search_box.ts
+++ b/chrome/browser/resources/print_preview/ui/print_preview_search_box.ts
@@ -6,13 +6,14 @@
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
 import 'chrome://resources/cr_elements/cr_icon_button/cr_icon_button.m.js';
 import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
-import './print_preview_shared_css.js';
+import './print_preview_shared.css.js';
 
 import {CrInputElement} from 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
 import {CrSearchFieldMixin} from 'chrome://resources/cr_elements/cr_search_field/cr_search_field_mixin.js';
 import {stripDiacritics} from 'chrome://resources/js/search_highlight_utils.js';
 import {WebUIListenerMixin} from 'chrome://resources/js/web_ui_listener_mixin.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 import {getTemplate} from './print_preview_search_box.html.js';
 
 declare global {
diff --git a/chrome/browser/resources/print_preview/ui/print_preview_shared.css b/chrome/browser/resources/print_preview/ui/print_preview_shared.css
new file mode 100644
index 0000000..23dbc2c7
--- /dev/null
+++ b/chrome/browser/resources/print_preview/ui/print_preview_shared.css
@@ -0,0 +1,74 @@
+/* Copyright 2022 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. */
+
+/* #css_wrapper_metadata_start
+ * #type=style
+ * #import=chrome://resources/cr_elements/shared_style_css.m.js
+ * #import=chrome://resources/cr_elements/shared_vars_css.m.js
+ * #import=./print_preview_vars.css.js
+ * #include=cr-shared-style
+ * #css_wrapper_metadata_end */
+
+/* Default state ********************************************************/
+select.md-select {
+  margin-bottom: 2px;
+  margin-top: 2px;
+  min-height: 32px;
+  padding-bottom: 1px;
+<if expr="is_win or is_macosx">
+  /* The following platform-specific rule is necessary to get adjacent
+   * buttons, text inputs, and so forth to align on their borders while
+   * also aligning on the text's baselines. */
+  padding-bottom: 2px;
+</if>
+  padding-inline-end: 32px;
+  padding-top: 1px;
+  user-select: none;
+  --md-select-width: calc(100% - 2 * var(--print-preview-sidebar-margin));
+}
+
+.checkbox cr-checkbox {
+  min-height: var(--print-preview-row-height);
+  --cr-checkbox-ripple-size: var(--print-preview-row-height);
+}
+
+.checkbox cr-checkbox::part(label-container) {
+  overflow: hidden;
+  padding-inline-start: 16px;
+}
+
+cr-input {
+  line-height: 20px;
+}
+
+cr-input::part(row-container) {
+  min-height: var(--print-preview-row-height);
+}
+
+print-preview-settings-section [slot=controls] > * {
+  margin: 0 var(--print-preview-sidebar-margin);
+}
+
+/* Default print preview dialog styles. */
+cr-dialog::part(wrapper) {
+  max-height: calc(100vh - 68px);
+  max-width: 100%;
+  width:  calc(100vw - 68px);
+}
+
+cr-dialog [slot=body] {
+  box-sizing: border-box;
+}
+
+#dialog div[slot='title'] {
+  padding-bottom: 8px;
+}
+
+#dialog div[slot='button-container'] {
+  align-items: center;
+  box-shadow: 0 -1px 1px 0 rgba(var(--cr-card-shadow-color-rgb), 0.3);
+  min-height: 64px;
+  padding-bottom: 0;
+  padding-top: 0;
+}
diff --git a/chrome/browser/resources/print_preview/ui/print_preview_shared_css.html b/chrome/browser/resources/print_preview/ui/print_preview_shared_css.html
deleted file mode 100644
index cee1e46..0000000
--- a/chrome/browser/resources/print_preview/ui/print_preview_shared_css.html
+++ /dev/null
@@ -1,66 +0,0 @@
-<template>
-  <style include="cr-shared-style">
-    /* Default state ********************************************************/
-    select.md-select {
-      margin-bottom: 2px;
-      margin-top: 2px;
-      min-height: 32px;
-      padding-bottom: 1px;
-    <if expr="is_win or is_macosx">
-      /* The following platform-specific rule is necessary to get adjacent
-       * buttons, text inputs, and so forth to align on their borders while
-       * also aligning on the text's baselines. */
-      padding-bottom: 2px;
-    </if>
-      padding-inline-end: 32px;
-      padding-top: 1px;
-      user-select: none;
-      --md-select-width: calc(100% - 2 * var(--print-preview-sidebar-margin));
-    }
-
-    .checkbox cr-checkbox {
-      min-height: var(--print-preview-row-height);
-      --cr-checkbox-ripple-size: var(--print-preview-row-height);
-    }
-
-    .checkbox cr-checkbox::part(label-container) {
-      overflow: hidden;
-      padding-inline-start: 16px;
-    }
-
-    cr-input {
-      line-height: 20px;
-    }
-
-    cr-input::part(row-container) {
-      min-height: var(--print-preview-row-height);
-    }
-
-    print-preview-settings-section [slot=controls] > * {
-      margin: 0 var(--print-preview-sidebar-margin);
-    }
-
-    /* Default print preview dialog styles. */
-    cr-dialog::part(wrapper) {
-      max-height: calc(100vh - 68px);
-      max-width: 100%;
-      width:  calc(100vw - 68px);
-    }
-
-    cr-dialog [slot=body] {
-      box-sizing: border-box;
-    }
-
-    #dialog div[slot='title'] {
-      padding-bottom: 8px;
-    }
-
-    #dialog div[slot='button-container'] {
-      align-items: center;
-      box-shadow: 0 -1px 1px 0 rgba(var(--cr-card-shadow-color-rgb), 0.3);
-      min-height: 64px;
-      padding-bottom: 0;
-      padding-top: 0;
-    }
-  </style>
-</template>
diff --git a/chrome/browser/resources/print_preview/ui/print_preview_shared_css.ts b/chrome/browser/resources/print_preview/ui/print_preview_shared_css.ts
deleted file mode 100644
index 9552d3b5..0000000
--- a/chrome/browser/resources/print_preview/ui/print_preview_shared_css.ts
+++ /dev/null
@@ -1,13 +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.
-
-import 'chrome://resources/cr_elements/shared_style_css.m.js';
-import 'chrome://resources/cr_elements/shared_vars_css.m.js';
-import './print_preview_vars_css.js';
-
-import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-const styleMod = document.createElement('dom-module');
-styleMod.innerHTML = `{__html_template__}`;
-styleMod.register('print-preview-shared');
diff --git a/chrome/browser/resources/print_preview/ui/print_preview_vars.css b/chrome/browser/resources/print_preview/ui/print_preview_vars.css
new file mode 100644
index 0000000..7bedab8
--- /dev/null
+++ b/chrome/browser/resources/print_preview/ui/print_preview_vars.css
@@ -0,0 +1,39 @@
+/* Copyright 2022 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. */
+
+/* #css_wrapper_metadata_start
+ * #type=vars
+ * #import=chrome://resources/cr_elements/shared_vars_css.m.js
+ * #css_wrapper_metadata_end */
+
+html {
+  --print-preview-row-height: 38px;
+  --print-preview-sidebar-width: 384px;
+  --print-preview-title-width: 120px;
+  --print-preview-sidebar-margin: 24px;
+  /* Controls width = total width - title width - start/middle/end margin */
+  --print-preview-dropdown-width: calc(var(--print-preview-sidebar-width)
+      - var(--print-preview-title-width)
+      - 3 * var(--print-preview-sidebar-margin));
+
+  --print-preview-settings-border: 1px solid var(--google-grey-200);
+  --print-preview-dialog-margin: 34px;
+  --cr-form-field-label-height: initial;
+  --cr-form-field-label-line-height: .75rem;
+  --destination-item-height: 32px;
+  --preview-area-background-color: var(--google-grey-300);
+  --iron-icon-fill-color: var(--google-grey-700);
+  --iron-icon-height: var(--cr-icon-size);
+  --iron-icon-width: var(--cr-icon-size);
+  --search-icon-size: 32px;
+  --throbber-size: 16px;
+}
+
+@media (prefers-color-scheme: dark) {
+  html {
+    --preview-area-background-color: var(--google-grey-700);
+    --print-preview-settings-border: var(--cr-separator-line);
+    --iron-icon-fill-color: var(--google-grey-500);
+  }
+}
diff --git a/chrome/browser/resources/print_preview/ui/print_preview_vars_css.html b/chrome/browser/resources/print_preview/ui/print_preview_vars_css.html
deleted file mode 100644
index f720871a..0000000
--- a/chrome/browser/resources/print_preview/ui/print_preview_vars_css.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<custom-style>
-<style>
-  html {
-    --print-preview-row-height: 38px;
-    --print-preview-sidebar-width: 384px;
-    --print-preview-title-width: 120px;
-    --print-preview-sidebar-margin: 24px;
-    /* Controls width = total width - title width - start/middle/end margin */
-    --print-preview-dropdown-width: calc(var(--print-preview-sidebar-width)
-        - var(--print-preview-title-width)
-        - 3 * var(--print-preview-sidebar-margin));
-
-    --print-preview-settings-border: 1px solid var(--google-grey-200);
-    --print-preview-dialog-margin: 34px;
-    --cr-form-field-label-height: initial;
-    --cr-form-field-label-line-height: .75rem;
-    --destination-item-height: 32px;
-    --preview-area-background-color: var(--google-grey-300);
-    --iron-icon-fill-color: var(--google-grey-700);
-    --iron-icon-height: var(--cr-icon-size);
-    --iron-icon-width: var(--cr-icon-size);
-    --search-icon-size: 32px;
-    --throbber-size: 16px;
-  }
-
-  @media (prefers-color-scheme: dark) {
-    html {
-      --preview-area-background-color: var(--google-grey-700);
-      --print-preview-settings-border: var(--cr-separator-line);
-      --iron-icon-fill-color: var(--google-grey-500);
-    }
-  }
-</style>
-</custom-style>
diff --git a/chrome/browser/resources/print_preview/ui/print_preview_vars_css.ts b/chrome/browser/resources/print_preview/ui/print_preview_vars_css.ts
deleted file mode 100644
index 31d9a77..0000000
--- a/chrome/browser/resources/print_preview/ui/print_preview_vars_css.ts
+++ /dev/null
@@ -1,11 +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.
-
-import 'chrome://resources/cr_elements/shared_vars_css.m.js';
-
-import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-
-const $_documentContainer = document.createElement('template');
-$_documentContainer.innerHTML = `{__html_template__}`;
-document.head.appendChild($_documentContainer.content);
diff --git a/chrome/browser/resources/print_preview/ui/provisional_destination_resolver.ts b/chrome/browser/resources/print_preview/ui/provisional_destination_resolver.ts
index 7f08a50..20d5c5e 100644
--- a/chrome/browser/resources/print_preview/ui/provisional_destination_resolver.ts
+++ b/chrome/browser/resources/print_preview/ui/provisional_destination_resolver.ts
@@ -6,10 +6,10 @@
 import 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import 'chrome://resources/cr_elements/hidden_style_css.m.js';
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
-import './print_preview_shared_css.js';
-import './print_preview_vars_css.js';
+import './print_preview_shared.css.js';
+import './print_preview_vars.css.js';
 import '../strings.m.js';
-import './throbber_css.js';
+import './throbber.css.js';
 
 import {CrDialogElement} from 'chrome://resources/cr_elements/cr_dialog/cr_dialog.m.js';
 import {assert} from 'chrome://resources/js/assert_ts.js';
diff --git a/chrome/browser/resources/print_preview/ui/scaling_settings.ts b/chrome/browser/resources/print_preview/ui/scaling_settings.ts
index 364ad36..abc53e4 100644
--- a/chrome/browser/resources/print_preview/ui/scaling_settings.ts
+++ b/chrome/browser/resources/print_preview/ui/scaling_settings.ts
@@ -5,7 +5,7 @@
 import 'chrome://resources/cr_elements/md_select_css.m.js';
 import 'chrome://resources/polymer/v3_0/iron-collapse/iron-collapse.js';
 import './number_settings_section.js';
-import './print_preview_shared_css.js';
+import './print_preview_shared.css.js';
 import './settings_section.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/print_preview/ui/settings_section.ts b/chrome/browser/resources/print_preview/ui/settings_section.ts
index 424f7fd..bf55541b 100644
--- a/chrome/browser/resources/print_preview/ui/settings_section.ts
+++ b/chrome/browser/resources/print_preview/ui/settings_section.ts
@@ -3,9 +3,10 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/shared_vars_css.m.js';
-import './print_preview_shared_css.js';
+import './print_preview_shared.css.js';
 
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 import {getTemplate} from './settings_section.html.js';
 
 export class PrintPreviewSettingsSectionElement extends PolymerElement {
diff --git a/chrome/browser/resources/print_preview/ui/settings_select.ts b/chrome/browser/resources/print_preview/ui/settings_select.ts
index f8c7f2c3..ccea4f0 100644
--- a/chrome/browser/resources/print_preview/ui/settings_select.ts
+++ b/chrome/browser/resources/print_preview/ui/settings_select.ts
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 import 'chrome://resources/cr_elements/md_select_css.m.js';
-import './print_preview_shared_css.js';
+import './print_preview_shared.css.js';
 
 import {assertNotReached} from 'chrome://resources/js/assert_ts.js';
 import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
diff --git a/chrome/browser/resources/print_preview/ui/sidebar.ts b/chrome/browser/resources/print_preview/ui/sidebar.ts
index a5c2779..f9c156a 100644
--- a/chrome/browser/resources/print_preview/ui/sidebar.ts
+++ b/chrome/browser/resources/print_preview/ui/sidebar.ts
@@ -23,7 +23,7 @@
 // <if expr="chromeos_ash or chromeos_lacros">
 import './pin_settings.js';
 // </if>
-import './print_preview_vars_css.js';
+import './print_preview_vars.css.js';
 import './scaling_settings.js';
 import '../strings.m.js';
 // <if expr="not chromeos_ash and not chromeos_lacros">
diff --git a/chrome/browser/resources/print_preview/ui/throbber.css b/chrome/browser/resources/print_preview/ui/throbber.css
new file mode 100644
index 0000000..0a66cc6
--- /dev/null
+++ b/chrome/browser/resources/print_preview/ui/throbber.css
@@ -0,0 +1,15 @@
+/* Copyright 2022 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. */
+
+/* #css_wrapper_metadata_start
+ * #type=style
+ * #import=./print_preview_vars.css.js
+ * #css_wrapper_metadata_end */
+
+.throbber {
+  background: url(chrome://resources/images/throbber_small.svg) no-repeat;
+  display: inline-block;
+  height: var(--throbber-size);
+  width: var(--throbber-size);
+}
diff --git a/chrome/browser/resources/print_preview/ui/throbber_css.html b/chrome/browser/resources/print_preview/ui/throbber_css.html
deleted file mode 100644
index 466502f9..0000000
--- a/chrome/browser/resources/print_preview/ui/throbber_css.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<template>
-  <style>
-    .throbber {
-      background: url(chrome://resources/images/throbber_small.svg) no-repeat;
-      display: inline-block;
-      height: var(--throbber-size);
-      width: var(--throbber-size);
-    }
-  </style>
-</template>
diff --git a/chrome/browser/resources/print_preview/ui/throbber_css.ts b/chrome/browser/resources/print_preview/ui/throbber_css.ts
deleted file mode 100644
index fcf11f4..0000000
--- a/chrome/browser/resources/print_preview/ui/throbber_css.ts
+++ /dev/null
@@ -1,10 +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.
-
-import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import './print_preview_vars_css.js';
-
-const styleMod = document.createElement('dom-module');
-styleMod.innerHTML = `{__html_template__}`;
-styleMod.register('throbber');
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_extra_containers.html b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_extra_containers.html
index 75f88f4..91ebc930 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_extra_containers.html
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_extra_containers.html
@@ -102,6 +102,13 @@
           on-click="onDeleteContainerClick_">
         $i18n{crostiniExtraContainersDelete}
       </button>
+      <button id="exportContainerButton"
+          class="dropdown-item"
+          role="menuitem"
+          on-click="onExportContainerClick_"
+          disabled="[[!enableButtons_]]">
+          $i18n{crostiniExport}
+      </button>
     </cr-action-menu>
   </template>
 </cr-lazy-render>
diff --git a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_extra_containers.js b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_extra_containers.js
index 8b0eaee..1996077 100644
--- a/chrome/browser/resources/settings/chromeos/crostini_page/crostini_extra_containers.js
+++ b/chrome/browser/resources/settings/chromeos/crostini_page/crostini_extra_containers.js
@@ -58,6 +58,31 @@
       lastMenuContainerInfo_: {
         type: Object,
       },
+
+      /**
+       * Whether the export import buttons should be enabled. Initially false
+       * until status has been confirmed.
+       * @private {boolean}
+       */
+      enableButtons_: {
+        type: Boolean,
+        computed:
+            'isEnabledButtons_(installerShowing_, exportImportInProgress_)',
+      },
+
+      /** @private */
+      installerShowing_: {
+        type: Boolean,
+        value: false,
+      },
+
+      // TODO(b/231890242): Disable delete and stop buttons when a container is
+      // being exported or imported.
+      /** @private */
+      exportImportInProgress_: {
+        type: Boolean,
+        value: false,
+      },
     };
   }
 
@@ -78,6 +103,23 @@
     CrostiniBrowserProxyImpl.getInstance().requestContainerInfo();
   }
 
+  /** @override */
+  connectedCallback() {
+    super.connectedCallback();
+    this.addWebUIListener(
+        'crostini-export-import-operation-status-changed', inProgress => {
+          this.exportImportInProgress_ = inProgress;
+        });
+    this.addWebUIListener(
+        'crostini-installer-status-changed', installerShowing => {
+          this.installerShowing_ = installerShowing;
+        });
+
+    CrostiniBrowserProxyImpl.getInstance()
+        .requestCrostiniExportImportOperationStatus();
+    CrostiniBrowserProxyImpl.getInstance().requestCrostiniInstallerStatus();
+  }
+
   /**
    * @param {!Array<!ContainerInfo>} containerInfos
    */
@@ -142,6 +184,18 @@
    * @param {!Event} event
    * @private
    */
+  onExportContainerClick_(event) {
+    if (this.lastMenuContainerInfo_) {
+      CrostiniBrowserProxyImpl.getInstance().exportCrostiniContainer(
+          this.lastMenuContainerInfo_.id);
+    }
+    this.closeContainerMenu_();
+  }
+
+  /**
+   * @param {!Event} event
+   * @private
+   */
   onContainerColorChange_(event) {
     const containerId =
         /** @type {ContainerId} */ (event.currentTarget['dataContainerId']);
@@ -184,6 +238,15 @@
     menu.close();
     this.lastMenuContainerInfo_ = null;
   }
+
+  /**
+   * @param {!Boolean} installerShowing
+   * @param {!Boolean} exportImportInProgress
+   * @private
+   */
+  isEnabledButtons_(installerShowing, exportImportInProgress) {
+    return !(installerShowing || exportImportInProgress);
+  }
 }
 
 customElements.define(ExtraContainersElement.is, ExtraContainersElement);
diff --git a/chrome/browser/resources/webui_js_error/BUILD.gn b/chrome/browser/resources/webui_js_error/BUILD.gn
index 0b2172a..36b2ee1d 100644
--- a/chrome/browser/resources/webui_js_error/BUILD.gn
+++ b/chrome/browser/resources/webui_js_error/BUILD.gn
@@ -4,24 +4,22 @@
 
 import("//chrome/browser/resources/tools/optimize_webui.gni")
 import("//chrome/common/features.gni")
-import("//third_party/closure_compiler/compile_js.gni")
 import("//tools/grit/grit_rule.gni")
 import("//tools/grit/preprocess_if_expr.gni")
+import("//tools/typescript/ts_library.gni")
 import("//ui/webui/resources/tools/generate_grd.gni")
 import("//ui/webui/webui_features.gni")
 
+assert(is_linux || is_chromeos)
+
 preprocess_folder = "preprocessed"
-preprocess_manifest = "preprocessed_manifest.json"
 
 if (optimize_webui) {
   build_manifest = "build_manifest.json"
   optimize_webui("build") {
     host = "webuijserror"
-    input = rebase_path("$target_gen_dir/$preprocess_folder", root_build_dir)
-    deps = [
-      ":preprocess",
-      "../../../../ui/webui/resources/js:preprocess",
-    ]
+    input = rebase_path("$target_gen_dir/tsc", root_build_dir)
+    deps = [ ":build_ts" ]
     js_module_in_files = [ "webui_js_error.js" ]
     js_out_files = [ "webui_js_error.rollup.js" ]
     out_manifest = "$target_gen_dir/$build_manifest"
@@ -29,10 +27,9 @@
 }
 
 preprocess_if_expr("preprocess") {
-  in_folder = "./"
+  in_folder = "."
   out_folder = "$target_gen_dir/$preprocess_folder"
-  out_manifest = "$target_gen_dir/$preprocess_manifest"
-  in_files = [ "webui_js_error.js" ]
+  in_files = [ "webui_js_error.ts" ]
 }
 
 generate_grd("build_grd") {
@@ -43,8 +40,8 @@
     resource_path_rewrites = [ "webui_js_error.rollup.js|webui_js_error.js" ]
     manifest_files = [ "$target_gen_dir/$build_manifest" ]
   } else {
-    deps = [ ":preprocess" ]
-    manifest_files = [ "$target_gen_dir/$preprocess_manifest" ]
+    deps = [ ":build_ts" ]
+    manifest_files = [ "$target_gen_dir/tsconfig.manifest" ]
   }
   grd_prefix = "webui_js_error"
   out_grd = "$target_gen_dir/${grd_prefix}_resources.grd"
@@ -67,10 +64,10 @@
   output_dir = "$root_gen_dir/chrome"
 }
 
-js_type_check("closure_compile") {
-  deps = [ ":webui_js_error" ]
-}
-
-js_library("webui_js_error") {
-  deps = [ "//ui/webui/resources/js:util.m" ]
+ts_library("build_ts") {
+  root_dir = "$target_gen_dir/$preprocess_folder"
+  out_dir = "$target_gen_dir/tsc"
+  in_files = [ "webui_js_error.ts" ]
+  deps = [ "//ui/webui/resources:library" ]
+  extra_deps = [ ":preprocess" ]
 }
diff --git a/chrome/browser/resources/webui_js_error/webui_js_error.js b/chrome/browser/resources/webui_js_error/webui_js_error.ts
similarity index 91%
rename from chrome/browser/resources/webui_js_error/webui_js_error.js
rename to chrome/browser/resources/webui_js_error/webui_js_error.ts
index 7c5064ed..6ae515c4 100644
--- a/chrome/browser/resources/webui_js_error/webui_js_error.js
+++ b/chrome/browser/resources/webui_js_error/webui_js_error.ts
@@ -65,17 +65,11 @@
 }
 
 /**
- * Promise executor. Always rejects the promise.
- */
-function promiseRejector(resolve, reject) {
-  reject('WebUI JS Error: The rejector always rejects!');
-}
-
-/**
  * Creates a promise which will be rejected and doesn't handle the rejection.
  */
 function unhandledPromiseRejection() {
-  const promise = new Promise(promiseRejector);
+  const promise =
+      Promise.reject('WebUI JS Error: The rejector always rejects!');
   promise.then(promiseSuccessful);
 }
 
diff --git a/chrome/browser/safe_browsing/download_protection/download_protection_observer.cc b/chrome/browser/safe_browsing/download_protection/download_protection_observer.cc
index 83596c8..e67a572 100644
--- a/chrome/browser/safe_browsing/download_protection/download_protection_observer.cc
+++ b/chrome/browser/safe_browsing/download_protection/download_protection_observer.cc
@@ -122,7 +122,7 @@
           metadata.mime_type,
           extensions::SafeBrowsingPrivateEventRouter::kTriggerFileDownload,
           DeepScanAccessPoint::DOWNLOAD, metadata.size, metadata.scan_response,
-          /*user_justification=*/absl::nullopt);
+          stored_result->user_justification);
     }
   } else {
     std::string raw_digest_sha256 = download->GetHash();
diff --git a/chrome/browser/safe_browsing/download_protection/file_analyzer.cc b/chrome/browser/safe_browsing/download_protection/file_analyzer.cc
index 92363ee..a3944937 100644
--- a/chrome/browser/safe_browsing/download_protection/file_analyzer.cc
+++ b/chrome/browser/safe_browsing/download_protection/file_analyzer.cc
@@ -154,7 +154,7 @@
 
 void FileAnalyzer::OnZipAnalysisFinished(
     const ArchiveAnalyzerResults& archive_results) {
-  base::UmaHistogramEnumeration("SBClientDownload.ZipArchiveNotValidReason",
+  base::UmaHistogramEnumeration("SBClientDownload.ZipArchiveAnalysisResult",
                                 archive_results.analysis_result);
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
@@ -202,7 +202,7 @@
 void FileAnalyzer::OnRarAnalysisFinished(
     const ArchiveAnalyzerResults& archive_results) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  base::UmaHistogramEnumeration("SBClientDownload.RarArchiveNotValidReason",
+  base::UmaHistogramEnumeration("SBClientDownload.RarArchiveAnalysisResult",
                                 archive_results.analysis_result);
 
   results_.archive_is_valid =
@@ -260,7 +260,7 @@
 void FileAnalyzer::OnDmgAnalysisFinished(
     const safe_browsing::ArchiveAnalyzerResults& archive_results) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  base::UmaHistogramEnumeration("SBClientDownload.DmgArchiveNotValidReason",
+  base::UmaHistogramEnumeration("SBClientDownload.DmgArchiveAnalysisResult",
                                 archive_results.analysis_result);
 
   if (archive_results.signature_blob.size() > 0) {
diff --git a/chrome/browser/safe_browsing/download_protection/file_analyzer_unittest.cc b/chrome/browser/safe_browsing/download_protection/file_analyzer_unittest.cc
index b414e4af..2d34d8a 100644
--- a/chrome/browser/safe_browsing/download_protection/file_analyzer_unittest.cc
+++ b/chrome/browser/safe_browsing/download_protection/file_analyzer_unittest.cc
@@ -10,8 +10,10 @@
 #include "base/files/scoped_temp_dir.h"
 #include "base/path_service.h"
 #include "base/run_loop.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "build/build_config.h"
 #include "chrome/common/chrome_paths.h"
+#include "chrome/common/safe_browsing/archive_analyzer_results.h"
 #include "chrome/common/safe_browsing/mock_binary_feature_extractor.h"
 #include "components/safe_browsing/content/common/file_type_policies_test_util.h"
 #include "components/safe_browsing/core/common/features.h"
@@ -914,4 +916,93 @@
   ASSERT_EQ(0, result_.archived_binaries.size());
 }
 
+TEST_F(FileAnalyzerTest, ZipAnalysisResultMetric) {
+  scoped_refptr<MockBinaryFeatureExtractor> extractor =
+      new testing::StrictMock<MockBinaryFeatureExtractor>();
+  FileAnalyzer analyzer(extractor);
+  base::HistogramTester histogram_tester;
+  base::RunLoop run_loop;
+
+  base::FilePath target_path(FILE_PATH_LITERAL("target.zip"));
+  base::FilePath tmp_path =
+      temp_dir_.GetPath().Append(FILE_PATH_LITERAL("tmp.crdownload"));
+
+  base::ScopedTempDir zip_source_dir;
+  ASSERT_TRUE(zip_source_dir.CreateUniqueTempDir());
+  std::string file_contents = "dummy file";
+  ASSERT_EQ(static_cast<int>(file_contents.size()),
+            base::WriteFile(
+                zip_source_dir.GetPath().Append(FILE_PATH_LITERAL("file.exe")),
+                file_contents.data(), file_contents.size()));
+  ASSERT_TRUE(zip::Zip(zip_source_dir.GetPath(), tmp_path,
+                       /* include_hidden_files= */ false));
+
+  analyzer.Start(
+      target_path, tmp_path,
+      base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
+                     run_loop.QuitClosure()));
+  run_loop.Run();
+
+  ASSERT_TRUE(has_result_);
+  histogram_tester.ExpectBucketCount(
+      "SBClientDownload.ZipArchiveAnalysisResult",
+      ArchiveAnalysisResult::kValid, 1);
+}
+
+TEST_F(FileAnalyzerTest, RarAnalysisResultMetric) {
+  scoped_refptr<MockBinaryFeatureExtractor> extractor =
+      new testing::StrictMock<MockBinaryFeatureExtractor>();
+  FileAnalyzer analyzer(extractor);
+  base::HistogramTester histogram_tester;
+  base::RunLoop run_loop;
+
+  base::FilePath target_path(FILE_PATH_LITERAL("has_exe.rar"));
+  base::FilePath rar_path;
+  EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &rar_path));
+  rar_path = rar_path.AppendASCII("safe_browsing")
+                 .AppendASCII("rar")
+                 .AppendASCII("has_exe.rar");
+
+  analyzer.Start(
+      target_path, rar_path,
+      base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
+                     run_loop.QuitClosure()));
+
+  run_loop.Run();
+
+  ASSERT_TRUE(has_result_);
+  histogram_tester.ExpectBucketCount(
+      "SBClientDownload.RarArchiveAnalysisResult",
+      ArchiveAnalysisResult::kValid, 1);
+}
+
+#if BUILDFLAG(IS_MAC)
+TEST_F(FileAnalyzerTest, DmgAnalysisResultMetric) {
+  scoped_refptr<MockBinaryFeatureExtractor> extractor =
+      new testing::StrictMock<MockBinaryFeatureExtractor>();
+  FileAnalyzer analyzer(extractor);
+  base::HistogramTester histogram_tester;
+  base::RunLoop run_loop;
+
+  base::FilePath target_path(FILE_PATH_LITERAL("target.dmg"));
+  base::FilePath signed_dmg;
+  EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &signed_dmg));
+  signed_dmg = signed_dmg.AppendASCII("safe_browsing")
+                   .AppendASCII("mach_o")
+                   .AppendASCII("signed-archive.dmg");
+
+  analyzer.Start(
+      target_path, signed_dmg,
+      base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
+                     run_loop.QuitClosure()));
+
+  run_loop.Run();
+
+  ASSERT_TRUE(has_result_);
+  histogram_tester.ExpectBucketCount(
+      "SBClientDownload.DmgArchiveAnalysisResult",
+      ArchiveAnalysisResult::kValid, 1);
+}
+#endif
+
 }  // namespace safe_browsing
diff --git a/chrome/browser/segmentation_platform/segmentation_platform_service_factory.cc b/chrome/browser/segmentation_platform/segmentation_platform_service_factory.cc
index 174e947..5da61d7 100644
--- a/chrome/browser/segmentation_platform/segmentation_platform_service_factory.cc
+++ b/chrome/browser/segmentation_platform/segmentation_platform_service_factory.cc
@@ -87,7 +87,9 @@
   params->ukm_data_manager =
       UkmDatabaseClient::GetInstance().GetUkmDataManager();
   params->profile_prefs = profile->GetPrefs();
-  params->local_state = g_browser_process->local_state();
+  params->local_state = local_state_to_use_ == nullptr
+                            ? g_browser_process->local_state()
+                            : local_state_to_use_.get();
   params->configs = GetSegmentationPlatformConfig();
   params->field_trial_register = std::make_unique<FieldTrialRegisterImpl>();
 
diff --git a/chrome/browser/segmentation_platform/segmentation_platform_service_factory.h b/chrome/browser/segmentation_platform/segmentation_platform_service_factory.h
index 4e24232..250750e 100644
--- a/chrome/browser/segmentation_platform/segmentation_platform_service_factory.h
+++ b/chrome/browser/segmentation_platform/segmentation_platform_service_factory.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_SEGMENTATION_PLATFORM_SEGMENTATION_PLATFORM_SERVICE_FACTORY_H_
 #define CHROME_BROWSER_SEGMENTATION_PLATFORM_SEGMENTATION_PLATFORM_SERVICE_FACTORY_H_
 
+#include "base/memory/raw_ptr.h"
 #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
 
 namespace base {
@@ -16,6 +17,7 @@
 class BrowserContext;
 }  // namespace content
 
+class PrefService;
 class Profile;
 
 namespace segmentation_platform {
@@ -38,6 +40,10 @@
   SegmentationPlatformServiceFactory& operator=(
       const SegmentationPlatformServiceFactory&) = delete;
 
+  void set_local_state_for_testing(PrefService* local_state) {
+    local_state_to_use_ = local_state;
+  }
+
  private:
   friend struct base::DefaultSingletonTraits<
       SegmentationPlatformServiceFactory>;
@@ -48,6 +54,8 @@
   // BrowserContextKeyedServiceFactory overrides.
   KeyedService* BuildServiceInstanceFor(
       content::BrowserContext* context) const override;
+
+  raw_ptr<PrefService> local_state_to_use_;
 };
 
 }  // namespace segmentation_platform
diff --git a/chrome/browser/segmentation_platform/service_browsertest.cc b/chrome/browser/segmentation_platform/service_browsertest.cc
index 89de3d05..c61177b 100644
--- a/chrome/browser/segmentation_platform/service_browsertest.cc
+++ b/chrome/browser/segmentation_platform/service_browsertest.cc
@@ -198,7 +198,7 @@
 
   // Record page load UKM that should be recorded in the database, persisted
   // across sessions.
-  utils_.RecordPageLoadUkm(kUrl1);
+  utils_.RecordPageLoadUkm(kUrl1, base::Time::Now());
   while (!utils_.IsUrlInDatabase(kUrl1)) {
     base::RunLoop().RunUntilIdle();
   }
diff --git a/chrome/browser/segmentation_platform/ukm_data_manager_test_utils.cc b/chrome/browser/segmentation_platform/ukm_data_manager_test_utils.cc
index 5bbbd99..bf6cbfcc 100644
--- a/chrome/browser/segmentation_platform/ukm_data_manager_test_utils.cc
+++ b/chrome/browser/segmentation_platform/ukm_data_manager_test_utils.cc
@@ -27,11 +27,8 @@
 using ::testing::Return;
 using ::ukm::builders::PageLoad;
 
-constexpr ukm::SourceId kSourceId = 10;
-
 // Returns a sample UKM entry.
-ukm::mojom::UkmEntryPtr GetSamplePageLoadEntry(
-    ukm::SourceId source_id = kSourceId) {
+ukm::mojom::UkmEntryPtr GetSamplePageLoadEntry(ukm::SourceId source_id) {
   ukm::mojom::UkmEntryPtr entry = ukm::mojom::UkmEntry::New();
   entry->source_id = source_id;
   entry->event_hash = PageLoad::kEntryNameHash;
@@ -142,7 +139,8 @@
   return metadata;
 }
 
-void UkmDataManagerTestUtils::RecordPageLoadUkm(const GURL& url) {
+void UkmDataManagerTestUtils::RecordPageLoadUkm(const GURL& url,
+                                                base::Time history_timestamp) {
   UkmObserver* observer =
       UkmDatabaseClient::GetInstance().ukm_observer_for_testing();
   // Ensure that the observer is started before recording metrics.
@@ -150,12 +148,14 @@
   // Ensure that OTR profiles are not started in the test.
   ASSERT_FALSE(observer->is_paused_for_testing());
 
-  ukm_recorder_->AddEntry(GetSamplePageLoadEntry());
-  ukm_recorder_->UpdateSourceURL(kSourceId, url);
+  ukm_recorder_->AddEntry(GetSamplePageLoadEntry(source_id_counter_));
+  ukm_recorder_->UpdateSourceURL(source_id_counter_, url);
+  source_id_counter_++;
+
   // Without a history service the recorded URLs will not be written to
   // database.
   ASSERT_TRUE(history_service_);
-  history_service_->AddPage(url, base::Time::Now(),
+  history_service_->AddPage(url, history_timestamp,
                             history::VisitSource::SOURCE_BROWSED);
 }
 
diff --git a/chrome/browser/segmentation_platform/ukm_data_manager_test_utils.h b/chrome/browser/segmentation_platform/ukm_data_manager_test_utils.h
index 2ce792f1..7706dc5f 100644
--- a/chrome/browser/segmentation_platform/ukm_data_manager_test_utils.h
+++ b/chrome/browser/segmentation_platform/ukm_data_manager_test_utils.h
@@ -52,7 +52,7 @@
   // Records a page load and 2 valid UKM metrics associated with it. May record
   // other UKM metrics that are unrelated to the metadata provided by
   // GetSamplePageLoadMetadata().
-  void RecordPageLoadUkm(const GURL& url);
+  void RecordPageLoadUkm(const GURL& url, base::Time history_timestamp);
 
   // Returns whether the `url` is part of the UKM database.
   bool IsUrlInDatabase(const GURL& url);
@@ -72,6 +72,7 @@
       const ModelProvider::ModelUpdatedCallback& callback);
 
   const raw_ptr<ukm::TestUkmRecorder> ukm_recorder_;
+  int source_id_counter_ = 1;
   raw_ptr<history::HistoryService> history_service_;
 
   std::map<optimization_guide::proto::OptimizationTarget, MockModelProvider*>
diff --git a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/crow/CrowButtonDelegate.java b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/crow/CrowButtonDelegate.java
index 614f26d1b..43c927b 100644
--- a/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/crow/CrowButtonDelegate.java
+++ b/chrome/browser/share/android/java/src/org/chromium/chrome/browser/share/crow/CrowButtonDelegate.java
@@ -31,8 +31,10 @@
      * @param pageUrl URL for the page; passed in rather than derived from currentTab
      *     or WebContents's lastCommittedURL as it was used to construct UI in the caller.
      * @param canonicalUrl Canonical URL for 'pageUrl.' May be empty.
+     * @param isFollowing Whether the user is following the associated host in the feed.
      */
-    void launchCustomTab(Activity currentActivity, GURL pageUrl, GURL canonicalUrl);
+    void launchCustomTab(
+            Activity currentActivity, GURL pageUrl, GURL canonicalUrl, boolean isFollowing);
 
     /**
      * @return experiment-configured chip text.
diff --git a/chrome/browser/supervised_user/child_accounts/family_info_fetcher_unittest.cc b/chrome/browser/supervised_user/child_accounts/family_info_fetcher_unittest.cc
index 7f6f976..17e3c26 100644
--- a/chrome/browser/supervised_user/child_accounts/family_info_fetcher_unittest.cc
+++ b/chrome/browser/supervised_user/child_accounts/family_info_fetcher_unittest.cc
@@ -77,36 +77,32 @@
 
 std::string BuildGetFamilyMembersResponse(
     const std::vector<FamilyInfoFetcher::FamilyMember>& members) {
-  base::DictionaryValue dict;
-  auto list = std::make_unique<base::ListValue>();
+  base::Value::Dict dict;
+  base::Value::List list;
   for (size_t i = 0; i < members.size(); i++) {
     const FamilyInfoFetcher::FamilyMember& member = members[i];
-    std::unique_ptr<base::DictionaryValue> member_dict(
-        new base::DictionaryValue);
-    member_dict->SetKey("userId", base::Value(member.obfuscated_gaia_id));
-    member_dict->SetKey(
-        "role", base::Value(FamilyInfoFetcher::RoleToString(member.role)));
+    base::Value::Dict member_dict;
+    member_dict.Set("userId", member.obfuscated_gaia_id);
+    member_dict.Set("role", FamilyInfoFetcher::RoleToString(member.role));
     if (!member.display_name.empty() ||
         !member.email.empty() ||
         !member.profile_url.empty() ||
         !member.profile_image_url.empty()) {
-      auto profile_dict = std::make_unique<base::DictionaryValue>();
+      base::Value::Dict profile_dict;
       if (!member.display_name.empty())
-        profile_dict->SetKey("displayName", base::Value(member.display_name));
+        profile_dict.Set("displayName", member.display_name);
       if (!member.email.empty())
-        profile_dict->SetKey("email", base::Value(member.email));
+        profile_dict.Set("email", member.email);
       if (!member.profile_url.empty())
-        profile_dict->SetKey("profileUrl", base::Value(member.profile_url));
+        profile_dict.Set("profileUrl", member.profile_url);
       if (!member.profile_image_url.empty())
-        profile_dict->SetKey("profileImageUrl",
-                             base::Value(member.profile_image_url));
+        profile_dict.Set("profileImageUrl", member.profile_image_url);
 
-      member_dict->SetKey(
-          "profile", base::Value::FromUniquePtrValue(std::move(profile_dict)));
+      member_dict.Set("profile", std::move(profile_dict));
     }
-    list->Append(std::move(member_dict));
+    list.Append(std::move(member_dict));
   }
-  dict.SetKey("members", base::Value::FromUniquePtrValue(std::move(list)));
+  dict.Set("members", std::move(list));
   std::string result;
   base::JSONWriter::Write(dict, &result);
   return result;
diff --git a/chrome/browser/touch_to_fill/android/internal/BUILD.gn b/chrome/browser/touch_to_fill/android/internal/BUILD.gn
index 6268375..66c7b77ca 100644
--- a/chrome/browser/touch_to_fill/android/internal/BUILD.gn
+++ b/chrome/browser/touch_to_fill/android/internal/BUILD.gn
@@ -26,6 +26,7 @@
     "//components/url_formatter/android:url_formatter_java",
     "//third_party/androidx:androidx_annotation_annotation_java",
     "//third_party/androidx:androidx_appcompat_appcompat_resources_java",
+    "//third_party/androidx:androidx_core_core_java",
     "//third_party/androidx:androidx_recyclerview_recyclerview_java",
     "//ui/android:ui_java",
     "//url:gurl_java",
@@ -58,12 +59,24 @@
   ]
   sources = [
     "java/res/drawable-night/touch_to_fill_header_image.xml",
+    "java/res/drawable-v23/touch_to_fill_credential_background_modern.xml",
+    "java/res/drawable-v23/touch_to_fill_credential_background_modern_rounded_all.xml",
+    "java/res/drawable-v23/touch_to_fill_credential_background_modern_rounded_down.xml",
+    "java/res/drawable-v23/touch_to_fill_credential_background_modern_rounded_up.xml",
+    "java/res/drawable-v24/touch_to_fill_credential_background_modern.xml",
+    "java/res/drawable-v24/touch_to_fill_credential_background_modern_rounded_all.xml",
+    "java/res/drawable-v24/touch_to_fill_credential_background_modern_rounded_down.xml",
+    "java/res/drawable-v24/touch_to_fill_credential_background_modern_rounded_up.xml",
     "java/res/drawable/touch_to_fill_credential_background.xml",
     "java/res/drawable/touch_to_fill_header_image.xml",
     "java/res/layout/touch_to_fill_credential_item.xml",
+    "java/res/layout/touch_to_fill_credential_item_modern.xml",
     "java/res/layout/touch_to_fill_fill_button.xml",
+    "java/res/layout/touch_to_fill_fill_button_modern.xml",
     "java/res/layout/touch_to_fill_header_item.xml",
+    "java/res/layout/touch_to_fill_header_item_modern.xml",
     "java/res/layout/touch_to_fill_sheet.xml",
+    "java/res/layout/touch_to_fill_sheet_modern.xml",
     "java/res/values/dimens.xml",
   ]
 }
diff --git a/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v23/touch_to_fill_credential_background_modern.xml b/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v23/touch_to_fill_credential_background_modern.xml
new file mode 100644
index 0000000..cc886e6
--- /dev/null
+++ b/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v23/touch_to_fill_credential_background_modern.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 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. -->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="@color/default_bg_color_elev_1_baseline"/>
+</shape>
diff --git a/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v23/touch_to_fill_credential_background_modern_rounded_all.xml b/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v23/touch_to_fill_credential_background_modern_rounded_all.xml
new file mode 100644
index 0000000..e2461b7
--- /dev/null
+++ b/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v23/touch_to_fill_credential_background_modern_rounded_all.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 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. -->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+  <solid android:color="@color/default_bg_color_elev_1_baseline"/>
+  <corners android:topLeftRadius="@dimen/card_rounded_corner_radius" android:topRightRadius="@dimen/card_rounded_corner_radius" android:bottomLeftRadius="@dimen/card_rounded_corner_radius" android:bottomRightRadius="@dimen/card_rounded_corner_radius"/>
+</shape>
diff --git a/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v23/touch_to_fill_credential_background_modern_rounded_down.xml b/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v23/touch_to_fill_credential_background_modern_rounded_down.xml
new file mode 100644
index 0000000..c83bad5
--- /dev/null
+++ b/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v23/touch_to_fill_credential_background_modern_rounded_down.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 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. -->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+  <solid android:color="@color/default_bg_color_elev_1_baseline"/>
+  <corners android:bottomLeftRadius="@dimen/card_rounded_corner_radius" android:bottomRightRadius="@dimen/card_rounded_corner_radius"/>
+</shape>
diff --git a/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v23/touch_to_fill_credential_background_modern_rounded_up.xml b/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v23/touch_to_fill_credential_background_modern_rounded_up.xml
new file mode 100644
index 0000000..a6e350e
--- /dev/null
+++ b/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v23/touch_to_fill_credential_background_modern_rounded_up.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 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. -->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+  <solid android:color="@color/default_bg_color_elev_1_baseline"/>
+  <corners android:topLeftRadius="@dimen/card_rounded_corner_radius" android:topRightRadius="@dimen/card_rounded_corner_radius"/>
+</shape>
diff --git a/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v24/touch_to_fill_credential_background_modern.xml b/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v24/touch_to_fill_credential_background_modern.xml
new file mode 100644
index 0000000..51bbe8b
--- /dev/null
+++ b/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v24/touch_to_fill_credential_background_modern.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 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. -->
+<org.chromium.components.browser_ui.widget.SurfaceColorDrawable
+     xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:app="http://schemas.android.com/apk/res-auto"
+     android:shape="rectangle"
+     app:surfaceElevation="@dimen/default_elevation_1"/>
diff --git a/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v24/touch_to_fill_credential_background_modern_rounded_all.xml b/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v24/touch_to_fill_credential_background_modern_rounded_all.xml
new file mode 100644
index 0000000..2d17cebc
--- /dev/null
+++ b/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v24/touch_to_fill_credential_background_modern_rounded_all.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 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. -->
+<org.chromium.components.browser_ui.widget.SurfaceColorDrawable
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:shape="rectangle"
+    app:surfaceElevation="@dimen/default_elevation_1">
+  <corners android:topLeftRadius="@dimen/card_rounded_corner_radius"
+      android:topRightRadius="@dimen/card_rounded_corner_radius"
+      android:bottomLeftRadius="@dimen/card_rounded_corner_radius"
+      android:bottomRightRadius="@dimen/card_rounded_corner_radius"/>
+</org.chromium.components.browser_ui.widget.SurfaceColorDrawable>
diff --git a/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v24/touch_to_fill_credential_background_modern_rounded_down.xml b/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v24/touch_to_fill_credential_background_modern_rounded_down.xml
new file mode 100644
index 0000000..4271ef3c0
--- /dev/null
+++ b/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v24/touch_to_fill_credential_background_modern_rounded_down.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 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. -->
+<org.chromium.components.browser_ui.widget.SurfaceColorDrawable
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:shape="rectangle"
+    app:surfaceElevation="@dimen/default_elevation_1">
+    <corners android:bottomLeftRadius="@dimen/card_rounded_corner_radius"
+    android:bottomRightRadius="@dimen/card_rounded_corner_radius"/>
+</org.chromium.components.browser_ui.widget.SurfaceColorDrawable>
diff --git a/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v24/touch_to_fill_credential_background_modern_rounded_up.xml b/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v24/touch_to_fill_credential_background_modern_rounded_up.xml
new file mode 100644
index 0000000..07d9edc
--- /dev/null
+++ b/chrome/browser/touch_to_fill/android/internal/java/res/drawable-v24/touch_to_fill_credential_background_modern_rounded_up.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 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. -->
+<org.chromium.components.browser_ui.widget.SurfaceColorDrawable
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:shape="rectangle"
+    app:surfaceElevation="@dimen/default_elevation_1">
+    <corners android:topLeftRadius="@dimen/card_rounded_corner_radius"
+    android:topRightRadius="@dimen/card_rounded_corner_radius"/>
+</org.chromium.components.browser_ui.widget.SurfaceColorDrawable>
diff --git a/chrome/browser/touch_to_fill/android/internal/java/res/drawable/touch_to_fill_credential_background.xml b/chrome/browser/touch_to_fill/android/internal/java/res/drawable/touch_to_fill_credential_background.xml
index 08a0b525..3dcae12 100644
--- a/chrome/browser/touch_to_fill/android/internal/java/res/drawable/touch_to_fill_credential_background.xml
+++ b/chrome/browser/touch_to_fill/android/internal/java/res/drawable/touch_to_fill_credential_background.xml
@@ -6,6 +6,6 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle">
 
-    <stroke android:width="1dp" android:color="@macro/hairline_stroke_color"/>
-    <corners android:radius="8dp"/>
+  <stroke android:width="1dp" android:color="@macro/hairline_stroke_color"/>
+  <corners android:radius="8dp"/>
 </shape>
diff --git a/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_credential_item.xml b/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_credential_item.xml
index d729acb..4eef5dc 100644
--- a/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_credential_item.xml
+++ b/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_credential_item.xml
@@ -3,9 +3,9 @@
      Use of this source code is governed by a BSD-style license that can be
      found in the LICENSE file. -->
 
- <!-- Please update R.dimens.touch_to_fill_sheet_height_single_credential and
-      R.dimens.touch_to_fill_sheet_height_second_credential when modifying
-      the height of the credential. -->
+<!-- Please update R.dimens.touch_to_fill_sheet_height_single_credential and
+     R.dimens.touch_to_fill_sheet_height_second_credential when modifying
+     the height of the credential. -->
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:descendantFocusability="blocksDescendants"
diff --git a/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_credential_item_modern.xml b/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_credential_item_modern.xml
new file mode 100644
index 0000000..6552e851
--- /dev/null
+++ b/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_credential_item_modern.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 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. -->
+
+<!-- Please update R.dimens.touch_to_fill_sheet_height_single_credential and
+     R.dimens.touch_to_fill_sheet_height_second_credential when modifying
+     the height of the credential. -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:descendantFocusability="blocksDescendants"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="72dp"
+    android:gravity="center_vertical"
+    android:orientation="horizontal">
+
+  <ImageView
+      android:id="@+id/favicon"
+      android:layout_width="@dimen/touch_to_fill_favicon_size"
+      android:layout_height="@dimen/touch_to_fill_favicon_size"
+      android:layout_margin="24dp"
+      android:importantForAccessibility="no"
+      android:layout_gravity="center"/>
+  <LinearLayout
+      android:layout_width="0dp"
+      android:layout_height="wrap_content"
+      android:layout_marginEnd="24dp"
+      android:layout_marginTop="14dp"
+      android:layout_marginBottom="14dp"
+      android:layout_weight="1"
+      android:orientation="vertical">
+    <TextView
+        android:id="@+id/credential_origin"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:minHeight="20dp"
+        android:ellipsize="start"
+        android:singleLine="true"
+        android:textAppearance="@style/TextAppearance.TextMedium.Secondary" />
+    <TextView
+        android:id="@+id/username"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:minHeight="20dp"
+        android:ellipsize="end"
+        android:singleLine="true"
+        android:textAppearance="@style/TextAppearance.TextLarge.Primary" />
+    <TextView
+        android:id="@+id/password"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:minHeight="20dp"
+        android:ellipsize="end"
+        android:singleLine="true"
+        android:textAppearance="@style/TextAppearance.TextMedium.Secondary" />
+  </LinearLayout>
+</LinearLayout>
diff --git a/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_fill_button_modern.xml b/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_fill_button_modern.xml
new file mode 100644
index 0000000..52b934b
--- /dev/null
+++ b/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_fill_button_modern.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 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. -->
+
+<!-- Please update R.dimens.touch_to_fill_sheet_height_button when modifying
+    the margins. -->
+<org.chromium.ui.widget.ButtonCompat
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:descendantFocusability="blocksDescendants"
+    android:id="@+id/touch_to_fill_button_title"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="15dp"
+    android:minHeight="48dp"
+    android:gravity="center"
+    android:background="@drawable/touch_to_fill_credential_background"
+    android:ellipsize="end"
+    android:singleLine="true"
+    style="@style/FilledButton.Flat"/>
diff --git a/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_header_item_modern.xml b/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_header_item_modern.xml
new file mode 100644
index 0000000..23ef1e8
--- /dev/null
+++ b/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_header_item_modern.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 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. -->
+
+<!-- Please update R.dimens.touch_to_fill_sheet_height_single_credential
+     when modifying the margins image or text sizes. -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_height="wrap_content"
+    android:layout_width="match_parent"
+    android:layout_marginTop="16dp"
+    android:layout_marginBottom="15dp"
+    android:orientation="vertical">
+
+  <ImageView
+      android:id="@+id/touch_to_fill_sheet_header_image"
+      android:layout_width="@dimen/touch_to_fill_favicon_size_modern"
+      android:layout_height="@dimen/touch_to_fill_favicon_size_modern"
+      android:layout_gravity="center_horizontal"
+      android:layout_marginBottom="8dp"
+      android:importantForAccessibility="no" />
+
+  <org.chromium.ui.widget.TextViewWithLeading
+      android:id="@+id/touch_to_fill_sheet_title"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_gravity="center_horizontal"
+      android:paddingTop="10dp"
+      android:paddingBottom="10dp"
+      android:textAppearance="@style/TextAppearance.Headline.Primary" />
+
+  <org.chromium.ui.widget.TextViewWithLeading
+      android:id="@+id/touch_to_fill_sheet_subtitle"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_gravity="center_horizontal"
+      android:textAppearance="@style/TextAppearance.TextMedium.Secondary"/>
+</LinearLayout>
diff --git a/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_sheet_modern.xml b/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_sheet_modern.xml
new file mode 100644
index 0000000..b0ea3fca
--- /dev/null
+++ b/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_sheet_modern.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 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. -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent"
+    android:layout_marginBottom="16dp"
+    android:orientation="vertical">
+
+  <!-- Please update R.dimens.touch_to_fill_sheet_height_single_credential
+       when modifying the margins. -->
+  <ImageView
+      android:id="@+id/drag_handlebar"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_gravity="center_horizontal"
+      android:layout_marginEnd="@dimen/touch_to_fill_sheet_margin_modern"
+      android:layout_marginStart="@dimen/touch_to_fill_sheet_margin_modern"
+      android:layout_marginTop="6dp"
+      android:layout_marginBottom="6dp"
+      android:importantForAccessibility="no"
+      app:srcCompat="@drawable/drag_handlebar" />
+
+  <!-- Please update R.dimens.touch_to_fill_sheet_height_second_credential
+       when modifying paddingBottom. -->
+  <androidx.recyclerview.widget.RecyclerView
+      android:id="@+id/sheet_item_list"
+      android:layout_width="match_parent"
+      android:layout_height="0dp"
+      android:layout_weight="1"
+      android:layout_marginEnd="@dimen/touch_to_fill_sheet_margin_modern"
+      android:layout_marginStart="@dimen/touch_to_fill_sheet_margin_modern"
+      android:clipToPadding="false"
+      android:paddingBottom="8dp"
+      android:divider="@null"
+      tools:listitem="@layout/touch_to_fill_credential_item"/>
+
+  <View style="@style/HorizontalDivider"
+      android:layout_height="@dimen/divider_height"
+      android:layout_marginBottom="8dp"
+      android:layout_width="match_parent"/>
+
+  <TextView
+      android:id="@+id/touch_to_fill_sheet_manage_passwords"
+      android:text="@string/manage_passwords"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:layout_marginBottom="8dp"
+      android:paddingStart="@dimen/touch_to_fill_sheet_margin_modern"
+      android:paddingEnd="@dimen/touch_to_fill_sheet_margin_modern"
+      android:minHeight="48dp"
+      android:gravity="center_vertical|start"
+      android:textAppearance="@style/TextAppearance.TextLarge.Primary"
+      android:background="?android:attr/selectableItemBackground"/>
+</LinearLayout>
diff --git a/chrome/browser/touch_to_fill/android/internal/java/res/values/dimens.xml b/chrome/browser/touch_to_fill/android/internal/java/res/values/dimens.xml
index 4f58fbe5..84186e4 100644
--- a/chrome/browser/touch_to_fill/android/internal/java/res/values/dimens.xml
+++ b/chrome/browser/touch_to_fill/android/internal/java/res/values/dimens.xml
@@ -5,7 +5,10 @@
 
 <resources>
     <dimen name="touch_to_fill_favicon_size">24dp</dimen>
+    <dimen name="touch_to_fill_favicon_size_modern">32dp</dimen>
     <dimen name="touch_to_fill_sheet_margin">16dp</dimen>
+    <dimen name="touch_to_fill_sheet_margin_modern">24dp</dimen>
+    <dimen name="touch_to_fill_sheet_items_spacing">1dp</dimen>
 
     <!-- Below are the different Half-state peeking heights. The height is the
          sum of all components. It varies depending on the suggestion count. The
@@ -16,16 +19,29 @@
          + Title size and margin (48+16dp)
          + First suggestion (72dp) -->
     <dimen name="touch_to_fill_sheet_height_single_credential">282dp</dimen>
+    <!-- The height of all the components for the modern design is the following:
+         Handlebar (16dp)
+         + Header size (32+8dp)
+         + Title size and margin (50+19+15dp)
+         + First suggestion (72dp) -->
+    <dimen name="touch_to_fill_sheet_height_single_credential_modern">230dp</dimen>
 
     <!-- Top padding between RecyclerView elements (8 dp)
          + Extra suggestion height (72 dp). -->
     <dimen name="touch_to_fill_sheet_height_second_credential">80dp</dimen>
+    <!-- For UPM, top padding between RecyclerView elements (1 dp)
+         + Extra suggestion height (72 dp). -->
+    <dimen name="touch_to_fill_sheet_height_second_credential_modern">73dp</dimen>
 
     <!-- Top padding between RecyclerView elements (8 dp)
          + Top margin (2 dp)
          + button height (48 dp)
          + Bottom margin (2 dp) -->
     <dimen name="touch_to_fill_sheet_height_button">60dp</dimen>
+    <!-- For the modern design: top padding between RecyclerView elements (1 dp)
+         + Top margin (15 dp)
+         + button height (48 dp)-->
+    <dimen name="touch_to_fill_sheet_height_button_modern">64dp</dimen>
 
     <!-- Depending on the experiments that are active we might show a call to
          action button or branding message to the users, at which point we need
@@ -33,4 +49,6 @@
          added dynamically, depending on the state of the experiments. -->
     <dimen name="touch_to_fill_sheet_bottom_padding_credentials">16dp</dimen>
     <dimen name="touch_to_fill_sheet_bottom_padding_button">8dp</dimen>
+    <dimen name="touch_to_fill_sheet_bottom_padding_credentials_modern">24dp</dimen>
+    <dimen name="touch_to_fill_sheet_bottom_padding_button_modern">16dp</dimen>
 </resources>
diff --git a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillCoordinator.java b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillCoordinator.java
index 3de68c3..a343a832 100644
--- a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillCoordinator.java
+++ b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillCoordinator.java
@@ -4,6 +4,8 @@
 
 package org.chromium.chrome.browser.touch_to_fill;
 
+import static org.chromium.chrome.browser.password_manager.PasswordManagerHelper.usesUnifiedPasswordManagerUI;
+
 import android.content.Context;
 
 import androidx.annotation.VisibleForTesting;
@@ -33,7 +35,9 @@
             TouchToFillComponent.Delegate delegate) {
         mMediator.initialize(delegate, mModel,
                 new LargeIconBridge(Profile.getLastUsedRegularProfile()),
-                context.getResources().getDimensionPixelSize(R.dimen.touch_to_fill_favicon_size));
+                context.getResources().getDimensionPixelSize(usesUnifiedPasswordManagerUI()
+                                ? R.dimen.touch_to_fill_favicon_size_modern
+                                : R.dimen.touch_to_fill_favicon_size));
         setUpModelChangeProcessors(mModel, new TouchToFillView(context, sheetController));
     }
 
diff --git a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillView.java b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillView.java
index a63e5e3..637b69e 100644
--- a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillView.java
+++ b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillView.java
@@ -4,14 +4,19 @@
 
 package org.chromium.chrome.browser.touch_to_fill;
 
+import static org.chromium.chrome.browser.password_manager.PasswordManagerHelper.usesUnifiedPasswordManagerUI;
+
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Rect;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.LinearLayout;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.Px;
+import androidx.appcompat.content.res.AppCompatResources;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -33,6 +38,73 @@
     private final LinearLayout mContentView;
     private Callback<Integer> mDismissHandler;
 
+    private static class HorizontalDividerItemDecoration extends RecyclerView.ItemDecoration {
+        private final int mHorizontalMargin;
+        private final Context mContext;
+
+        HorizontalDividerItemDecoration(int horizontalMargin, Context context) {
+            this.mHorizontalMargin = horizontalMargin;
+            this.mContext = context;
+        }
+
+        @Override
+        public void getItemOffsets(
+                Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+            outRect.top = getItemOffsetInternal(view, parent, state);
+        }
+
+        private int getItemOffsetInternal(
+                final View view, final RecyclerView parent, RecyclerView.State state) {
+            return mHorizontalMargin;
+        }
+
+        /**
+         * Returns the proper background for each of the credential items depending on their
+         * position.
+         * @param position Position of the credential inside the list, including the header and the
+         *         button.
+         * @param containsFillButton Indicates if the fill button is in the list.
+         * @param itemCount Shows how many items are in the list, including the header and the
+         *         button.
+         * @return The ID of the selected background resource.
+         */
+        private int selectBackgroundDrawable(
+                int position, boolean containsFillButton, int itemCount) {
+            if (!usesUnifiedPasswordManagerUI()) {
+                return R.drawable.touch_to_fill_credential_background;
+            }
+            if (containsFillButton) { // Round all the corners of the only item.
+                return R.drawable.touch_to_fill_credential_background_modern_rounded_all;
+            }
+            if (position == 1) { // Round the top of the first item.
+                return R.drawable.touch_to_fill_credential_background_modern_rounded_up;
+            }
+            if (position == itemCount - 1) { // Round the bottom of the last item.
+                return R.drawable.touch_to_fill_credential_background_modern_rounded_down;
+            }
+            // The rest of the items have a background with no rounded edges.
+            return R.drawable.touch_to_fill_credential_background_modern;
+        }
+
+        @Override
+        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+            View lastChildId = parent.getChildAt(state.getItemCount() - 1);
+            boolean containsFillButton = lastChildId.getId() == R.id.touch_to_fill_button_title;
+            int itemCount = state.getItemCount();
+            if (containsFillButton) {
+                // The background of the button should not be changed.
+                itemCount--;
+            }
+            // Skipping the first item because it's the header.
+            for (int i = 1; i < itemCount; i++) {
+                View child = parent.getChildAt(i);
+                int position = parent.getChildAdapterPosition(child);
+                child.setBackground(AppCompatResources.getDrawable(mContext,
+                        selectBackgroundDrawable(position, containsFillButton, itemCount)));
+            }
+        }
+    }
+
     private final BottomSheetObserver mBottomSheetObserver = new EmptyBottomSheetObserver() {
         @Override
         public void onSheetClosed(@BottomSheetController.StateChangeReason int reason) {
@@ -61,11 +133,18 @@
         mContext = context;
         mBottomSheetController = bottomSheetController;
         mContentView = (LinearLayout) LayoutInflater.from(mContext).inflate(
-                R.layout.touch_to_fill_sheet, null);
+                usesUnifiedPasswordManagerUI() ? R.layout.touch_to_fill_sheet_modern
+                                               : R.layout.touch_to_fill_sheet,
+                null);
         mSheetItemListView = mContentView.findViewById(R.id.sheet_item_list);
         mSheetItemListView.setLayoutManager(new LinearLayoutManager(
                 mSheetItemListView.getContext(), LinearLayoutManager.VERTICAL, false));
-        mSheetItemListView.setItemAnimator(null);
+        if (usesUnifiedPasswordManagerUI()) {
+            mSheetItemListView.addItemDecoration(new HorizontalDividerItemDecoration(
+                    mContentView.getResources().getDimensionPixelSize(
+                            R.dimen.touch_to_fill_sheet_items_spacing),
+                    mContext));
+        }
     }
 
     /**
@@ -188,25 +267,54 @@
     private @Px int getDesiredSheetHeight() {
         Resources resources = mContext.getResources();
         @Px
-        int totalHeight = resources.getDimensionPixelSize(
-                R.dimen.touch_to_fill_sheet_height_single_credential);
+        int totalHeight = resources.getDimensionPixelSize(usesUnifiedPasswordManagerUI()
+                        ? R.dimen.touch_to_fill_sheet_height_single_credential_modern
+                        : R.dimen.touch_to_fill_sheet_height_single_credential);
 
         final boolean hasMultipleCredentials = mSheetItemListView.getAdapter() != null
                 && mSheetItemListView.getAdapter().getItemCount() > 2
                 && mSheetItemListView.getAdapter().getItemViewType(2)
                         == TouchToFillProperties.ItemType.CREDENTIAL;
         if (hasMultipleCredentials) {
-            totalHeight += resources.getDimensionPixelSize(
-                    R.dimen.touch_to_fill_sheet_height_second_credential);
-            totalHeight += resources.getDimensionPixelSize(
-                    R.dimen.touch_to_fill_sheet_bottom_padding_credentials);
+            totalHeight += getSecondCredentialAndSpacingHeight(resources);
         } else {
-            totalHeight +=
-                    resources.getDimensionPixelSize(R.dimen.touch_to_fill_sheet_height_button);
-            totalHeight += resources.getDimensionPixelSize(
-                    R.dimen.touch_to_fill_sheet_bottom_padding_button);
+            totalHeight += getButtonAndSpacingHeight(resources);
         }
 
         return totalHeight;
     }
+
+    /**
+     * Calculates the height of the button together with the padding.
+     */
+    private @Px int getSecondCredentialAndSpacingHeight(Resources resources) {
+        int secondCredentialHeight;
+        int secondCredentialPadding;
+        if (usesUnifiedPasswordManagerUI()) {
+            secondCredentialHeight = R.dimen.touch_to_fill_sheet_height_second_credential_modern;
+            secondCredentialPadding = R.dimen.touch_to_fill_sheet_bottom_padding_credentials_modern;
+        } else {
+            secondCredentialHeight = R.dimen.touch_to_fill_sheet_height_second_credential;
+            secondCredentialPadding = R.dimen.touch_to_fill_sheet_bottom_padding_credentials;
+        }
+        return resources.getDimensionPixelSize(secondCredentialHeight)
+                + resources.getDimensionPixelSize(secondCredentialPadding);
+    }
+
+    /**
+     * Calculates the height of the second credential together with the padding.
+     */
+    private @Px int getButtonAndSpacingHeight(Resources resources) {
+        int buttonHeight;
+        int buttonPadding;
+        if (usesUnifiedPasswordManagerUI()) {
+            buttonHeight = R.dimen.touch_to_fill_sheet_height_button_modern;
+            buttonPadding = R.dimen.touch_to_fill_sheet_bottom_padding_button_modern;
+        } else {
+            buttonHeight = R.dimen.touch_to_fill_sheet_height_button;
+            buttonPadding = R.dimen.touch_to_fill_sheet_bottom_padding_button;
+        }
+        return resources.getDimensionPixelSize(buttonHeight)
+                + resources.getDimensionPixelSize(buttonPadding);
+    }
 }
diff --git a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewBinder.java b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewBinder.java
index ab3666f..ddb4fcca 100644
--- a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewBinder.java
+++ b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewBinder.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.touch_to_fill;
 
+import static org.chromium.chrome.browser.password_manager.PasswordManagerHelper.usesUnifiedPasswordManagerUI;
 import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.CredentialProperties.CREDENTIAL;
 import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.CredentialProperties.FAVICON_OR_FALLBACK;
 import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.CredentialProperties.FORMATTED_ORIGIN;
@@ -34,6 +35,7 @@
 
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.password_manager.PasswordManagerHelper;
+import org.chromium.chrome.browser.password_manager.PasswordManagerResourceProviderFactory;
 import org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.CredentialProperties;
 import org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.ItemType;
 import org.chromium.chrome.browser.touch_to_fill.data.Credential;
@@ -89,18 +91,28 @@
             ViewGroup parent, @ItemType int itemType) {
         switch (itemType) {
             case ItemType.HEADER:
-                return new TouchToFillViewHolder(parent, R.layout.touch_to_fill_header_item,
+                return new TouchToFillViewHolder(parent,
+                        usesUnifiedPasswordManagerUI() ? R.layout.touch_to_fill_header_item_modern
+                                                       : R.layout.touch_to_fill_header_item,
                         TouchToFillViewBinder::bindHeaderView);
             case ItemType.CREDENTIAL:
-                return new TouchToFillViewHolder(parent, R.layout.touch_to_fill_credential_item,
+                return new TouchToFillViewHolder(parent,
+                        usesUnifiedPasswordManagerUI()
+                                ? R.layout.touch_to_fill_credential_item_modern
+                                : R.layout.touch_to_fill_credential_item,
                         TouchToFillViewBinder::bindCredentialView);
             case ItemType.WEBAUTHN_CREDENTIAL:
                 // TODO(https://crbug.com/1318942): The specific UI for this is forthcoming, but
                 // for now it is just filling into the existing credential item layout.
-                return new TouchToFillViewHolder(parent, R.layout.touch_to_fill_credential_item,
+                return new TouchToFillViewHolder(parent,
+                        usesUnifiedPasswordManagerUI()
+                                ? R.layout.touch_to_fill_credential_item_modern
+                                : R.layout.touch_to_fill_credential_item,
                         TouchToFillViewBinder::bindWebAuthnCredentialView);
             case ItemType.FILL_BUTTON:
-                return new TouchToFillViewHolder(parent, R.layout.touch_to_fill_fill_button,
+                return new TouchToFillViewHolder(parent,
+                        usesUnifiedPasswordManagerUI() ? R.layout.touch_to_fill_fill_button_modern
+                                                       : R.layout.touch_to_fill_fill_button,
                         TouchToFillViewBinder::bindFillButtonView);
         }
         assert false : "Cannot create view for ItemType: " + itemType;
@@ -273,8 +285,10 @@
             sheetSubtitleText.setText(getSubtitle(model, view.getContext()));
 
             ImageView sheetHeaderImage = view.findViewById(R.id.touch_to_fill_sheet_header_image);
-            sheetHeaderImage.setImageDrawable(AppCompatResources.getDrawable(
-                    view.getContext(), model.get(IMAGE_DRAWABLE_ID)));
+            sheetHeaderImage.setImageDrawable(AppCompatResources.getDrawable(view.getContext(),
+                    usesUnifiedPasswordManagerUI() ? PasswordManagerResourceProviderFactory.create()
+                                                             .getPasswordManagerIcon()
+                                                   : model.get(IMAGE_DRAWABLE_ID)));
         } else {
             assert false : "Unhandled update to property:" + key;
         }
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
index 099a8c8f..478e0e4 100644
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -5456,7 +5456,13 @@
       <message name="IDS_ACCOUNT_SELECTION_SHEET_TITLE_EXPLICIT" desc="Header for sign in sheet. Sheet is shown to prompt user for sign in consent.">
         Sign in to <ph name="SITE_ETLD_PLUS_ONE">%1$s<ex>rp.example</ex></ph> with <ph name="IDENTITY_PROVIDER_ETLD_PLUS_ONE">%2$s<ex>idp.com</ex></ph>
       </message>
-      <message name="IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_TOS" desc="The consent text shown to the user before sign up when there are no terms of service.">
+      <message name="IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP_OR_TOS" desc="The consent text shown to the user before sign up when there is no privacy policy or terms of service.">
+      To continue, <ph name="IDENTITY_PROVIDER_ETLD_PLUS_ONE">%1$s<ex>idp.com</ex></ph> will share your name, email address, and profile picture with this site.
+      </message>
+      <message name="IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP" desc="The consent text shown to the user before sign up when there is no privacy policy.">
+      To continue, <ph name="IDENTITY_PROVIDER_ETLD_PLUS_ONE">%1$s<ex>idp.com</ex></ph> will share your name, email address, and profile picture with this site. See this site's <ph name="BEGIN_LINK1">&lt;link_terms_of_service&gt;</ph>terms of service<ph name="END_LINK1">&lt;/link_terms_of_service&gt;</ph>.
+      </message>
+      <message name="IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_TOS" desc="The consent text shown to the user before sign up when there is no terms of service.">
       To continue, <ph name="IDENTITY_PROVIDER_ETLD_PLUS_ONE">%1$s<ex>idp.com</ex></ph> will share your name, email address, and profile picture with this site. See this site's <ph name="BEGIN_LINK1">&lt;link_privacy_policy&gt;</ph>privacy policy<ph name="END_LINK1">&lt;/link_privacy_policy&gt;</ph>.
       </message>
       <message name="IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT" desc="The consent text shown to the user before sign up.">
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP.png.sha1
new file mode 100644
index 0000000..1f64fad8
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP.png.sha1
@@ -0,0 +1 @@
+27524f3da48a11477746a8b01e1342cf510bae89
\ No newline at end of file
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP_OR_TOS.png.sha1 b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP_OR_TOS.png.sha1
new file mode 100644
index 0000000..444bf31
--- /dev/null
+++ b/chrome/browser/ui/android/strings/android_chrome_strings_grd/IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP_OR_TOS.png.sha1
@@ -0,0 +1 @@
+1b1a2782f4033fde537fc88d4e3b0aeabdae2103
\ No newline at end of file
diff --git a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionViewBinder.java b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionViewBinder.java
index 46b4e929..ab1e75b 100644
--- a/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionViewBinder.java
+++ b/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionViewBinder.java
@@ -178,19 +178,23 @@
             SpanApplier.SpanInfo termsOfServiceSpan =
                     createLink(context, properties.mTermsOfServiceUrl, "link_terms_of_service");
 
-            int consentTextId = termsOfServiceSpan == null
-                    ? R.string.account_selection_data_sharing_consent_no_tos
-                    : R.string.account_selection_data_sharing_consent;
+            int consentTextId;
+            if (privacyPolicySpan == null && termsOfServiceSpan == null) {
+                consentTextId = R.string.account_selection_data_sharing_consent_no_pp_or_tos;
+            } else if (privacyPolicySpan == null) {
+                consentTextId = R.string.account_selection_data_sharing_consent_no_pp;
+            } else if (termsOfServiceSpan == null) {
+                consentTextId = R.string.account_selection_data_sharing_consent_no_tos;
+            } else {
+                consentTextId = R.string.account_selection_data_sharing_consent;
+            }
             String consentText = String.format(
                     context.getString(consentTextId), properties.mFormattedIdpEtldPlusOne);
 
-            // |privacyPolicySpan| cannot be null due to the following:
-            // 1. We check that the privacy URL is valid in
-            // FederatedAuthRequestImpl::OnClientMetadataResponseReceived(), and an empty URL is
-            // invalid.
-            // 2. createLink() only returns null if the provided URL is empty.
             List<SpanApplier.SpanInfo> spans = new ArrayList<>();
-            spans.add(privacyPolicySpan);
+            if (privacyPolicySpan != null) {
+                spans.add(privacyPolicySpan);
+            }
             if (termsOfServiceSpan != null) {
                 spans.add(termsOfServiceSpan);
             }
diff --git a/chrome/browser/ui/app_list/app_service/app_service_app_model_builder_unittest.cc b/chrome/browser/ui/app_list/app_service/app_service_app_model_builder_unittest.cc
index 4cde89a1..ea9b2f0b 100644
--- a/chrome/browser/ui/app_list/app_service/app_service_app_model_builder_unittest.cc
+++ b/chrome/browser/ui/app_list/app_service/app_service_app_model_builder_unittest.cc
@@ -737,7 +737,7 @@
 
   void SetUp() override {
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
     AppServiceAppModelBuilderTest::SetUp();
@@ -758,7 +758,7 @@
     profile_.reset();
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
@@ -977,14 +977,14 @@
   struct ScopedDBusThreadManager {
     ScopedDBusThreadManager() {
       chromeos::DBusThreadManager::Initialize();
-      chromeos::CiceroneClient::InitializeFake();
+      ash::CiceroneClient::InitializeFake();
       ash::ConciergeClient::InitializeFake();
       ash::SeneschalClient::InitializeFake();
     }
     ~ScopedDBusThreadManager() {
       ash::SeneschalClient::Shutdown();
       ash::ConciergeClient::Shutdown();
-      chromeos::CiceroneClient::Shutdown();
+      ash::CiceroneClient::Shutdown();
       chromeos::DBusThreadManager::Shutdown();
     }
   } dbus_thread_manager_;
diff --git a/chrome/browser/ui/app_list/search/app_search_provider_unittest.cc b/chrome/browser/ui/app_list/search/app_search_provider_unittest.cc
index 693a1883..71f6554 100644
--- a/chrome/browser/ui/app_list/search/app_search_provider_unittest.cc
+++ b/chrome/browser/ui/app_list/search/app_search_provider_unittest.cc
@@ -643,7 +643,7 @@
  public:
   void SetUp() override {
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
     AppSearchProviderTest::SetUp();
@@ -660,7 +660,7 @@
     profile_.reset();
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 };
diff --git a/chrome/browser/ui/app_list/search/games/game_result.cc b/chrome/browser/ui/app_list/search/games/game_result.cc
index 0771de0d..f32a138 100644
--- a/chrome/browser/ui/app_list/search/games/game_result.cc
+++ b/chrome/browser/ui/app_list/search/games/game_result.cc
@@ -14,9 +14,13 @@
 #include "ash/public/cpp/style/color_provider.h"
 #include "ash/strings/grit/ash_strings.h"
 #include "base/bind.h"
+#include "base/containers/fixed_flat_set.h"
+#include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
 #include "chrome/browser/apps/app_discovery_service/app_discovery_service.h"
 #include "chrome/browser/apps/app_discovery_service/game_extras.h"
+#include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/ui/app_list/app_list_controller_delegate.h"
 #include "chrome/browser/ui/app_list/search/common/icon_constants.h"
 #include "chrome/browser/ui/app_list/search/common/search_result_util.h"
@@ -32,6 +36,9 @@
 
 constexpr char16_t kA11yDelimiter[] = u", ";
 
+constexpr auto kAllowedLaunchAppIds = base::MakeFixedFlatSet<base::StringPiece>(
+    {"egmafekfmcnknbdlbfbhafbllplmjlhn", "pnkcfpnngfokcnnijgkllghjlhkailce"});
+
 bool IsDarkModeEnabled() {
   // TODO(crbug.com/1258415): Simplify this logic once the productivity launcher
   // is launched.
@@ -95,6 +102,24 @@
 GameResult::~GameResult() = default;
 
 void GameResult::Open(int event_flags) {
+  // TODO(crbug.com/1305880): Add browser tests for the launch logic.
+
+  // Launch the app directly if possible.
+  auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile_);
+  if (proxy) {
+    std::vector<std::string> app_ids =
+        proxy->GetAppIdsForUrl(launch_url_, /*exclude_browsers=*/true,
+                               /*exclude_browser_tab_apps=*/true);
+    for (const auto& app_id : app_ids) {
+      if (kAllowedLaunchAppIds.contains(app_id)) {
+        proxy->LaunchAppWithUrl(app_id, event_flags, launch_url_,
+                                apps::mojom::LaunchSource::kFromAppListQuery);
+        return;
+      }
+    }
+  }
+
+  // If no suitable app was found, launch the URL in the browser.
   list_controller_->OpenURL(profile_, launch_url_, ui::PAGE_TRANSITION_TYPED,
                             ui::DispositionFromEventFlags(event_flags));
 }
diff --git a/chrome/browser/ui/ash/desks_templates/desks_templates_app_launch_handler.cc b/chrome/browser/ui/ash/desks_templates/desks_templates_app_launch_handler.cc
index 8e3d30e..1f167f8 100644
--- a/chrome/browser/ui/ash/desks_templates/desks_templates_app_launch_handler.cc
+++ b/chrome/browser/ui/ash/desks_templates/desks_templates_app_launch_handler.cc
@@ -6,7 +6,6 @@
 
 #include <string>
 
-#include "ash/constants/ash_features.h"
 #include "ash/public/cpp/desk_template.h"
 #include "ash/wm/desks/desks_controller.h"
 #include "base/metrics/histogram_macros.h"
@@ -231,9 +230,6 @@
 }
 
 void DesksTemplatesAppLaunchHandler::MaybeLaunchArcApps() {
-  if (!ash::features::AreDesksTemplatesEnabled())
-    return;
-
   apps::AppRegistryCache& cache =
       apps::AppServiceProxyFactory::GetForProfile(profile())
           ->AppRegistryCache();
diff --git a/chrome/browser/ui/ash/desks_templates/desks_templates_client_browsertest.cc b/chrome/browser/ui/ash/desks_templates/desks_templates_client_browsertest.cc
index 7b8e925d..dec2941 100644
--- a/chrome/browser/ui/ash/desks_templates/desks_templates_client_browsertest.cc
+++ b/chrome/browser/ui/ash/desks_templates/desks_templates_client_browsertest.cc
@@ -19,7 +19,7 @@
 #include "ash/shell.h"
 #include "ash/wm/desks/desk.h"
 #include "ash/wm/desks/desks_test_util.h"
-#include "ash/wm/desks/templates/desks_templates_test_util.h"
+#include "ash/wm/desks/templates/saved_desk_test_util.h"
 #include "ash/wm/desks/templates/saved_desk_util.h"
 #include "ash/wm/overview/overview_test_util.h"
 #include "base/guid.h"
diff --git a/chrome/browser/ui/ash/shelf/shelf_context_menu_unittest.cc b/chrome/browser/ui/ash/shelf/shelf_context_menu_unittest.cc
index 81d11b53..69cd5a8 100644
--- a/chrome/browser/ui/ash/shelf/shelf_context_menu_unittest.cc
+++ b/chrome/browser/ui/ash/shelf/shelf_context_menu_unittest.cc
@@ -106,7 +106,7 @@
 
   void SetUp() override {
     chromeos::DBusThreadManager::Initialize();
-    chromeos::CiceroneClient::InitializeFake();
+    ash::CiceroneClient::InitializeFake();
     ash::ConciergeClient::InitializeFake();
     ash::SeneschalClient::InitializeFake();
 
@@ -210,7 +210,7 @@
 
     ash::SeneschalClient::Shutdown();
     ash::ConciergeClient::Shutdown();
-    chromeos::CiceroneClient::Shutdown();
+    ash::CiceroneClient::Shutdown();
     chromeos::DBusThreadManager::Shutdown();
   }
 
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller.cc b/chrome/browser/ui/extensions/extension_action_view_controller.cc
index 30dc273..c9cdf4a 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller.cc
+++ b/chrome/browser/ui/extensions/extension_action_view_controller.cc
@@ -30,6 +30,7 @@
 #include "chrome/browser/ui/toolbar/toolbar_action_view_delegate.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/sessions/content/session_tab_helper.h"
+#include "content/public/browser/web_contents.h"
 #include "extensions/browser/extension_action.h"
 #include "extensions/browser/extension_action_manager.h"
 #include "extensions/browser/extension_registry.h"
@@ -470,18 +471,22 @@
 ExtensionActionViewController::GetIconImageSource(
     content::WebContents* web_contents,
     const gfx::Size& size) {
-  int tab_id = sessions::SessionTabHelper::IdForTab(web_contents).id();
   // `web_contents` may be null during tab closure or in tests.  Fall back on a
   // generic color provider.
-  const auto* const color_provider =
-      web_contents
-          ? &web_contents->GetColorProvider()
-          : ui::ColorProviderManager::Get().GetColorProviderFor(
-                ui::NativeTheme::GetInstanceForNativeUi()->GetColorProviderKey(
-                    nullptr));
-  auto image_source =
-      std::make_unique<IconWithBadgeImageSource>(size, color_provider);
+  auto get_color_provider_callback = base::BindRepeating(
+      [](base::WeakPtr<content::WebContents> weak_web_contents) {
+        return weak_web_contents
+                   ? &weak_web_contents->GetColorProvider()
+                   : ui::ColorProviderManager::Get().GetColorProviderFor(
+                         ui::NativeTheme::GetInstanceForNativeUi()
+                             ->GetColorProviderKey(nullptr));
+      },
+      web_contents ? web_contents->GetWeakPtr()
+                   : base::WeakPtr<content::WebContents>());
+  auto image_source = std::make_unique<IconWithBadgeImageSource>(
+      size, std::move(get_color_provider_callback));
 
+  int tab_id = sessions::SessionTabHelper::IdForTab(web_contents).id();
   image_source->SetIcon(icon_factory_.GetIcon(tab_id));
 
   std::unique_ptr<IconWithBadgeImageSource::Badge> badge;
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller_unittest.cc b/chrome/browser/ui/extensions/extension_action_view_controller_unittest.cc
index 7a11585..697eafb 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller_unittest.cc
+++ b/chrome/browser/ui/extensions/extension_action_view_controller_unittest.cc
@@ -46,6 +46,8 @@
 #include "extensions/common/user_script.h"
 #include "extensions/test/test_extension_dir.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/image/image_skia_rep.h"
+#include "ui/native_theme/native_theme.h"
 
 using extensions::mojom::ManifestLocation;
 using SiteInteraction = extensions::SitePermissionsHelper::SiteInteraction;
@@ -257,6 +259,13 @@
                                                                  view_size());
   EXPECT_FALSE(image_source->grayscale());
   EXPECT_TRUE(image_source->paint_blocked_actions_decoration());
+
+  // Simulate NativeTheme update after `image_source` is created.
+  // `image_source` should paint fine without hitting use-after-free in such
+  // case.  See http://crbug.com/1315967
+  ui::NativeTheme* theme = ui::NativeTheme::GetInstanceForNativeUi();
+  theme->NotifyOnNativeThemeUpdated();
+  image_source->GetImageForScale(1.0f);
 }
 
 // Tests the appearance of extension actions for extensions without a browser or
diff --git a/chrome/browser/ui/extensions/icon_with_badge_image_source.cc b/chrome/browser/ui/extensions/icon_with_badge_image_source.cc
index b3216d1..95ed0fa 100644
--- a/chrome/browser/ui/extensions/icon_with_badge_image_source.cc
+++ b/chrome/browser/ui/extensions/icon_with_badge_image_source.cc
@@ -59,12 +59,13 @@
 
 IconWithBadgeImageSource::IconWithBadgeImageSource(
     const gfx::Size& size,
-    const ui::ColorProvider* color_provider)
-    : gfx::CanvasImageSource(size), color_provider_(color_provider) {
-  CHECK(color_provider_);
+    GetColorProviderCallback get_color_provider_callback)
+    : gfx::CanvasImageSource(size),
+      get_color_provider_callback_(std::move(get_color_provider_callback)) {
+  DCHECK(get_color_provider_callback_);
 }
 
-IconWithBadgeImageSource::~IconWithBadgeImageSource() {}
+IconWithBadgeImageSource::~IconWithBadgeImageSource() = default;
 
 void IconWithBadgeImageSource::SetIcon(const gfx::Image& icon) {
   icon_ = icon;
@@ -76,10 +77,12 @@
   if (!badge_ || badge_->text.empty())
     return;
 
+  const ui::ColorProvider* color_provider = get_color_provider_callback_.Run();
+
   // Generate the badge's render text.
   SkColor text_color =
       SkColorGetA(badge_->text_color) == SK_AlphaTRANSPARENT
-          ? color_provider_->GetColor(kColorExtensionIconBadgeForegroundDefault)
+          ? color_provider->GetColor(kColorExtensionIconBadgeForegroundDefault)
           : badge_->text_color;
 
   constexpr int kBadgeHeight = 12;
@@ -177,10 +180,12 @@
   if (!badge_text_)
     return;
 
+  const ui::ColorProvider* color_provider = get_color_provider_callback_.Run();
+
   // Make sure the background color is opaque. See http://crbug.com/619499
   SkColor background_color =
       SkColorGetA(badge_->background_color) == SK_AlphaTRANSPARENT
-          ? color_provider_->GetColor(kColorExtensionIconBadgeBackgroundDefault)
+          ? color_provider->GetColor(kColorExtensionIconBadgeBackgroundDefault)
           : SkColorSetA(badge_->background_color, SK_AlphaOPAQUE);
   cc::PaintFlags rect_flags;
   rect_flags.setStyle(cc::PaintFlags::kFill_Style);
@@ -213,13 +218,14 @@
   // to double the CSS-based blur values.
   constexpr int kBlurCorrection = 2;
 
+  const ui::ColorProvider* color_provider = get_color_provider_callback_.Run();
   const gfx::ShadowValue key_shadow(
       gfx::Vector2d(0, 1), kBlurCorrection * 2 /*blur*/,
-      color_provider_->GetColor(kColorExtensionIconDecorationKeyShadow));
+      color_provider->GetColor(kColorExtensionIconDecorationKeyShadow));
 
   const gfx::ShadowValue ambient_shadow(
       gfx::Vector2d(0, 2), kBlurCorrection * 6 /*blur*/,
-      color_provider_->GetColor(kColorExtensionIconDecorationAmbientShadow));
+      color_provider->GetColor(kColorExtensionIconDecorationAmbientShadow));
 
   const float blocked_action_badge_radius = GetBlockedActionBadgeRadius();
 
@@ -232,7 +238,7 @@
   paint_flags.setStyle(cc::PaintFlags::kFill_Style);
   paint_flags.setAntiAlias(true);
   paint_flags.setColor(
-      color_provider_->GetColor(kColorExtensionIconDecorationBackground));
+      color_provider->GetColor(kColorExtensionIconDecorationBackground));
   paint_flags.setLooper(
       gfx::CreateShadowDrawLooper({key_shadow, ambient_shadow}));
 
diff --git a/chrome/browser/ui/extensions/icon_with_badge_image_source.h b/chrome/browser/ui/extensions/icon_with_badge_image_source.h
index eaffb48..c27ce4b 100644
--- a/chrome/browser/ui/extensions/icon_with_badge_image_source.h
+++ b/chrome/browser/ui/extensions/icon_with_badge_image_source.h
@@ -7,6 +7,7 @@
 
 #include <string>
 
+#include "base/callback.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/image/canvas_image_source.h"
@@ -40,8 +41,11 @@
     SkColor background_color;
   };
 
-  IconWithBadgeImageSource(const gfx::Size& size,
-                           const ui::ColorProvider* color_provider);
+  using GetColorProviderCallback =
+      base::RepeatingCallback<const ui::ColorProvider*()>;
+  IconWithBadgeImageSource(
+      const gfx::Size& size,
+      GetColorProviderCallback get_color_provider_callback);
 
   IconWithBadgeImageSource(const IconWithBadgeImageSource&) = delete;
   IconWithBadgeImageSource& operator=(const IconWithBadgeImageSource&) = delete;
@@ -81,7 +85,7 @@
   // https://crbug.com/831946.
   gfx::Rect GetIconAreaRect() const;
 
-  const ui::ColorProvider* const color_provider_;
+  GetColorProviderCallback get_color_provider_callback_;
 
   // The base icon to draw.
   gfx::Image icon_;
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.cc b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.cc
index 60515f9..60eaffc7 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.cc
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.cc
@@ -6,6 +6,7 @@
 
 #include <string>
 
+#include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/tabs/tab_group_theme.h"
 #include "chrome/browser/ui/view_ids.h"
@@ -16,6 +17,8 @@
 #include "ui/accessibility/ax_enums.mojom.h"
 #include "ui/accessibility/ax_node_data.h"
 #include "ui/base/l10n/l10n_util.h"
+#include "ui/base/models/dialog_model.h"
+#include "ui/base/models/dialog_model_menu_model_adapter.h"
 #include "ui/base/theme_provider.h"
 #include "ui/gfx/animation/slide_animation.h"
 #include "ui/gfx/canvas.h"
@@ -25,6 +28,7 @@
 #include "ui/views/controls/button/label_button_border.h"
 #include "ui/views/controls/button/menu_button.h"
 #include "ui/views/controls/highlight_path_generator.h"
+#include "ui/views/controls/menu/menu_runner.h"
 
 namespace {
 constexpr float kBorderRadius = 4.5f;
@@ -81,6 +85,9 @@
     // comfortably fit in the bookmarks bar.
     SetPreferredSize(gfx::Size(button_height, button_height));
   }
+  // TODO(crbug.com/1324360): Add this back when the ContextMenuController does
+  // something reasonable.
+  // set_context_menu_controller(&context_menu_controller_);
 }
 
 SavedTabGroupButton::~SavedTabGroupButton() = default;
@@ -179,5 +186,37 @@
   return is_group_in_tabstrip_;
 }
 
+SavedTabGroupButton::ContextMenuController::ContextMenuController() = default;
+SavedTabGroupButton::ContextMenuController::~ContextMenuController() = default;
+
+void SavedTabGroupButton::ContextMenuController::ShowContextMenuForViewImpl(
+    View* source,
+    const gfx::Point& point,
+    ui::MenuSourceType source_type) {
+  // TODO(pbos): Populate with real data, this is a placeholder to show dljames@
+  // how the API is intended to be used. DoNothing()s need to be replaced with
+  // base::BindRepeating calls to open tabs.
+  auto dialog_model =
+      ui::DialogModel::Builder()
+          .AddMenuItem(ui::ImageModel::FromVectorIcon(kSaveGroupIcon), u"HELLO",
+                       base::DoNothing())
+          .AddMenuItem(
+              ui::ImageModel::FromVectorIcon(kMoveGroupToNewWindowIcon),
+              u"HELLO AGAIN", base::DoNothing())
+          .Build();
+  menu_model_ = std::make_unique<ui::DialogModelMenuModelAdapter>(
+      std::move(dialog_model));
+
+  // TODO(pbos): See if there's a better way than IS_NESTED to force this to
+  // show icons (we need favicons, I haven't figured out why this doesn't show
+  // icons under Mac OS context menus).
+  menu_runner_ = std::make_unique<views::MenuRunner>(
+      menu_model_.get(),
+      views::MenuRunner::CONTEXT_MENU | views::MenuRunner::IS_NESTED);
+  menu_runner_->RunMenuAt(source->GetWidget(), /*button_controller=*/nullptr,
+                          gfx::Rect(point, gfx::Size()),
+                          views::MenuAnchorPosition::kTopLeft, source_type);
+}
+
 BEGIN_METADATA(SavedTabGroupButton, MenuButton)
 END_METADATA
diff --git a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.h b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.h
index 5c433ff..bca07fca 100644
--- a/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.h
+++ b/chrome/browser/ui/views/bookmarks/saved_tab_groups/saved_tab_group_button.h
@@ -10,6 +10,7 @@
 #include "components/tab_groups/tab_group_color.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
+#include "ui/views/context_menu_controller.h"
 #include "ui/views/controls/button/menu_button.h"
 
 namespace gfx {
@@ -31,15 +32,12 @@
   SavedTabGroupButton& operator=(const SavedTabGroupButton&) = delete;
   ~SavedTabGroupButton() override;
 
+  // views::MenuButton:
   std::u16string GetTooltipText(const gfx::Point& p) const override;
-
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
-
   void OnPaintBackground(gfx::Canvas* canvas) override;
-
   std::unique_ptr<views::LabelButtonBorder> CreateDefaultBorder()
       const override;
-
   void OnThemeChanged() override;
 
   void RemoveButtonOutline();
@@ -50,6 +48,21 @@
   }
 
  private:
+  class ContextMenuController : public views::ContextMenuController {
+   public:
+    ContextMenuController();
+    ~ContextMenuController() override;
+
+   private:
+    void ShowContextMenuForViewImpl(View* source,
+                                    const gfx::Point& point,
+                                    ui::MenuSourceType source_type) override;
+
+    // TODO(pbos): Comment
+    std::unique_ptr<ui::MenuModel> menu_model_;
+    std::unique_ptr<views::MenuRunner> menu_runner_;
+  };
+
   // The animations for button movement.
   std::unique_ptr<gfx::SlideAnimation> show_animation_;
 
@@ -58,6 +71,8 @@
 
   // Denotes if the tabgroup is currently open in the tabstrip.
   bool is_group_in_tabstrip_;
+
+  ContextMenuController context_menu_controller_;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_BOOKMARKS_SAVED_TAB_GROUPS_SAVED_TAB_GROUP_BUTTON_H_
diff --git a/chrome/browser/ui/views/crostini/crostini_uninstaller_view_browsertest.cc b/chrome/browser/ui/views/crostini/crostini_uninstaller_view_browsertest.cc
index 0bf69e9..5b5e7c8 100644
--- a/chrome/browser/ui/views/crostini/crostini_uninstaller_view_browsertest.cc
+++ b/chrome/browser/ui/views/crostini/crostini_uninstaller_view_browsertest.cc
@@ -28,7 +28,7 @@
  public:
   class WaitingFakeConciergeClient : public ash::FakeConciergeClient {
    public:
-    explicit WaitingFakeConciergeClient(chromeos::FakeCiceroneClient* client)
+    explicit WaitingFakeConciergeClient(ash::FakeCiceroneClient* client)
         : ash::FakeConciergeClient(client) {}
 
     void StopVm(
diff --git a/chrome/browser/ui/views/crostini/crostini_update_filesystem_view_browsertest.cc b/chrome/browser/ui/views/crostini/crostini_update_filesystem_view_browsertest.cc
index 17ef236..c15af470 100644
--- a/chrome/browser/ui/views/crostini/crostini_update_filesystem_view_browsertest.cc
+++ b/chrome/browser/ui/views/crostini/crostini_update_filesystem_view_browsertest.cc
@@ -20,8 +20,8 @@
 #include "content/public/test/browser_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-chromeos::FakeCiceroneClient* GetFakeCiceroneClient() {
-  return chromeos::FakeCiceroneClient::Get();
+ash::FakeCiceroneClient* GetFakeCiceroneClient() {
+  return ash::FakeCiceroneClient::Get();
 }
 
 class CrostiniUpdateFilesystemViewBrowserTest
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_dialog_browsertest.cc b/chrome/browser/ui/views/download/bubble/download_bubble_dialog_browsertest.cc
deleted file mode 100644
index 88968dc..0000000
--- a/chrome/browser/ui/views/download/bubble/download_bubble_dialog_browsertest.cc
+++ /dev/null
@@ -1,278 +0,0 @@
-// Copyright 2022 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/download/bubble/download_toolbar_button_view.h"
-
-#include <memory>
-#include "base/containers/fixed_flat_map.h"
-#include "base/files/file_path.h"
-#include "base/path_service.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/test/test_browser_dialog.h"
-#include "chrome/browser/ui/views/download/bubble/download_bubble_row_list_view.h"
-#include "chrome/browser/ui/views/download/bubble/download_bubble_row_view.h"
-#include "chrome/browser/ui/views/download/bubble/download_toolbar_button_view.h"
-#include "chrome/browser/ui/views/frame/browser_view.h"
-#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
-#include "chrome/common/chrome_paths.h"
-#include "chrome/test/base/ui_test_utils.h"
-#include "components/safe_browsing/core/common/features.h"
-#include "content/public/browser/download_manager.h"
-#include "content/public/test/browser_test.h"
-#include "content/public/test/download_test_observer.h"
-#include "content/public/test/slow_download_http_response.h"
-#include "ui/events/test/event_generator.h"
-#include "ui/views/controls/button/button.h"
-#include "ui/views/widget/widget_utils.h"
-#include "url/gurl.h"
-
-namespace {
-enum Testcase {
-  kInProgressDownload,
-  kCompletedDownload,
-  kFullView,
-  kDangerousDownloadSubpage
-};
-
-auto kNameToTestcase = base::MakeFixedFlatMap<std::string, Testcase>({
-    {"InProgressDownload", kInProgressDownload},
-    {"CompletedDownload", kCompletedDownload},
-    {"FullView", kFullView},
-    {"DangerousDownloadSubpage", kDangerousDownloadSubpage},
-});
-
-constexpr base::StringPiece kDangerousUrl =
-    "/downloads/dangerous/dangerous.swf";
-}  // namespace
-
-class DownloadBubbleDialogBrowserTest : public DialogBrowserTest {
- public:
-  DownloadBubbleDialogBrowserTest() {
-    feature_list_.InitWithFeatures(
-        /*enabled_features=*/{safe_browsing::kDownloadBubble},
-        /*disabled_features=*/{});
-  }
-  DownloadBubbleDialogBrowserTest(const DownloadBubbleDialogBrowserTest& test) =
-      delete;
-  DownloadBubbleDialogBrowserTest& operator=(
-      const DownloadBubbleDialogBrowserTest& test) = delete;
-
-  DownloadToolbarButtonView* GetDownloadToolbarButtonView() {
-    BrowserView* browser_view =
-        BrowserView::GetBrowserViewForBrowser(browser());
-    return (browser_view && browser_view->toolbar())
-               ? browser_view->toolbar()->download_button()
-               : nullptr;
-  }
-
-  void ClickDownloadItem() {
-    DownloadToolbarButtonView* button = GetDownloadToolbarButtonView();
-    const views::View::Views& row_list =
-        button->download_row_list_view_->children();
-    ASSERT_EQ(row_list.size(), 1u);
-    DownloadBubbleRowView* row =
-        static_cast<DownloadBubbleRowView*>(row_list[0]);
-
-    base::RunLoop dangerous_wait;
-    row->SetNotifyDangerousDownloadCallbackForTesting(
-        dangerous_wait.QuitClosure());
-    dangerous_wait.Run();
-
-    ui::test::EventGenerator generator(
-        GetRootWindow(button->bubble_delegate_->GetWidget()));
-    generator.MoveMouseTo(row->GetBoundsInScreen().CenterPoint());
-    generator.ClickLeftButton();
-  }
-
-  void ClickDownloadToolbarButton(bool is_creation) {
-    DownloadToolbarButtonView* button = GetDownloadToolbarButtonView();
-    base::RunLoop wait;
-    if (is_creation) {
-      button->SetBubbleCreatedCallbackForTesting(wait.QuitClosure());
-    } else {
-      button->SetBubbleDestroyedCallbackForTesting(wait.QuitClosure());
-    }
-
-    ui::test::EventGenerator generator(GetRootWindow(button->GetWidget()));
-    generator.MoveMouseTo(button->GetBoundsInScreen().CenterPoint());
-    generator.ClickLeftButton();
-    wait.Run();
-  }
-
-  content::DownloadManager::DownloadVector GetDownloadItems() {
-    content::DownloadManager::DownloadVector items;
-    browser()->profile()->GetDownloadManager()->GetAllDownloads(&items);
-    return items;
-  }
-
-  void StartServer() {
-    embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
-        &content::SlowDownloadHttpResponse::HandleSlowDownloadRequest));
-    base::FilePath test_file_directory;
-    base::PathService::Get(chrome::DIR_TEST_DATA, &test_file_directory);
-    embedded_test_server()->ServeFilesFromDirectory(test_file_directory);
-    ASSERT_TRUE(embedded_test_server()->Start());
-  }
-
-  void StartDownload(base::StringPiece url, bool observe_terminally) {
-    std::unique_ptr<content::DownloadTestObserver> observer;
-    if (observe_terminally) {
-      observer = std::make_unique<content::DownloadTestObserverTerminal>(
-          browser()->profile()->GetDownloadManager(), 1,
-          content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_QUIT);
-    } else {
-      observer = std::make_unique<content::DownloadTestObserverInProgress>(
-          browser()->profile()->GetDownloadManager(), 1);
-    }
-
-    ui_test_utils::NavigateToURLWithDisposition(
-        browser(), embedded_test_server()->GetURL(url),
-        WindowOpenDisposition::NEW_FOREGROUND_TAB,
-        ui_test_utils::BROWSER_TEST_NONE);
-    observer->WaitForFinished();
-  }
-
-  void WaitForBubbleCreation() {
-    // Download Toolbar button should exist.
-    ASSERT_NE(GetDownloadToolbarButtonView(), nullptr);
-    base::RunLoop creation_wait;
-    GetDownloadToolbarButtonView()->SetBubbleCreatedCallbackForTesting(
-        creation_wait.QuitClosure());
-    creation_wait.Run();
-  }
-
-  void CompleteSlowDownload() {
-    content::DownloadTestObserverTerminal observer(
-        browser()->profile()->GetDownloadManager(), 1,
-        content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_QUIT);
-    ui_test_utils::NavigateToURLWithDisposition(
-        browser(),
-        embedded_test_server()->GetURL(
-            content::SlowHttpResponse::kFinishSlowResponseUrl),
-        WindowOpenDisposition::NEW_FOREGROUND_TAB,
-        ui_test_utils::BROWSER_TEST_NONE);
-    observer.WaitForFinished();
-  }
-
-  void SetUp() override {
-    StartServer();
-    InProcessBrowserTest::SetUp();
-  }
-
-  // DialogBrowserTest:
-  void ShowUi(const std::string& name) override {
-    content::DownloadManager::DownloadVector items;
-
-    auto* testcase_iter = kNameToTestcase.find(name);
-    ASSERT_TRUE(testcase_iter != kNameToTestcase.end());
-    testcase_ = testcase_iter->second;
-    switch (testcase_) {
-      case Testcase::kInProgressDownload:
-        StartDownload(content::SlowDownloadHttpResponse::kKnownSizeUrl,
-                      /*observe_terminally=*/false);
-        WaitForBubbleCreation();
-        break;
-      case Testcase::kCompletedDownload:
-        StartDownload(content::SlowDownloadHttpResponse::kKnownSizeUrl,
-                      /*observe_terminally=*/false);
-        WaitForBubbleCreation();
-        EXPECT_TRUE(GetDownloadToolbarButtonView()->IsBubbleVisible());
-        ClickDownloadToolbarButton(/*is_creation=*/false);
-        EXPECT_FALSE(GetDownloadToolbarButtonView()->IsBubbleVisible());
-        items = GetDownloadItems();
-        ASSERT_EQ(items.size(), 1u);
-        EXPECT_EQ(items[0]->GetState(), download::DownloadItem::IN_PROGRESS);
-        CompleteSlowDownload();
-        WaitForBubbleCreation();
-        break;
-      case Testcase::kFullView:
-        StartDownload(content::SlowDownloadHttpResponse::kKnownSizeUrl,
-                      /*observe_terminally=*/false);
-        CompleteSlowDownload();
-        WaitForBubbleCreation();
-        EXPECT_TRUE(GetDownloadToolbarButtonView()->IsBubbleVisible());
-        ClickDownloadToolbarButton(/*is_creation=*/false);
-        EXPECT_FALSE(GetDownloadToolbarButtonView()->IsBubbleVisible());
-        ClickDownloadToolbarButton(/*is_creation=*/true);
-        break;
-      case Testcase::kDangerousDownloadSubpage:
-        StartDownload(kDangerousUrl, /*observe_terminally=*/true);
-        WaitForBubbleCreation();
-        ClickDownloadItem();
-        break;
-    }
-  }
-
-  bool VerifyUi() override {
-    if (!DialogBrowserTest::VerifyUi())
-      return false;
-    DownloadToolbarButtonView* button = GetDownloadToolbarButtonView();
-    content::DownloadManager::DownloadVector items = GetDownloadItems();
-    if (items.size() != 1u) {
-      LOG(ERROR) << "There should be only one download item, not "
-                 << items.size();
-      return false;
-    }
-    switch (testcase_) {
-      case Testcase::kInProgressDownload:
-        EXPECT_TRUE(button->IsBubbleVisible());
-        EXPECT_TRUE(button->is_primary_partial_view_);
-        EXPECT_FALSE(button->security_view_->GetVisible());
-        EXPECT_EQ(button->download_list_size_, 1u);
-        EXPECT_EQ(items[0]->GetState(), download::DownloadItem::IN_PROGRESS);
-        items[0]->Cancel(true);
-        return true;
-      case Testcase::kCompletedDownload:
-        EXPECT_TRUE(button->IsBubbleVisible());
-        EXPECT_TRUE(button->is_primary_partial_view_);
-        EXPECT_FALSE(button->security_view_->GetVisible());
-        EXPECT_EQ(button->download_list_size_, 1u);
-        EXPECT_EQ(items[0]->GetState(), download::DownloadItem::COMPLETE);
-        return true;
-      case Testcase::kFullView:
-        EXPECT_TRUE(button->IsBubbleVisible());
-        EXPECT_FALSE(button->is_primary_partial_view_);
-        EXPECT_FALSE(button->security_view_->GetVisible());
-        EXPECT_EQ(button->download_list_size_, 1u);
-        EXPECT_EQ(items[0]->GetState(), download::DownloadItem::COMPLETE);
-        return true;
-      case Testcase::kDangerousDownloadSubpage:
-        EXPECT_TRUE(button->IsBubbleVisible());
-        EXPECT_TRUE(button->is_primary_partial_view_);
-        EXPECT_TRUE(button->security_view_->GetVisible());
-        EXPECT_EQ(button->download_list_size_, 1u);
-        items[0]->Cancel(true);
-        return true;
-    }
-    return false;
-  }
-
-  void TearDown() override {
-    ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
-    InProcessBrowserTest::TearDown();
-  }
-
- private:
-  Testcase testcase_;
-  base::test::ScopedFeatureList feature_list_;
-};
-
-IN_PROC_BROWSER_TEST_F(DownloadBubbleDialogBrowserTest,
-                       InvokeUi_InProgressDownload) {
-  ShowAndVerifyUi();
-}
-
-IN_PROC_BROWSER_TEST_F(DownloadBubbleDialogBrowserTest,
-                       InvokeUi_CompletedDownload) {
-  ShowAndVerifyUi();
-}
-
-IN_PROC_BROWSER_TEST_F(DownloadBubbleDialogBrowserTest, InvokeUi_FullView) {
-  ShowAndVerifyUi();
-}
-
-IN_PROC_BROWSER_TEST_F(DownloadBubbleDialogBrowserTest,
-                       InvokeUi_DangerousDownloadSubpage) {
-  ShowAndVerifyUi();
-}
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
index 43f3982f..9468597 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc
@@ -389,8 +389,6 @@
   LoadIcon();
   UpdateButtonsForItems();
   UpdateProgressBar();
-  ui_updated_for_dangerous_download_ = model_->IsDangerous();
-  NotifyIfDownloadDangerous();
 }
 
 void DownloadBubbleRowView::OnDownloadOpened() {
@@ -447,18 +445,5 @@
                      base::RepeatingClosure());
 }
 
-void DownloadBubbleRowView::SetNotifyDangerousDownloadCallbackForTesting(
-    base::OnceClosure callback) {
-  notify_dangerous_download_callback_for_testing_ = std::move(callback);
-  NotifyIfDownloadDangerous();
-}
-
-void DownloadBubbleRowView::NotifyIfDownloadDangerous() {
-  if (ui_updated_for_dangerous_download_ &&
-      notify_dangerous_download_callback_for_testing_) {
-    std::move(notify_dangerous_download_callback_for_testing_).Run();
-  }
-}
-
 BEGIN_METADATA(DownloadBubbleRowView, views::View)
 END_METADATA
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.h b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.h
index ddd68d33..0456a15 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_row_view.h
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_row_view.h
@@ -69,8 +69,6 @@
                                   float new_device_scale_factor) override;
 
  private:
-  friend class DownloadBubbleDialogBrowserTest;
-
   raw_ptr<views::MdTextButton> AddMainPageButton(
       DownloadCommands::Command command,
       const std::u16string& button_string);
@@ -92,12 +90,6 @@
   void OnDiscardButtonPressed();
   void OnMainButtonPressed();
 
-  // Callback for dangerous downloads for testing.
-  void SetNotifyDangerousDownloadCallbackForTesting(base::OnceClosure callback);
-  void NotifyIfDownloadDangerous();
-  base::OnceClosure notify_dangerous_download_callback_for_testing_;
-  bool ui_updated_for_dangerous_download_ = false;
-
   // TODO(bhatiarohit): Add platform-independent icons.
   // The icon for the file. We get platform-specific icons from IconLoader.
   raw_ptr<views::ImageView> icon_ = nullptr;
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_security_view.cc b/chrome/browser/ui/views/download/bubble/download_bubble_security_view.cc
index ef87f05..30bebc4 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_security_view.cc
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_security_view.cc
@@ -17,6 +17,7 @@
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/strings/grit/ui_strings.h"
+#include "ui/views/accessibility/view_accessibility.h"
 #include "ui/views/controls/button/checkbox.h"
 #include "ui/views/controls/button/image_button.h"
 #include "ui/views/controls/button/image_button_factory.h"
@@ -48,16 +49,16 @@
       gfx::Insets(ChromeLayoutProvider::Get()->GetDistanceMetric(
           views::DISTANCE_RELATED_CONTROL_VERTICAL)));
 
-  auto* back_button =
+  back_button_ =
       header->AddChildView(views::CreateVectorImageButtonWithNativeTheme(
           base::BindRepeating(
               &DownloadBubbleNavigationHandler::OpenPrimaryDialog,
               base::Unretained(navigation_handler_)),
           vector_icons::kArrowBackIcon, GetLayoutConstant(DOWNLOAD_ICON_SIZE)));
-  views::InstallCircleHighlightPathGenerator(back_button);
-  back_button->SetTooltipText(l10n_util::GetStringUTF16(IDS_ACCNAME_BACK));
-  back_button->SetProperty(views::kCrossAxisAlignmentKey,
-                           views::LayoutAlignment::kStart);
+  views::InstallCircleHighlightPathGenerator(back_button_);
+  back_button_->SetTooltipText(l10n_util::GetStringUTF16(IDS_ACCNAME_BACK));
+  back_button_->SetProperty(views::kCrossAxisAlignmentKey,
+                            views::LayoutAlignment::kStart);
 
   title_ = header->AddChildView(std::make_unique<views::Label>(
       std::u16string(), views::style::CONTEXT_DIALOG_TITLE,
@@ -293,6 +294,22 @@
   UpdateButtons();
 }
 
+void DownloadBubbleSecurityView::UpdateAccessibilityTextAndFocus() {
+  DownloadUIModel::BubbleUIInfo& ui_info = download_row_view_->ui_info();
+  // Announce that the subpage was opened to inform the user about the changes
+  // in the UI.
+#if BUILDFLAG(IS_MAC)
+  GetViewAccessibility().OverrideName(ui_info.warning_summary);
+  NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
+#else
+  GetViewAccessibility().AnnounceText(ui_info.warning_summary);
+#endif
+
+  // Focus the back button by default to ensure that focus is set when new
+  // content is displayed.
+  back_button_->RequestFocus();
+}
+
 DownloadBubbleSecurityView::DownloadBubbleSecurityView(
     DownloadBubbleUIController* bubble_controller,
     DownloadBubbleNavigationHandler* navigation_handler)
diff --git a/chrome/browser/ui/views/download/bubble/download_bubble_security_view.h b/chrome/browser/ui/views/download/bubble/download_bubble_security_view.h
index e18b0d3..8e164b6 100644
--- a/chrome/browser/ui/views/download/bubble/download_bubble_security_view.h
+++ b/chrome/browser/ui/views/download/bubble/download_bubble_security_view.h
@@ -16,6 +16,7 @@
 class Label;
 class ImageView;
 class StyledLabel;
+class ImageButton;
 }  // namespace views
 
 class DownloadBubbleUIController;
@@ -32,8 +33,15 @@
   DownloadBubbleSecurityView& operator=(const DownloadBubbleSecurityView&) =
       delete;
   ~DownloadBubbleSecurityView() override;
+
+  // Update the security view when a subpage is opened for a particular
+  // download.
   void UpdateSecurityView(DownloadBubbleRowView* download_row_view);
 
+  // Update the view after it is visible, in particular asking for focus and
+  // announcing accessibility text.
+  void UpdateAccessibilityTextAndFocus();
+
   raw_ptr<views::MdTextButton> keep_button_ = nullptr;
   raw_ptr<views::MdTextButton> discard_button_ = nullptr;
   raw_ptr<views::MdTextButton> bypass_deep_scan_button_ = nullptr;
@@ -48,6 +56,7 @@
   void AddIconAndText();
   void UpdateButtons();
   void AddButtons();
+
   void ProcessButtonClick(DownloadCommands::Command command,
                           bool is_first_button);
   views::MdTextButton* GetButtonForCommand(DownloadCommands::Command command);
@@ -61,6 +70,7 @@
   raw_ptr<views::Label> title_ = nullptr;
   raw_ptr<views::ImageView> icon_ = nullptr;
   raw_ptr<views::StyledLabel> styled_label_ = nullptr;
+  raw_ptr<views::ImageButton> back_button_ = nullptr;
   absl::optional<base::Time> warning_time_;
 };
 
diff --git a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc
index ba9f397..1cf01d7 100644
--- a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc
+++ b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.cc
@@ -69,22 +69,6 @@
   bubble_controller_.reset();
 }
 
-void DownloadToolbarButtonView::SetBubbleCreatedCallbackForTesting(
-    base::OnceClosure callback) {
-  // The bubble already exists, run the callback right away. This can happen
-  // when starting a new download.
-  if (bubble_delegate_) {
-    std::move(callback).Run();
-    return;
-  }
-  bubble_created_callback_for_testing_ = std::move(callback);
-}
-
-void DownloadToolbarButtonView::SetBubbleDestroyedCallbackForTesting(
-    base::OnceClosure callback) {
-  bubble_destroyed_callback_for_testing_ = std::move(callback);
-}
-
 void DownloadToolbarButtonView::PaintButtonContents(gfx::Canvas* canvas) {
   DownloadDisplayController::ProgressInfo progress_info =
       controller_->GetProgress();
@@ -131,6 +115,9 @@
 }
 
 void DownloadToolbarButtonView::Hide() {
+  if (bubble_delegate_) {
+    CloseDialog(views::Widget::ClosedReason::kUnspecified);
+  }
   SetVisible(false);
   PreferredSizeChanged();
 }
@@ -210,6 +197,7 @@
   security_view_->UpdateSecurityView(download_row_view);
   primary_view_->SetVisible(false);
   security_view_->SetVisible(true);
+  security_view_->UpdateAccessibilityTextAndFocus();
   ResizeDialog();
 }
 
@@ -229,11 +217,6 @@
   bubble_delegate_ = nullptr;
   primary_view_ = nullptr;
   security_view_ = nullptr;
-  download_row_list_view_ = nullptr;
-  download_list_size_ = 0;
-  if (bubble_destroyed_callback_for_testing_) {
-    std::move(bubble_destroyed_callback_for_testing_).Run();
-  }
 }
 
 // TODO(bhatiarohit): Remove the margin around the bubble.
@@ -245,6 +228,8 @@
   std::unique_ptr<views::BubbleDialogDelegate> bubble_delegate =
       std::make_unique<views::BubbleDialogDelegate>(
           this, views::BubbleBorder::TOP_RIGHT);
+  bubble_delegate->SetTitle(
+      l10n_util::GetStringUTF16(IDS_DOWNLOAD_BUBBLE_HEADER_TEXT));
   bubble_delegate->SetShowTitle(false);
   bubble_delegate->SetShowCloseButton(false);
   bubble_delegate->SetButtons(ui::DIALOG_BUTTON_NONE);
@@ -270,9 +255,6 @@
   bubble_delegate_ = bubble_delegate.get();
   views::BubbleDialogDelegate::CreateBubble(std::move(bubble_delegate));
   bubble_delegate_->GetWidget()->Show();
-  if (bubble_created_callback_for_testing_) {
-    std::move(bubble_created_callback_for_testing_).Run();
-  }
 }
 
 // If the bubble delegate is set (either the main or the partial view), the
@@ -293,19 +275,17 @@
   if (is_primary_partial_view_ && model_list.empty())
     return nullptr;
 
-  download_list_size_ = model_list.size();
-  auto scroll_view = std::make_unique<views::ScrollView>();
-  download_row_list_view_ = scroll_view->SetContents(
-      std::make_unique<DownloadBubbleRowListView>(is_primary_partial_view_));
+  auto row_list_view =
+      std::make_unique<DownloadBubbleRowListView>(is_primary_partial_view_);
   for (DownloadUIModel::DownloadUIModelPtr& model : model_list) {
     // raw pointer is safe as the toolbar owns the bubble, which owns an
     // individual row view.
-    download_row_list_view_->AddChildView(
-        std::make_unique<DownloadBubbleRowView>(
-            std::move(model), download_row_list_view_.get(),
-            bubble_controller_.get(), this));
+    row_list_view->AddChildView(std::make_unique<DownloadBubbleRowView>(
+        std::move(model), row_list_view.get(), bubble_controller_.get(), this));
   }
 
+  auto scroll_view = std::make_unique<views::ScrollView>();
+  scroll_view->SetContents(std::move(row_list_view));
   scroll_view->ClipHeightTo(0, kMaxHeightForRowList);
   scroll_view->SetHorizontalScrollBarMode(
       views::ScrollView::ScrollBarMode::kDisabled);
diff --git a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.h b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.h
index 07cbefe..2e92a16 100644
--- a/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.h
+++ b/chrome/browser/ui/views/download/bubble/download_toolbar_button_view.h
@@ -9,7 +9,6 @@
 #include "chrome/browser/download/bubble/download_display.h"
 #include "chrome/browser/download/bubble/download_icon_state.h"
 #include "chrome/browser/download/download_ui_model.h"
-#include "chrome/browser/ui/views/download/bubble/download_bubble_security_view.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_button.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/gfx/animation/throb_animation.h"
@@ -20,7 +19,7 @@
 class DownloadDisplayController;
 class DownloadBubbleUIController;
 class DownloadBubbleRowView;
-class DownloadBubbleRowListView;
+class DownloadBubbleSecurityView;
 
 class DownloadBubbleNavigationHandler {
  public:
@@ -68,12 +67,6 @@
   }
 
  private:
-  // For testing.
-  friend class DownloadBubbleDialogBrowserTest;
-  bool IsBubbleVisible() { return bubble_delegate_ != nullptr; }
-  void SetBubbleCreatedCallbackForTesting(base::OnceClosure callback);
-  void SetBubbleDestroyedCallbackForTesting(base::OnceClosure callback);
-
   // views::Button overrides:
   void PaintButtonContents(gfx::Canvas* canvas) override;
 
@@ -97,12 +90,6 @@
   raw_ptr<View> primary_view_ = nullptr;
   raw_ptr<DownloadBubbleSecurityView> security_view_ = nullptr;
 
-  // For testing
-  base::OnceClosure bubble_created_callback_for_testing_;
-  base::OnceClosure bubble_destroyed_callback_for_testing_;
-  size_t download_list_size_ = 0;
-  raw_ptr<DownloadBubbleRowListView> download_row_list_view_ = nullptr;
-
   gfx::SlideAnimation scanning_animation_{this};
 
   base::WeakPtrFactory<DownloadToolbarButtonView> weak_factory_{this};
diff --git a/chrome/browser/ui/views/download/download_item_view.cc b/chrome/browser/ui/views/download/download_item_view.cc
index 18b96ef1..3c879cd0 100644
--- a/chrome/browser/ui/views/download/download_item_view.cc
+++ b/chrome/browser/ui/views/download/download_item_view.cc
@@ -1341,15 +1341,23 @@
               enterprise_connectors::AnalysisConnector::FILE_DOWNLOADED, tag)
           .value_or(GURL());
 
+  bool bypass_justification_required =
+      connectors_service
+          ->GetBypassJustificationRequired(
+              enterprise_connectors::AnalysisConnector::FILE_DOWNLOADED, tag)
+          .value_or(false);
+
   // This dialog opens itself, and is thereafter owned by constrained window
   // code.
   new enterprise_connectors::ContentAnalysisDialog(
       std::make_unique<enterprise_connectors::ContentAnalysisDownloadsDelegate>(
           filename, custom_message, learn_more_url,
+          bypass_justification_required,
           base::BindOnce(&DownloadItemView::ExecuteCommand,
                          base::Unretained(this), DownloadCommands::KEEP),
           base::BindOnce(&DownloadItemView::ExecuteCommand,
-                         base::Unretained(this), DownloadCommands::DISCARD)),
+                         base::Unretained(this), DownloadCommands::DISCARD),
+          model_->download()),
       shelf_->browser()->tab_strip_model()->GetActiveWebContents(),
       safe_browsing::DeepScanAccessPoint::DOWNLOAD, /* file_count */ 1, state);
 }
diff --git a/chrome/browser/ui/views/first_run_dialog.cc b/chrome/browser/ui/views/first_run_dialog.cc
index 6db49d7..377a2c1d 100644
--- a/chrome/browser/ui/views/first_run_dialog.cc
+++ b/chrome/browser/ui/views/first_run_dialog.cc
@@ -108,11 +108,17 @@
 
 void FirstRunDialog::Done() {
   CHECK(!quit_runloop_.is_null());
+
+  if (!closed_through_accept_button_) {
+    ChangeMetricsReportingState(false);
+  }
+
   quit_runloop_.Run();
 }
 
 bool FirstRunDialog::Accept() {
   GetWidget()->Hide();
+  closed_through_accept_button_ = true;
 
 #if BUILDFLAG(IS_MAC)
   ChangeMetricsReportingState(report_crashes_->GetChecked());
diff --git a/chrome/browser/ui/views/first_run_dialog.h b/chrome/browser/ui/views/first_run_dialog.h
index 99d0dbe5..fb02b74 100644
--- a/chrome/browser/ui/views/first_run_dialog.h
+++ b/chrome/browser/ui/views/first_run_dialog.h
@@ -38,6 +38,11 @@
   // views::WidgetDelegate:
   void WindowClosing() override;
 
+  // Used to determine whether the dialog was closed by pressing the accept
+  // button. The user might close the dialog by pressing the close button
+  // instead, in which we default to disabling metrics reporting.
+  bool closed_through_accept_button_ = false;
+
   views::Checkbox* make_default_ = nullptr;
   views::Checkbox* report_crashes_ = nullptr;
   base::RepeatingClosure quit_runloop_;
diff --git a/chrome/browser/ui/views/page_info/accuracy_tip_bubble_view.cc b/chrome/browser/ui/views/page_info/accuracy_tip_bubble_view.cc
index ad0019bb..9e8a3c6 100644
--- a/chrome/browser/ui/views/page_info/accuracy_tip_bubble_view.cc
+++ b/chrome/browser/ui/views/page_info/accuracy_tip_bubble_view.cc
@@ -99,8 +99,7 @@
                              parent_window,
                              PageInfoBubbleViewBase::BUBBLE_ACCURACY_TIP,
                              web_contents),
-      close_callback_(std::move(close_callback)),
-      web_contents_(web_contents) {
+      close_callback_(std::move(close_callback)) {
   DCHECK(status == accuracy_tips::AccuracyTipStatus::kShowAccuracyTip);
   set_close_on_deactivate(false);
 
@@ -175,9 +174,9 @@
 }
 
 AccuracyTipBubbleView::~AccuracyTipBubbleView() {
-  if (web_contents_) {
+  if (web_contents()) {
     permissions::PermissionRequestManager* permission_request_manager =
-        permissions::PermissionRequestManager::FromWebContents(web_contents_);
+        permissions::PermissionRequestManager::FromWebContents(web_contents());
     if (permission_request_manager) {
       permission_request_manager->RemoveObserver(this);
     }
@@ -236,11 +235,6 @@
   GetWidget()->Close();
 }
 
-void AccuracyTipBubbleView::WebContentsDestroyed() {
-  web_contents_ = nullptr;
-  PageInfoBubbleViewBase::WebContentsDestroyed();
-}
-
 void AccuracyTipBubbleView::DidChangeVisibleSecurityState() {
   // Do nothing. (Base class closes the bubble.)
 }
diff --git a/chrome/browser/ui/views/page_info/accuracy_tip_bubble_view.h b/chrome/browser/ui/views/page_info/accuracy_tip_bubble_view.h
index eb43ef4e..0fdaad53 100644
--- a/chrome/browser/ui/views/page_info/accuracy_tip_bubble_view.h
+++ b/chrome/browser/ui/views/page_info/accuracy_tip_bubble_view.h
@@ -60,10 +60,6 @@
   // permissions::PermissionRequestManager::Observer:
   void OnBubbleAdded() override;
 
- protected:
-  // WebContentsObserver:
-  void WebContentsDestroyed() override;
-
  private:
   void OpenHelpCenter();
   void OnSecondaryButtonClicked(AccuracyTipInteraction action);
@@ -73,9 +69,6 @@
 
   base::OnceCallback<void(AccuracyTipInteraction)> close_callback_;
   AccuracyTipInteraction action_taken_ = AccuracyTipInteraction::kNoAction;
-  // We hold a raw pointer to the WebContents passed in during construction, but
-  // we make sure to set it back to nullptr when the WebContents is destroyed.
-  raw_ptr<content::WebContents> web_contents_ = nullptr;
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_PAGE_INFO_ACCURACY_TIP_BUBBLE_VIEW_H_
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_mac_win_linux.cc b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_mac_win_linux.cc
index 8119dd5..a379b2be 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_mac_win_linux.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_browsertest_mac_win_linux.cc
@@ -47,15 +47,6 @@
   helper_.CheckPlatformShortcutNotExists(Site::kSiteA);
 }
 
-IN_PROC_BROWSER_TEST_F(WebAppIntegrationBrowserTestMacWinLinux,
-                       CheckDeletePlatformShortcut) {
-  helper_.DeletePlatformShortcut(Site::kSiteA);
-  helper_.InstallCreateShortcutWindowed(Site::kSiteA);
-  helper_.CheckPlatformShortcutAndIcon(Site::kSiteA);
-  helper_.DeletePlatformShortcut(Site::kSiteA);
-  helper_.CheckPlatformShortcutNotExists(Site::kSiteA);
-}
-
 // Generated tests:
 
 IN_PROC_BROWSER_TEST_F(
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
index 689c8e5..4987936 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.cc
@@ -301,6 +301,27 @@
   }
   return shortcut_profile;
 }
+
+bool IsShortcutAndIconCorrectOnWin(Profile* profile,
+                                   const std::string& name,
+                                   base::FilePath shortcut_dir,
+                                   SkColor expected_icon_pixel_color) {
+  std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
+  base::FileEnumerator enumerator(shortcut_dir, false,
+                                  base::FileEnumerator::FILES);
+  while (!enumerator.Next().empty()) {
+    std::wstring shortcut_filename = enumerator.GetInfo().GetName().value();
+    if (re2::RE2::FullMatch(converter.to_bytes(shortcut_filename),
+                            name + "(.*).lnk")) {
+      base::FilePath shortcut_path = shortcut_dir.Append(shortcut_filename);
+      if (GetShortcutProfile(shortcut_path) == profile->GetBaseName()) {
+        SkColor icon_pixel_color = GetIconTopLeftColor(shortcut_path);
+        return (icon_pixel_color == expected_icon_pixel_color);
+      }
+    }
+  }
+  return false;
+}
 #endif
 
 #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
@@ -1001,86 +1022,6 @@
 #endif
 }
 
-void WebAppIntegrationTestDriver::DeletePlatformShortcut(Site site) {
-  if (!before_state_change_action_state_ && !after_state_change_action_state_)
-    return;
-  BeforeStateChangeAction(__FUNCTION__);
-  base::ScopedAllowBlockingForTesting allow_blocking;
-  AppId app_id = GetAppIdBySiteMode(site);
-  std::string app_name = provider()->registrar().GetAppShortName(app_id);
-  if (app_name.empty()) {
-    ASSERT_TRUE(base::Contains(g_site_to_app_name, site));
-    app_name = g_site_to_app_name.find(site)->second;
-  }
-#if BUILDFLAG(IS_WIN)
-  base::FilePath desktop_shortcut_path =
-      GetShortcutPath(shortcut_override_->desktop.GetPath(), app_name, app_id);
-  ASSERT_TRUE(base::PathExists(desktop_shortcut_path));
-  base::DeleteFile(desktop_shortcut_path);
-  base::FilePath app_menu_shortcut_path = GetShortcutPath(
-      shortcut_override_->application_menu.GetPath(), app_name, app_id);
-  ASSERT_TRUE(base::PathExists(app_menu_shortcut_path));
-  base::DeleteFile(app_menu_shortcut_path);
-  AfterStateChangeAction();
-#elif BUILDFLAG(IS_MAC)
-  base::FilePath app_folder_shortcut_path = GetShortcutPath(
-      shortcut_override_->chrome_apps_folder.GetPath(), app_name, app_id);
-  ASSERT_TRUE(base::PathExists(app_folder_shortcut_path));
-  base::DeleteFile(app_folder_shortcut_path);
-  AfterStateChangeAction();
-#elif BUILDFLAG(IS_LINUX)
-  base::FilePath desktop_shortcut_path =
-      GetShortcutPath(shortcut_override_->desktop.GetPath(), app_name, app_id);
-  ASSERT_TRUE(base::PathExists(desktop_shortcut_path));
-  base::DeleteFile(desktop_shortcut_path);
-  AfterStateChangeAction();
-#else
-  NOTREACHED() << "Not implemented on Chrome OS.";
-#endif
-}
-
-base::FilePath WebAppIntegrationTestDriver::GetShortcutPath(
-    base::FilePath shortcut_dir,
-    const std::string& app_name,
-    const AppId& app_id) {
-  base::FilePath shortcut_path;
-#if BUILDFLAG(IS_WIN)
-  std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
-  base::FileEnumerator enumerator(shortcut_dir, false,
-                                  base::FileEnumerator::FILES);
-  while (!enumerator.Next().empty()) {
-    std::wstring shortcut_filename = enumerator.GetInfo().GetName().value();
-    if (re2::RE2::FullMatch(converter.to_bytes(shortcut_filename),
-                            app_name + "(.*).lnk")) {
-      shortcut_path = shortcut_dir.Append(shortcut_filename);
-      if (GetShortcutProfile(shortcut_path) == profile()->GetBaseName())
-        return shortcut_path;
-    }
-  }
-#elif BUILDFLAG(IS_MAC)
-  std::string shortcut_filename = app_name + ".app";
-  shortcut_path = shortcut_dir.Append(shortcut_filename);
-  AppShimRegistry* registry = AppShimRegistry::Get();
-  // Exits early if the app id is empty because the verification won't work.
-  // TODO(crbug.com/1289865): Figure a way to find the profile that has the app
-  //                          installed without using app ID.
-  if (!app_id.empty()) {
-    std::set<base::FilePath> app_installed_profiles =
-        registry->GetInstalledProfilesForApp(app_id);
-    if (app_installed_profiles.find(profile()->GetPath()) !=
-        app_installed_profiles.end())
-      return shortcut_path;
-  }
-#elif BUILDFLAG(IS_LINUX)
-  std::string shortcut_filename =
-      "chrome-" + app_id + "-" + profile()->GetBaseName().value() + ".desktop";
-  shortcut_path = shortcut_dir.Append(shortcut_filename);
-  if (base::PathExists(shortcut_path))
-    return shortcut_path;
-#endif
-  return shortcut_path;
-}
-
 void WebAppIntegrationTestDriver::CheckAppSettingsAppState(
     Profile* profile,
     const AppState& app_state) {
@@ -1806,10 +1747,9 @@
 #elif BUILDFLAG(IS_WIN)
   DCHECK(base::Contains(g_app_name_icon_color, app_state->name));
   SkColor color = g_app_name_icon_color.find(app_state->name)->second;
-  base::FilePath startup_shortcut_path = GetShortcutPath(
-      shortcut_override_->startup.GetPath(), app_state->name, app_state->id);
-  ASSERT_TRUE(base::PathExists(startup_shortcut_path));
-  ASSERT_TRUE(GetIconTopLeftColor(startup_shortcut_path) == color);
+  ASSERT_TRUE(IsShortcutAndIconCorrectOnWin(
+      profile(), app_state->name, shortcut_override_->startup.GetPath(),
+      color));
 #elif BUILDFLAG(IS_MAC)
   std::string shortcut_filename = app_state->name + ".app";
   base::FilePath app_shortcut_path =
@@ -1833,9 +1773,11 @@
   ASSERT_FALSE(base::PathExists(
       shortcut_override_->startup.GetPath().Append(shortcut_filename)));
 #elif BUILDFLAG(IS_WIN)
-  base::FilePath startup_shortcut_path = GetShortcutPath(
-      shortcut_override_->startup.GetPath(), app_state->name, app_state->id);
-  ASSERT_FALSE(base::PathExists(startup_shortcut_path));
+  DCHECK(base::Contains(g_app_name_icon_color, app_state->name));
+  SkColor color = g_app_name_icon_color.find(app_state->name)->second;
+  ASSERT_FALSE(IsShortcutAndIconCorrectOnWin(
+      profile(), app_state->name, shortcut_override_->startup.GetPath(),
+      color));
 #elif BUILDFLAG(IS_MAC)
   std::string shortcut_filename = app_state->name + ".app";
   base::FilePath app_shortcut_path =
@@ -2399,28 +2341,41 @@
 #endif
 
 #if BUILDFLAG(IS_WIN)
-  base::FilePath desktop_shortcut_path =
-      GetShortcutPath(shortcut_override_->desktop.GetPath(), name, id);
-  base::FilePath application_menu_shortcut_path =
-      GetShortcutPath(shortcut_override_->application_menu.GetPath(), name, id);
-  if (base::PathExists(desktop_shortcut_path) &&
-      base::PathExists(application_menu_shortcut_path))
-    is_shortcut_and_icon_correct =
-        (GetIconTopLeftColor(desktop_shortcut_path) ==
-             expected_icon_pixel_color &&
-         GetIconTopLeftColor(application_menu_shortcut_path) ==
-             expected_icon_pixel_color);
+  is_shortcut_and_icon_correct =
+      (IsShortcutAndIconCorrectOnWin(profile, name,
+                                     shortcut_override_->desktop.GetPath(),
+                                     expected_icon_pixel_color) &&
+       IsShortcutAndIconCorrectOnWin(
+           profile, name, shortcut_override_->application_menu.GetPath(),
+           expected_icon_pixel_color));
 #elif BUILDFLAG(IS_MAC)
-  base::FilePath app_shortcut_path = GetShortcutPath(
-      shortcut_override_->chrome_apps_folder.GetPath(), name, id);
-  if (base::PathExists(app_shortcut_path)) {
+  std::string shortcut_filename = name + ".app";
+  base::FilePath app_shortcut_path =
+      shortcut_override_->chrome_apps_folder.GetPath().Append(
+          shortcut_filename);
+  AppShimRegistry* registry = AppShimRegistry::Get();
+  bool is_app_profile_found = false;
+  // Exits early if the app id is empty because the verification won't work.
+  // TODO(crbug.com/1289865): Figure a way to find the profile that has the app
+  //                          installed without using app ID.
+  if (id.empty())
+    return false;
+  std::set<base::FilePath> app_installed_profiles =
+      registry->GetInstalledProfilesForApp(id);
+  is_app_profile_found = (app_installed_profiles.find(profile->GetPath()) !=
+                          app_installed_profiles.end());
+  bool shortcut_exists =
+      (base::PathExists(app_shortcut_path) && is_app_profile_found);
+  if (shortcut_exists) {
     SkColor icon_pixel_color = GetIconTopLeftColor(app_shortcut_path);
     is_shortcut_and_icon_correct =
         (icon_pixel_color == expected_icon_pixel_color);
   }
 #elif BUILDFLAG(IS_LINUX)
+  std::string shortcut_filename =
+      "chrome-" + id + "-" + profile->GetBaseName().value() + ".desktop";
   base::FilePath desktop_shortcut_path =
-      GetShortcutPath(shortcut_override_->desktop.GetPath(), name, id);
+      shortcut_override_->desktop.GetPath().Append(shortcut_filename);
   if (base::PathExists(desktop_shortcut_path)) {
     is_shortcut_and_icon_correct = IconManagerCheckIconTopLeftColor(
         provider()->icon_manager(), id, {kLauncherIconSize, kInstallIconSize},
diff --git a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.h b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.h
index c4ca9e27..31ce948 100644
--- a/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.h
+++ b/chrome/browser/ui/views/web_apps/web_app_integration_test_driver.h
@@ -204,7 +204,6 @@
   void ApplyRunOnOsLoginPolicyAllowed(Site site);
   void ApplyRunOnOsLoginPolicyBlocked(Site site);
   void ApplyRunOnOsLoginPolicyRunWindowed(Site site);
-  void DeletePlatformShortcut(Site site);
   void RemoveRunOnOsLoginPolicy(Site site);
   void LaunchFromChromeApps(Site site);
   void LaunchFromLaunchIcon(Site site);
@@ -297,9 +296,6 @@
   content::WebContents* GetCurrentTab(Browser* browser);
   GURL GetInScopeURL(Site site);
   GURL GetScopeForSiteMode(Site site);
-  base::FilePath GetShortcutPath(base::FilePath shortcut_dir,
-                                 const std::string& app_name,
-                                 const AppId& app_id);
   GURL GetURLForSiteMode(Site site);
   void InstallCreateShortcut(bool open_in_window);
 
diff --git a/chrome/browser/ui/views/webid/account_selection_bubble_view.cc b/chrome/browser/ui/views/webid/account_selection_bubble_view.cc
index 89c1f714..7ac2387 100644
--- a/chrome/browser/ui/views/webid/account_selection_bubble_view.cc
+++ b/chrome/browser/ui/views/webid/account_selection_bubble_view.cc
@@ -17,6 +17,7 @@
 #include "content/public/browser/storage_partition.h"
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "skia/ext/image_operations.h"
+#include "ui/accessibility/ax_role_properties.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/gfx/image/canvas_image_source.h"
@@ -32,6 +33,7 @@
 #include "ui/views/layout/layout_types.h"
 #include "ui/views/view_class_properties.h"
 #include "ui/views/view_utils.h"
+#include "ui/views/widget/widget.h"
 
 namespace {
 
@@ -98,6 +100,14 @@
   const std::u16string letter_;
 };
 
+void SendAccessibilityEvent(views::Widget* widget) {
+  if (!widget)
+    return;
+
+  widget->GetRootView()->NotifyAccessibilityEvent(ax::mojom::Event::kAlert,
+                                                  true);
+}
+
 }  // namespace
 
 AccountSelectionBubbleView::AccountSelectionBubbleView(
@@ -228,7 +238,7 @@
   if (brand_text_color_)
     button->SetEnabledTextColors(brand_text_color_);
   button->SetProminent(true);
-  row->AddChildView(std::move(button));
+  continue_button_ = row->AddChildView(std::move(button));
 
   // Do not add disclosure text if this is a sign in.
   if (account.login_state == Account::LoginState::kSignIn)
@@ -252,6 +262,35 @@
   disclosure_label->SetDefaultTextStyle(views::style::STYLE_SECONDARY);
 
   std::vector<size_t> offsets;
+
+  if (client_data_.privacy_policy_url.is_empty() &&
+      client_data_.terms_of_service_url.is_empty()) {
+    // Case for both the privacy policy and terms of service URLs are missing.
+    std::u16string disclosure_text = l10n_util::GetStringFUTF16(
+        IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP_OR_TOS,
+        {idp_etld_plus_one_});
+    disclosure_label->SetText(disclosure_text);
+    return row;
+  }
+
+  if (client_data_.privacy_policy_url.is_empty()) {
+    // Case for when we only need to add a link for terms of service URL, but
+    // not privacy policy. We use two placeholders for the start and end of
+    // 'terms of service' in order to style that text as a link.
+    std::u16string disclosure_text = l10n_util::GetStringFUTF16(
+        IDS_ACCOUNT_SELECTION_DATA_SHARING_CONSENT_NO_PP,
+        {idp_etld_plus_one_, std::u16string(), std::u16string()}, &offsets);
+    disclosure_label->SetText(disclosure_text);
+    // Add link styling for terms of service url.
+    disclosure_label->AddStyleRange(
+        gfx::Range(offsets[1], offsets[2]),
+        views::StyledLabel::RangeStyleInfo::CreateForLink(
+            base::BindRepeating(&AccountSelectionBubbleView::OnLinkClicked,
+                                weak_ptr_factory_.GetWeakPtr(),
+                                client_data_.terms_of_service_url)));
+    return row;
+  }
+
   if (client_data_.terms_of_service_url.is_empty()) {
     // Case for when we only need to add a link for privacy policy URL, but not
     // terms of service. We use two placeholders for the start and end of
@@ -398,6 +437,9 @@
   AddChildView(CreateAccountChooser(accounts));
   SizeToContents();
   PreferredSizeChanged();
+
+  continue_button_->RequestFocus();
+  SendAccessibilityEvent(GetWidget());
 }
 
 void AccountSelectionBubbleView::OnAccountSelected(
@@ -423,6 +465,8 @@
   AddChildView(row.release());
   SizeToContents();
   PreferredSizeChanged();
+
+  SendAccessibilityEvent(GetWidget());
 }
 
 void AccountSelectionBubbleView::RemoveNonHeaderChildViews() {
diff --git a/chrome/browser/ui/views/webid/account_selection_bubble_view.h b/chrome/browser/ui/views/webid/account_selection_bubble_view.h
index 8a4664f..2f28f24 100644
--- a/chrome/browser/ui/views/webid/account_selection_bubble_view.h
+++ b/chrome/browser/ui/views/webid/account_selection_bubble_view.h
@@ -113,10 +113,13 @@
   const content::ClientIdData client_data_;
 
   // View containing the logo of the identity provider and the title.
-  views::View* header_view_;
+  views::View* header_view_{nullptr};
 
   // View containing the bubble title.
-  views::Label* title_label_;
+  views::Label* title_label_{nullptr};
+
+  // View containing the continue button.
+  views::View* continue_button_{nullptr};
 
   // Used to ensure that callbacks are not run if the AccountSelectionBubbleView
   // is destroyed.
diff --git a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
index e50ee560..7e321bbd 100644
--- a/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
+++ b/chrome/browser/ui/views/webid/fedcm_account_selection_view_desktop.cc
@@ -40,12 +40,7 @@
   notify_delegate_of_dismiss_ = false;
   Close();
 
-  Browser* browser =
-      chrome::FindBrowserWithWebContents(delegate_->GetWebContents());
-  if (!browser)
-    return;
-
-  browser->tab_strip_model()->RemoveObserver(this);
+  TabStripModelObserver::StopObservingAll(this);
 }
 
 void FedCmAccountSelectionView::Show(
diff --git a/chrome/browser/ui/web_applications/system_web_app_delegate_ui_impl.cc b/chrome/browser/ui/web_applications/system_web_app_delegate_ui_impl.cc
index afdf1d92..6f11a821 100644
--- a/chrome/browser/ui/web_applications/system_web_app_delegate_ui_impl.cc
+++ b/chrome/browser/ui/web_applications/system_web_app_delegate_ui_impl.cc
@@ -21,6 +21,9 @@
 
 namespace web_app {
 
+// TODO(crbug.com/1231886): Reduce code duplication between SWA launch code and
+// web app launch code, so SWAs can easily maintain feature parity with regular
+// web apps (e.g. launch_handler behaviours).
 Browser* SystemWebAppDelegate::LaunchAndNavigateSystemWebApp(
     Profile* profile,
     WebAppProvider* provider,
diff --git a/chrome/browser/ui/web_applications/system_web_app_ui_utils.h b/chrome/browser/ui/web_applications/system_web_app_ui_utils.h
index faeb754..8156e5a 100644
--- a/chrome/browser/ui/web_applications/system_web_app_ui_utils.h
+++ b/chrome/browser/ui/web_applications/system_web_app_ui_utils.h
@@ -91,6 +91,9 @@
 
 // Implementation of LaunchSystemWebApp. Do not use this before discussing your
 // use case with the System Web Apps team.
+//
+// This method returns `nullptr` if the app aborts the launch (e.g. delaying the
+// launch after some async operation).
 Browser* LaunchSystemWebAppImpl(Profile* profile,
                                 SystemAppType type,
                                 const GURL& url,
diff --git a/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc b/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc
index a5fbbb8..7814420 100644
--- a/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc
+++ b/chrome/browser/ui/web_applications/test/system_web_app_interactive_uitest.cc
@@ -1089,14 +1089,6 @@
     return menu;
   }
 
-  size_t GetSystemWebAppBrowserCount(SystemAppType type) {
-    auto* browser_list = BrowserList::GetInstance();
-    return std::count_if(
-        browser_list->begin(), browser_list->end(), [&](Browser* browser) {
-          return web_app::IsBrowserForSystemWebApp(browser, type);
-        });
-  }
-
   void ExpectMenuCommandLaunchesSystemWebApp(
       std::unique_ptr<ui::MenuModel> menu,
       int command_id,
diff --git a/chrome/browser/ui/web_applications/web_app_launch_process.cc b/chrome/browser/ui/web_applications/web_app_launch_process.cc
index aaa11f0..9b5c4514 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_process.cc
+++ b/chrome/browser/ui/web_applications/web_app_launch_process.cc
@@ -98,18 +98,21 @@
 #endif
 
   // System Web Apps have their own launch code path.
-  // TODO(crbug.com/1231886): Don't use a separate code path so that SWAs can
-  // maintain feature parity with regular web apps (e.g. launch_handler
-  // behaviours).
-  content::WebContents* web_contents = MaybeLaunchSystemWebApp(launch_url);
-  if (web_contents)
-    return web_contents;
+  absl::optional<SystemAppType> system_app_type =
+      GetSystemWebAppTypeForAppId(&profile_, params_.app_id);
+  if (system_app_type) {
+    Browser* browser = LaunchSystemWebAppImpl(&profile_, *system_app_type,
+                                              launch_url, params_);
+
+    return browser ? browser->tab_strip_model()->GetActiveWebContents()
+                   : nullptr;
+  }
 
   auto [browser, is_new_browser] = EnsureBrowser();
 
   NavigateResult navigate_result =
       MaybeNavigateBrowser(browser, is_new_browser, launch_url, share_target);
-  web_contents = navigate_result.web_contents;
+  content::WebContents* web_contents = navigate_result.web_contents;
   if (!web_contents)
     return nullptr;
 
@@ -229,18 +232,6 @@
   }
 }
 
-content::WebContents* WebAppLaunchProcess::MaybeLaunchSystemWebApp(
-    const GURL& launch_url) {
-  absl::optional<SystemAppType> system_app_type =
-      GetSystemWebAppTypeForAppId(&profile_, params_.app_id);
-  if (!system_app_type)
-    return nullptr;
-
-  Browser* browser =
-      LaunchSystemWebAppImpl(&profile_, *system_app_type, launch_url, params_);
-  return browser->tab_strip_model()->GetActiveWebContents();
-}
-
 std::tuple<Browser*, bool /*is_new_browser*/>
 WebAppLaunchProcess::EnsureBrowser() {
   Browser* browser = MaybeFindBrowserForLaunch();
diff --git a/chrome/browser/ui/web_applications/web_app_launch_process.h b/chrome/browser/ui/web_applications/web_app_launch_process.h
index ada965f..af5e3394 100644
--- a/chrome/browser/ui/web_applications/web_app_launch_process.h
+++ b/chrome/browser/ui/web_applications/web_app_launch_process.h
@@ -46,7 +46,6 @@
   std::tuple<GURL, bool /*is_file_handling*/> GetLaunchUrl(
       const apps::ShareTarget* share_target) const;
   WindowOpenDisposition GetNavigationDisposition(bool is_new_browser) const;
-  content::WebContents* MaybeLaunchSystemWebApp(const GURL& launch_url);
   std::tuple<Browser*, bool /*is_new_browser*/> EnsureBrowser();
   LaunchHandler::RouteTo GetLaunchRouteTo() const;
   bool RouteToExistingClient() const;
diff --git a/chrome/browser/ui/webui/access_code_cast/access_code_cast_dialog.cc b/chrome/browser/ui/webui/access_code_cast/access_code_cast_dialog.cc
index fcaea0c..9ebd558 100644
--- a/chrome/browser/ui/webui/access_code_cast/access_code_cast_dialog.cc
+++ b/chrome/browser/ui/webui/access_code_cast/access_code_cast_dialog.cc
@@ -141,7 +141,12 @@
 void AccessCodeCastDialog::GetDialogSize(gfx::Size* size) const {
   const int kDefaultWidth = 448;
   const int kDefaultHeight = 271;
-  size->SetSize(kDefaultWidth, kDefaultHeight);
+  const int kRememberDevicesHeight = 310;
+  base::TimeDelta duration_pref = GetAccessCodeDeviceDurationPref(
+      context_->GetPrefs());
+  bool rememberDevices = duration_pref != base::Seconds(0);
+  size->SetSize(kDefaultWidth,
+      rememberDevices ? kRememberDevicesHeight : kDefaultHeight);
 }
 
 std::string AccessCodeCastDialog::GetDialogArgs() const {
diff --git a/chrome/browser/ui/webui/access_code_cast/access_code_cast_dialog.h b/chrome/browser/ui/webui/access_code_cast/access_code_cast_dialog.h
index 75bc1972..483dfc0 100644
--- a/chrome/browser/ui/webui/access_code_cast/access_code_cast_dialog.h
+++ b/chrome/browser/ui/webui/access_code_cast/access_code_cast_dialog.h
@@ -7,6 +7,7 @@
 
 #include "base/memory/raw_ptr.h"
 #include "base/time/time.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/media_router/media_cast_mode.h"
 #include "chrome/browser/ui/media_router/media_route_starter.h"
 #include "components/access_code_cast/common/access_code_cast_metrics.h"
@@ -17,7 +18,6 @@
 #include "url/gurl.h"
 
 namespace content {
-class BrowserContext;
 class WebContents;
 }  // namespace content
 
@@ -93,7 +93,7 @@
   std::unique_ptr<media_router::MediaRouteStarter> media_route_starter_;
 
   const raw_ptr<content::WebContents> web_contents_;
-  const raw_ptr<content::BrowserContext> context_;
+  const raw_ptr<Profile> context_;
   base::Time dialog_creation_timestamp_;
 };
 
diff --git a/chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.cc b/chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.cc
index a10c9558..f08ef30 100644
--- a/chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.cc
+++ b/chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.cc
@@ -4,7 +4,10 @@
 
 #include "chrome/browser/ui/webui/access_code_cast/access_code_cast_ui.h"
 
+#include "chrome/browser/media/router/discovery/access_code/access_code_cast_feature.h"
+#include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/webui/access_code_cast/access_code_cast_dialog.h"
+#include "chrome/browser/ui/webui/plural_string_handler.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/common/webui_url_constants.h"
@@ -12,6 +15,7 @@
 #include "chrome/grit/access_code_cast_resources_map.h"
 #include "chrome/grit/generated_resources.h"
 #include "components/media_router/browser/media_router_factory.h"
+#include "components/prefs/pref_service.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/web_ui_data_source.h"
@@ -51,6 +55,22 @@
   source->AddBoolean("qrScannerEnabled", false);
   source->AddString("learnMoreUrl", chrome::kAccessCodeCastLearnMoreURL);
 
+  Profile* const profile = Profile::FromWebUI(web_ui);
+  source->AddInteger("rememberedDeviceDuration",
+      GetAccessCodeDeviceDurationPref(profile->GetPrefs()).InSeconds());
+
+  // Add a handler to provide pluralized strings.
+  auto plural_string_handler = std::make_unique<PluralStringHandler>();
+  plural_string_handler->AddLocalizedString(
+      "managedFootnoteHours", IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_HOURS);
+  plural_string_handler->AddLocalizedString(
+      "managedFootnoteDays", IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_DAYS);
+  plural_string_handler->AddLocalizedString(
+      "managedFootnoteMonths", IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_MONTHS);
+  plural_string_handler->AddLocalizedString(
+      "managedFootnoteYears", IDS_ACCESS_CODE_CAST_MANAGED_FOOTNOTE_YEARS);
+  web_ui->AddMessageHandler(std::move(plural_string_handler));
+
   content::BrowserContext* browser_context =
       web_ui->GetWebContents()->GetBrowserContext();
   content::WebUIDataSource::Add(browser_context, source.release());
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index fe640ad..0963a28 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -233,11 +233,7 @@
 #include "chrome/browser/ash/web_applications/chrome_file_manager_ui_delegate.h"
 #include "chrome/browser/ash/web_applications/help_app/help_app_ui_delegate.h"
 #include "chrome/browser/ash/web_applications/media_app/chrome_media_app_ui_delegate.h"
-#include "chrome/browser/ash/web_applications/personalization_app/personalization_app_ambient_provider_impl.h"
-#include "chrome/browser/ash/web_applications/personalization_app/personalization_app_keyboard_backlight_provider_impl.h"
-#include "chrome/browser/ash/web_applications/personalization_app/personalization_app_theme_provider_impl.h"
-#include "chrome/browser/ash/web_applications/personalization_app/personalization_app_user_provider_impl.h"
-#include "chrome/browser/ash/web_applications/personalization_app/personalization_app_wallpaper_provider_impl.h"
+#include "chrome/browser/ash/web_applications/personalization_app/personalization_app_utils.h"
 #include "chrome/browser/feedback/feedback_dialog_utils.h"
 #include "chrome/browser/nearby_sharing/nearby_sharing_service_factory.h"
 #include "chrome/browser/ui/ash/holding_space/holding_space_keyed_service.h"
@@ -666,23 +662,7 @@
 WebUIController* NewWebUI<ash::personalization_app::PersonalizationAppUI>(
     WebUI* web_ui,
     const GURL& url) {
-  auto ambient_provider = std::make_unique<
-      ash::personalization_app::PersonalizationAppAmbientProviderImpl>(web_ui);
-  auto keyboard_backlight_provider =
-      std::make_unique<ash::personalization_app::
-                           PersonalizationAppKeyboardBacklightProviderImpl>(
-          web_ui);
-  auto theme_provider = std::make_unique<
-      ash::personalization_app::PersonalizationAppThemeProviderImpl>(web_ui);
-  auto user_provider = std::make_unique<
-      ash::personalization_app::PersonalizationAppUserProviderImpl>(web_ui);
-  auto wallpaper_provider = std::make_unique<
-      ash::personalization_app::PersonalizationAppWallpaperProviderImpl>(
-      web_ui);
-  return new ash::personalization_app::PersonalizationAppUI(
-      web_ui, std::move(ambient_provider),
-      std::move(keyboard_backlight_provider), std::move(theme_provider),
-      std::move(user_provider), std::move(wallpaper_provider));
+  return ash::personalization_app::CreatePersonalizationAppUI(web_ui);
 }
 
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
diff --git a/chrome/browser/ui/webui/net_internals/net_internals_ui_browsertest.cc b/chrome/browser/ui/webui/net_internals/net_internals_ui_browsertest.cc
index bacb329..4a5281fe 100644
--- a/chrome/browser/ui/webui/net_internals/net_internals_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/net_internals/net_internals_ui_browsertest.cc
@@ -123,27 +123,26 @@
  private:
   void RegisterMessages() override;
 
-  void RegisterMessage(
-      const std::string& message,
-      const content::WebUI::DeprecatedMessageCallback& handler);
+  void RegisterMessage(const std::string& message,
+                       const content::WebUI::MessageCallback& handler);
 
-  void HandleMessage(const content::WebUI::DeprecatedMessageCallback& handler,
-                     const base::ListValue* data);
+  void HandleMessage(const content::WebUI::MessageCallback& handler,
+                     const base::Value::List& data);
 
   // Runs NetInternalsTest.callback with the given value.
   void RunJavascriptCallback(base::Value* value);
 
   // Takes a string and provides the corresponding URL from the test server,
   // which must already have been started.
-  void GetTestServerURL(const base::ListValue* list_value);
+  void GetTestServerURL(const base::Value::List& list);
 
   // Sets up the test server to receive test Expect-CT reports. Calls the
   // Javascript callback to return the test server URI.
-  void SetUpTestReportURI(const base::ListValue* list_value);
+  void SetUpTestReportURI(const base::Value::List& list);
 
   // Performs a DNS lookup. Calls the Javascript callback with the host's IP
   // address or an error string.
-  void DnsLookup(const base::ListValue* list_value);
+  void DnsLookup(const base::Value::List& list);
 
   Browser* browser() { return net_internals_test_->browser(); }
 
@@ -177,16 +176,16 @@
 
 void NetInternalsTest::MessageHandler::RegisterMessage(
     const std::string& message,
-    const content::WebUI::DeprecatedMessageCallback& handler) {
-  web_ui()->RegisterDeprecatedMessageCallback(
+    const content::WebUI::MessageCallback& handler) {
+  web_ui()->RegisterMessageCallback(
       message,
       base::BindRepeating(&NetInternalsTest::MessageHandler::HandleMessage,
                           weak_factory_.GetWeakPtr(), handler));
 }
 
 void NetInternalsTest::MessageHandler::HandleMessage(
-    const content::WebUI::DeprecatedMessageCallback& handler,
-    const base::ListValue* data) {
+    const content::WebUI::MessageCallback& handler,
+    const base::Value::List& data) {
   // The handler might run a nested loop to wait for something.
   base::CurrentThread::ScopedNestableTaskAllower nestable_task_allower;
   handler.Run(data);
@@ -198,16 +197,16 @@
 }
 
 void NetInternalsTest::MessageHandler::GetTestServerURL(
-    const base::ListValue* list_value) {
+    const base::Value::List& list) {
   ASSERT_TRUE(net_internals_test_->StartTestServer());
-  const std::string& path = list_value->GetListDeprecated()[0].GetString();
+  const std::string& path = list[0].GetString();
   GURL url = net_internals_test_->embedded_test_server()->GetURL(path);
   base::Value url_value(url.spec());
   RunJavascriptCallback(&url_value);
 }
 
 void NetInternalsTest::MessageHandler::SetUpTestReportURI(
-    const base::ListValue* list_value) {
+    const base::Value::List& list) {
   net_internals_test_->embedded_test_server()->RegisterRequestHandler(
       base::BindRepeating(&HandleExpectCTReportPreflight));
   ASSERT_TRUE(net_internals_test_->embedded_test_server()->Start());
@@ -217,8 +216,7 @@
 }
 
 void NetInternalsTest::MessageHandler::DnsLookup(
-    const base::ListValue* list_value) {
-  const auto& list = list_value->GetListDeprecated();
+    const base::Value::List& list) {
   ASSERT_GE(2u, list.size());
   ASSERT_TRUE(list[0].is_string());
   ASSERT_TRUE(list[1].is_bool());
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.cc
index 0292d5c5..b4eeca8 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.cc
@@ -691,6 +691,13 @@
   auto ntp_modules_fre_visible =
       profile_->GetPrefs()->GetBoolean(prefs::kNtpModulesFreVisible);
 
+  if (ntp_modules_fre_visible &&
+      (ntp_modules_shown_count == kMaxModuleFreImpressions ||
+       (!ntp_modules_first_shown_time.is_null() &&
+        (base::Time::Now() - ntp_modules_first_shown_time) == base::Days(1)))) {
+    LogModulesFreOptInStatus(new_tab_page::mojom::OptInStatus::kImplicitOptIn);
+  }
+
   // Hide Modular NTP Desktop v1 First Run Experience after
   // |kMaxModuleFreImpressions| impressions or 1 day, whichever comes first.
   if (ntp_modules_shown_count >= kMaxModuleFreImpressions ||
@@ -701,12 +708,6 @@
   } else {
     page_->SetModulesFreVisibility(ntp_modules_fre_visible);
   }
-
-  if (ntp_modules_shown_count == kMaxModuleFreImpressions ||
-      (!ntp_modules_first_shown_time.is_null() &&
-       (base::Time::Now() - ntp_modules_first_shown_time) == base::Days(1))) {
-    LogModulesFreOptInStatus(new_tab_page::mojom::OptInStatus::kImplicitOptIn);
-  }
 }
 
 void NewTabPageHandler::LogModulesFreOptInStatus(
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index 7a63cdf..9b395a7 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -2845,11 +2845,8 @@
       "enablePaymentHandlerContentSetting",
       base::FeatureList::IsEnabled(features::kServiceWorkerPaymentApps));
 
-  html_source->AddBoolean(
-      "enableFederatedIdentityApiContentSetting",
-      GetFieldTrialParamByFeatureAsBool(
-          features::kFedCm, features::kFedCmDesktopSettingsFieldTrialParamName,
-          false));
+  html_source->AddBoolean("enableFederatedIdentityApiContentSetting",
+                          base::FeatureList::IsEnabled(features::kFedCm));
 
   base::CommandLine& cmd = *base::CommandLine::ForCurrentProcess();
   html_source->AddBoolean(
diff --git a/chrome/browser/ui/webui/settings/site_settings_helper.cc b/chrome/browser/ui/webui/settings/site_settings_helper.cc
index b2a7778..57a5deb 100644
--- a/chrome/browser/ui/webui/settings/site_settings_helper.cc
+++ b/chrome/browser/ui/webui/settings/site_settings_helper.cc
@@ -462,9 +462,7 @@
     if (base::FeatureList::IsEnabled(::features::kServiceWorkerPaymentApps))
       base_types->push_back(ContentSettingsType::PAYMENT_HANDLER);
 
-    if (GetFieldTrialParamByFeatureAsBool(
-            features::kFedCm,
-            features::kFedCmDesktopSettingsFieldTrialParamName, false)) {
+    if (base::FeatureList::IsEnabled(features::kFedCm)) {
       base_types->push_back(ContentSettingsType::FEDERATED_IDENTITY_API);
     }
 
diff --git a/chrome/browser/ui/webui/signin/profile_picker_handler.cc b/chrome/browser/ui/webui/signin/profile_picker_handler.cc
index 312270b..1972c76 100644
--- a/chrome/browser/ui/webui/signin/profile_picker_handler.cc
+++ b/chrome/browser/ui/webui/signin/profile_picker_handler.cc
@@ -1193,16 +1193,8 @@
   AccountProfileMapper* mapper =
       g_browser_process->profile_manager()->GetAccountProfileMapper();
 
-  if (IsSelectingSecondaryAccount(web_ui())) {
-    GetAccountsAvailableAsSecondary(
-        mapper, GetCurrentProfilePath(web_ui()),
-        base::BindOnce(&ProfilePickerHandler::GetAvailableAccountsInfo,
-                       weak_factory_.GetWeakPtr()));
-    return;
-  }
-  GetAccountsAvailableAsPrimary(
-      mapper,
-      &g_browser_process->profile_manager()->GetProfileAttributesStorage(),
+  GetAllAvailableAccounts(
+      mapper, GetCurrentProfilePath(web_ui()),
       base::BindOnce(&ProfilePickerHandler::GetAvailableAccountsInfo,
                      weak_factory_.GetWeakPtr()));
 }
diff --git a/chrome/browser/ui/webui/signin/profile_picker_handler_unittest.cc b/chrome/browser/ui/webui/signin/profile_picker_handler_unittest.cc
index 62ba41a1f..bc4cc3aa 100644
--- a/chrome/browser/ui/webui/signin/profile_picker_handler_unittest.cc
+++ b/chrome/browser/ui/webui/signin/profile_picker_handler_unittest.cc
@@ -355,7 +355,8 @@
   EXPECT_EQ("available-accounts-changed", data1.arg1()->GetString());
   EXPECT_EQ(data1.arg2()->GetListDeprecated().size(), 2u);
 
-  // ****** Account 1 syncing in Secondary profile: return account 2.
+  // ****** Account 1 syncing in Secondary profile: return account 1 and 2
+  // regardless of syncing status.
   secondary->SetAuthInfo(kGaiaId1, u"example1@gmail.com",
                          /*is_consented_primary_account=*/true);
   // Send message to the handler.
@@ -366,11 +367,20 @@
   const content::TestWebUI::CallData& data2 = *web_ui()->call_data().back();
   EXPECT_EQ("cr.webUIListenerCallback", data2.function_name());
   EXPECT_EQ("available-accounts-changed", data2.arg1()->GetString());
-  EXPECT_EQ(data2.arg2()->GetListDeprecated().size(), 1u);
-  const std::string* gaia_id =
+  EXPECT_EQ(data2.arg2()->GetListDeprecated().size(), 2u);
+  // Arbitrary order of results; using a set to perform the search without
+  // order.
+  base::flat_set<std::string> gaia_id_results;
+  const std::string* gaia_id1 =
       data2.arg2()->GetListDeprecated()[0].FindStringPath("gaiaId");
-  EXPECT_NE(gaia_id, nullptr);
-  EXPECT_EQ(*gaia_id, kGaiaId2);
+  EXPECT_NE(gaia_id1, nullptr);
+  gaia_id_results.insert(*gaia_id1);
+  const std::string* gaia_id2 =
+      data2.arg2()->GetListDeprecated()[1].FindStringPath("gaiaId");
+  EXPECT_NE(gaia_id2, nullptr);
+  gaia_id_results.insert(*gaia_id2);
+  EXPECT_TRUE(gaia_id_results.contains(kGaiaId1));
+  EXPECT_TRUE(gaia_id_results.contains(kGaiaId2));
   // TODO(https://crbug/1226050): Test all other fields.
 }
 
diff --git a/chrome/browser/ui/webui/web_app_internals/web_app_internals_source.cc b/chrome/browser/ui/webui/web_app_internals/web_app_internals_source.cc
index 6444b54..ae9b651c 100644
--- a/chrome/browser/ui/webui/web_app_internals/web_app_internals_source.cc
+++ b/chrome/browser/ui/webui/web_app_internals/web_app_internals_source.cc
@@ -35,6 +35,8 @@
 // New fields must be added to BuildIndexJson().
 constexpr char kInstalledWebApps[] = "InstalledWebApps";
 constexpr char kPreinstalledWebAppConfigs[] = "PreinstalledWebAppConfigs";
+constexpr char kPreinstalledAppsUninstalledByUserConfigs[] =
+    "PreinstalledAppsUninstalledByUserConfigs";
 constexpr char kExternallyManagedWebAppPrefs[] = "ExternallyManagedWebAppPrefs";
 constexpr char kIconErrorLog[] = "IconErrorLog";
 constexpr char kInstallationProcessErrorLog[] = "InstallationProcessErrorLog";
@@ -61,6 +63,7 @@
 
   index.Append(kInstalledWebApps);
   index.Append(kPreinstalledWebAppConfigs);
+  index.Append(kPreinstalledAppsUninstalledByUserConfigs);
   index.Append(kExternallyManagedWebAppPrefs);
   index.Append(kIconErrorLog);
   index.Append(kInstallationProcessErrorLog);
@@ -187,6 +190,15 @@
   return root;
 }
 
+base::Value BuildPreinstalledAppsUninstalledByUserJson(Profile* profile) {
+  base::Value::Dict root;
+  root.Set(kPreinstalledAppsUninstalledByUserConfigs,
+           profile->GetPrefs()
+               ->GetDictionary(prefs::kUserUninstalledPreinstalledWebAppPref)
+               ->Clone());
+  return base::Value(std::move(root));
+}
+
 base::Value BuildIconErrorLogJson(web_app::WebAppProvider& provider) {
   base::Value root(base::Value::Type::DICTIONARY);
 
@@ -283,6 +295,7 @@
   root.Append(BuildInstalledWebAppsJson(*provider));
   root.Append(BuildPreinstalledWebAppConfigsJson(*provider));
   root.Append(BuildExternallyManagedWebAppPrefsJson(profile));
+  root.Append(BuildPreinstalledAppsUninstalledByUserJson(profile));
   root.Append(BuildIconErrorLogJson(*provider));
   root.Append(BuildInstallProcessErrorLogJson(*provider));
 #if BUILDFLAG(IS_MAC)
diff --git a/chrome/browser/util/BUILD.gn b/chrome/browser/util/BUILD.gn
index 1671f915..160beb2 100644
--- a/chrome/browser/util/BUILD.gn
+++ b/chrome/browser/util/BUILD.gn
@@ -36,7 +36,9 @@
   ]
 }
 
-android_library("javatests") {
+java_library("junit_tests") {
+  # Skip platform checks since Robolectric depends on requires_android targets.
+  bypass_platform_checks = true
   testonly = true
   sources = [
     "android/java/src/org/chromium/chrome/browser/util/ChromeFileProviderTest.java",
@@ -46,11 +48,11 @@
     ":java",
     "//base:base_java",
     "//base:base_java_test_support",
+    "//base:base_junit_test_support",
     "//base/test:test_support_java",
     "//chrome/test/android:chrome_java_test_support",
     "//content/public/test/android:content_java_test_support",
-    "//third_party/android_sdk:android_test_mock_java",
-    "//third_party/android_support_test_runner:runner_java",
+    "//third_party/android_deps:robolectric_all_java",
     "//third_party/androidx:androidx_test_runner_java",
     "//third_party/junit",
     "//third_party/mockito:mockito_java",
diff --git a/chrome/browser/util/android/java/src/org/chromium/chrome/browser/util/ChromeFileProviderTest.java b/chrome/browser/util/android/java/src/org/chromium/chrome/browser/util/ChromeFileProviderTest.java
index 05b62981..707a2df7 100644
--- a/chrome/browser/util/android/java/src/org/chromium/chrome/browser/util/ChromeFileProviderTest.java
+++ b/chrome/browser/util/android/java/src/org/chromium/chrome/browser/util/ChromeFileProviderTest.java
@@ -13,11 +13,11 @@
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
 
 import org.chromium.base.task.PostTask;
 import org.chromium.base.task.TaskTraits;
-import org.chromium.base.test.BaseJUnit4ClassRunner;
-import org.chromium.base.test.util.Batch;
+import org.chromium.base.test.BaseRobolectricTestRunner;
 
 import java.io.FileNotFoundException;
 
@@ -27,8 +27,8 @@
  * The openFile should be blocked till notify is called. These tests can timeout if the notify does
  * not work correctly.
  */
-@RunWith(BaseJUnit4ClassRunner.class)
-@Batch(Batch.UNIT_TESTS)
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
 public class ChromeFileProviderTest {
     private ParcelFileDescriptor openFileFromProvider(Uri uri) {
         ChromeFileProvider provider = new ChromeFileProvider();
diff --git a/chrome/browser/util/android/java/src/org/chromium/chrome/browser/util/HashUtilTest.java b/chrome/browser/util/android/java/src/org/chromium/chrome/browser/util/HashUtilTest.java
index 15e1cc43..ce34682 100644
--- a/chrome/browser/util/android/java/src/org/chromium/chrome/browser/util/HashUtilTest.java
+++ b/chrome/browser/util/android/java/src/org/chromium/chrome/browser/util/HashUtilTest.java
@@ -9,11 +9,13 @@
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
 
+import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Feature;
-import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
-@RunWith(ChromeJUnit4ClassRunner.class)
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
 public class HashUtilTest {
     @Test
     @SmallTest
diff --git a/chrome/browser/web_applications/BUILD.gn b/chrome/browser/web_applications/BUILD.gn
index fb90493..74e9448a 100644
--- a/chrome/browser/web_applications/BUILD.gn
+++ b/chrome/browser/web_applications/BUILD.gn
@@ -105,6 +105,8 @@
     "system_web_apps/system_web_app_types.h",
     "user_display_mode.cc",
     "user_display_mode.h",
+    "user_uninstalled_preinstalled_web_app_prefs.cc",
+    "user_uninstalled_preinstalled_web_app_prefs.h",
     "web_app.cc",
     "web_app.h",
     "web_app_audio_focus_id_map.cc",
@@ -492,6 +494,7 @@
     "preinstalled_web_app_utils_unittest.cc",
     "preinstalled_web_apps/preinstalled_web_app_definition_utils_unittest.cc",
     "test/web_app_test.h",
+    "user_uninstalled_preinstalled_web_app_prefs_unittest.cc",
     "web_app_command_manager_unittest.cc",
     "web_app_constants_unittest.cc",
     "web_app_data_retriever_unittest.cc",
diff --git a/chrome/browser/web_applications/system_web_apps/test/system_web_app_browsertest_base.cc b/chrome/browser/web_applications/system_web_apps/test/system_web_app_browsertest_base.cc
index 5a8d1e0..67a5e16 100644
--- a/chrome/browser/web_applications/system_web_apps/test/system_web_app_browsertest_base.cc
+++ b/chrome/browser/web_applications/system_web_apps/test/system_web_app_browsertest_base.cc
@@ -101,8 +101,11 @@
     DCHECK(navigation_observer.last_navigation_succeeded());
   }
 
-  if (out_browser)
-    *out_browser = chrome::FindBrowserWithWebContents(web_contents);
+  if (out_browser) {
+    *out_browser = web_contents
+                       ? chrome::FindBrowserWithWebContents(web_contents)
+                       : nullptr;
+  }
 
   return web_contents;
 }
@@ -148,6 +151,15 @@
   return GetStartUrl(LaunchParamsForApp(GetMockAppType()));
 }
 
+size_t SystemWebAppBrowserTestBase::GetSystemWebAppBrowserCount(
+    SystemAppType type) {
+  auto* browser_list = BrowserList::GetInstance();
+  return std::count_if(
+      browser_list->begin(), browser_list->end(), [&](Browser* browser) {
+        return web_app::IsBrowserForSystemWebApp(browser, type);
+      });
+}
+
 SystemWebAppManagerBrowserTest::SystemWebAppManagerBrowserTest(
     bool install_mock)
     : TestProfileTypeMixin<SystemWebAppBrowserTestBase>(install_mock) {
diff --git a/chrome/browser/web_applications/system_web_apps/test/system_web_app_browsertest_base.h b/chrome/browser/web_applications/system_web_apps/test/system_web_app_browsertest_base.h
index 7c54b16..4d2b82a5 100644
--- a/chrome/browser/web_applications/system_web_apps/test/system_web_app_browsertest_base.h
+++ b/chrome/browser/web_applications/system_web_apps/test/system_web_app_browsertest_base.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
 #include "chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h"
 #include "chrome/browser/web_applications/test/fake_web_app_provider.h"
@@ -88,6 +89,9 @@
   content::WebContents* LaunchAppWithoutWaiting(SystemAppType type,
                                                 Browser** browser = nullptr);
 
+  // Returns number of system web app browser windows matching |type|.
+  size_t GetSystemWebAppBrowserCount(SystemAppType type);
+
  protected:
   std::unique_ptr<TestSystemWebAppInstallation> maybe_installation_;
 
diff --git a/chrome/browser/web_applications/system_web_apps/test/system_web_app_manager_browsertest.cc b/chrome/browser/web_applications/system_web_apps/test/system_web_app_manager_browsertest.cc
index 17412c2..3ba8349 100644
--- a/chrome/browser/web_applications/system_web_apps/test/system_web_app_manager_browsertest.cc
+++ b/chrome/browser/web_applications/system_web_apps/test/system_web_app_manager_browsertest.cc
@@ -1791,6 +1791,26 @@
   // Start the actions.
   speech_monitor_.Replay();
 }
+
+class SystemWebAppAbortsLaunchTest : public SystemWebAppManagerBrowserTest {
+ public:
+  SystemWebAppAbortsLaunchTest()
+      : SystemWebAppManagerBrowserTest(/*install_mock*/ false) {
+    maybe_installation_ =
+        TestSystemWebAppInstallation::SetUpAppThatAbortsLaunch();
+  }
+  ~SystemWebAppAbortsLaunchTest() override = default;
+};
+
+IN_PROC_BROWSER_TEST_P(SystemWebAppAbortsLaunchTest, LaunchAborted) {
+  WaitForTestSystemAppInstall();
+
+  LaunchSystemWebAppAsync(browser()->profile(), maybe_installation_->GetType());
+  FlushSystemWebAppLaunchesForTesting(browser()->profile());
+
+  EXPECT_EQ(0U, GetSystemWebAppBrowserCount(maybe_installation_->GetType()));
+}
+
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 #if !BUILDFLAG(IS_CHROMEOS_LACROS)
@@ -1859,6 +1879,8 @@
     SystemWebAppManagerDefaultBoundsTest);
 INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
     SystemWebAppAccessibilityTest);
+INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
+    SystemWebAppAbortsLaunchTest);
 #endif
 
 #if !BUILDFLAG(IS_CHROMEOS_LACROS)
diff --git a/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.cc b/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.cc
index 28ad57af..a36d5097 100644
--- a/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.cc
+++ b/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.cc
@@ -167,6 +167,19 @@
   }
   return gfx::Rect();
 }
+Browser* UnittestingSystemAppDelegate::LaunchAndNavigateSystemWebApp(
+    Profile* profile,
+    WebAppProvider* provider,
+    const GURL& url,
+    const apps::AppLaunchParams& params) const {
+  if (launch_and_navigate_system_web_apps_) {
+    return launch_and_navigate_system_web_apps_.Run(profile, provider, url,
+                                                    params);
+  }
+  return SystemWebAppDelegate::LaunchAndNavigateSystemWebApp(profile, provider,
+                                                             url, params);
+}
+
 bool UnittestingSystemAppDelegate::IsAppEnabled() const {
   return is_app_enabled;
 }
@@ -245,6 +258,10 @@
     base::RepeatingCallback<gfx::Rect(Browser*)> lambda) {
   get_default_bounds_ = std::move(lambda);
 }
+void UnittestingSystemAppDelegate::SetLaunchAndNavigateSystemWebApp(
+    LaunchAndNavigateSystemWebAppCallback lambda) {
+  launch_and_navigate_system_web_apps_ = std::move(lambda);
+}
 void UnittestingSystemAppDelegate::SetIsAppEnabled(bool value) {
   is_app_enabled = value;
 }
@@ -624,6 +641,22 @@
       new TestSystemWebAppInstallation(std::move(delegate)));
 }
 
+// static
+std::unique_ptr<TestSystemWebAppInstallation>
+TestSystemWebAppInstallation::SetUpAppThatAbortsLaunch() {
+  std::unique_ptr<UnittestingSystemAppDelegate> delegate =
+      std::make_unique<UnittestingSystemAppDelegate>(
+          SystemAppType::OS_FEEDBACK, "Test",
+          GURL("chrome://test-system-app/pwa.html"),
+          base::BindRepeating(&GenerateWebAppInstallInfoForTestApp));
+  delegate->SetLaunchAndNavigateSystemWebApp(base::BindRepeating(
+      [](Profile*, WebAppProvider*, const GURL&,
+         const apps::AppLaunchParams&) -> Browser* { return nullptr; }));
+
+  return base::WrapUnique(
+      new TestSystemWebAppInstallation(std::move(delegate)));
+}
+
 namespace {
 enum SystemWebAppWindowConfig {
   SINGLE_WINDOW,
diff --git a/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h b/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h
index 3f2edcd..045ff36 100644
--- a/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h
+++ b/chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h
@@ -29,6 +29,12 @@
       delete;
   ~UnittestingSystemAppDelegate() override;
 
+  using LaunchAndNavigateSystemWebAppCallback =
+      base::RepeatingCallback<Browser*(Profile*,
+                                       WebAppProvider*,
+                                       const GURL&,
+                                       const apps::AppLaunchParams&)>;
+
   std::unique_ptr<WebAppInstallInfo> GetWebAppInfo() const override;
 
   std::vector<AppId> GetAppIdsToUninstallAndReplace() const override;
@@ -49,6 +55,11 @@
   bool ShouldAllowScriptsToCloseWindows() const override;
   absl::optional<SystemAppBackgroundTaskInfo> GetTimerInfo() const override;
   gfx::Rect GetDefaultBounds(Browser* browser) const override;
+  Browser* LaunchAndNavigateSystemWebApp(
+      Profile* profile,
+      WebAppProvider* provider,
+      const GURL& url,
+      const apps::AppLaunchParams& params) const override;
   bool IsAppEnabled() const override;
   bool IsUrlInSystemAppScope(const GURL& url) const override;
   bool PreferManifestBackgroundColor() const override;
@@ -74,6 +85,7 @@
   void SetShouldAllowScriptsToCloseWindows(bool);
   void SetTimerInfo(const SystemAppBackgroundTaskInfo&);
   void SetDefaultBounds(base::RepeatingCallback<gfx::Rect(Browser*)>);
+  void SetLaunchAndNavigateSystemWebApp(LaunchAndNavigateSystemWebAppCallback);
   void SetIsAppEnabled(bool);
   void SetUrlInSystemAppScope(const GURL& url);
   void SetPreferManifestBackgroundColor(bool);
@@ -109,6 +121,9 @@
   base::RepeatingCallback<gfx::Rect(Browser*)> get_default_bounds_ =
       base::NullCallback();
 
+  LaunchAndNavigateSystemWebAppCallback launch_and_navigate_system_web_apps_ =
+      base::NullCallback();
+
   absl::optional<SystemAppBackgroundTaskInfo> timer_info_;
 };
 
@@ -179,6 +194,9 @@
 
   static std::unique_ptr<TestSystemWebAppInstallation> SetUpAppWithShortcuts();
 
+  static std::unique_ptr<TestSystemWebAppInstallation>
+  SetUpAppThatAbortsLaunch();
+
   // This creates 4 system web app types for testing context menu with
   // different windowing options:
   //
diff --git a/chrome/browser/web_applications/user_uninstalled_preinstalled_web_app_prefs.cc b/chrome/browser/web_applications/user_uninstalled_preinstalled_web_app_prefs.cc
new file mode 100644
index 0000000..36040f12
--- /dev/null
+++ b/chrome/browser/web_applications/user_uninstalled_preinstalled_web_app_prefs.cc
@@ -0,0 +1,108 @@
+// Copyright 2022 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/web_applications/user_uninstalled_preinstalled_web_app_prefs.h"
+
+#include <string>
+#include <utility>
+
+#include "base/containers/contains.h"
+#include "base/strings/string_piece.h"
+#include "base/values.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace web_app {
+
+UserUninstalledPreinstalledWebAppPrefs::UserUninstalledPreinstalledWebAppPrefs(
+    PrefService* pref_service)
+    : pref_service_(pref_service) {}
+
+// static
+void UserUninstalledPreinstalledWebAppPrefs::RegisterProfilePrefs(
+    user_prefs::PrefRegistrySyncable* registry) {
+  registry->RegisterDictionaryPref(
+      prefs::kUserUninstalledPreinstalledWebAppPref);
+}
+
+void UserUninstalledPreinstalledWebAppPrefs::Add(
+    const AppId& app_id,
+    base::flat_set<GURL> install_urls) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  base::Value::List url_list;
+
+  AppendExistingInstallUrlsPerAppId(app_id, install_urls);
+
+  for (auto install_url : install_urls)
+    url_list.Append(install_url.spec());
+
+  DictionaryPrefUpdate update(pref_service_,
+                              prefs::kUserUninstalledPreinstalledWebAppPref);
+  update->SetKey(app_id, base::Value(std::move(url_list)));
+}
+
+absl::optional<AppId>
+UserUninstalledPreinstalledWebAppPrefs::LookUpAppIdByInstallUrl(
+    const GURL& url) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  const base::Value* ids_to_urls = pref_service_->GetDictionary(
+      prefs::kUserUninstalledPreinstalledWebAppPref);
+
+  if (!ids_to_urls || !url.is_valid())
+    return absl::nullopt;
+
+  for (auto it : ids_to_urls->DictItems()) {
+    const base::Value::List* urls = it.second.GetIfList();
+    if (!urls)
+      continue;
+    for (const base::Value& link : *urls) {
+      GURL install_url(link.GetString());
+      DCHECK(install_url.is_valid());
+      if (install_url == url)
+        return it.first;
+    }
+  }
+
+  return absl::nullopt;
+}
+
+bool UserUninstalledPreinstalledWebAppPrefs::DoesAppIdExist(
+    const AppId& app_id) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  const base::Value* ids_to_urls = pref_service_->GetDictionary(
+      prefs::kUserUninstalledPreinstalledWebAppPref);
+
+  if (!ids_to_urls)
+    return false;
+
+  const base::Value::Dict* pref_info = ids_to_urls->GetIfDict();
+  return pref_info && pref_info->contains(app_id);
+}
+
+void UserUninstalledPreinstalledWebAppPrefs::AppendExistingInstallUrlsPerAppId(
+    const AppId& app_id,
+    base::flat_set<GURL>& urls) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+  const base::Value* ids_to_urls = pref_service_->GetDictionary(
+      prefs::kUserUninstalledPreinstalledWebAppPref);
+
+  if (!ids_to_urls)
+    return;
+
+  const base::Value::Dict* pref_info = ids_to_urls->GetIfDict();
+  if (!pref_info || !pref_info->contains(app_id))
+    return;
+
+  const base::Value::List* current_list = pref_info->FindList(app_id);
+  if (!current_list)
+    return;
+
+  for (const base::Value& url : *current_list) {
+    // This is being done so as to remove duplicate urls from being
+    // added to the list.
+    DCHECK(GURL(url.GetString()).is_valid());
+    urls.emplace(url.GetString());
+  }
+}
+
+}  // namespace web_app
diff --git a/chrome/browser/web_applications/user_uninstalled_preinstalled_web_app_prefs.h b/chrome/browser/web_applications/user_uninstalled_preinstalled_web_app_prefs.h
new file mode 100644
index 0000000..5a92683
--- /dev/null
+++ b/chrome/browser/web_applications/user_uninstalled_preinstalled_web_app_prefs.h
@@ -0,0 +1,66 @@
+// Copyright 2022 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_WEB_APPLICATIONS_USER_UNINSTALLED_PREINSTALLED_WEB_APP_PREFS_H_
+#define CHROME_BROWSER_WEB_APPLICATIONS_USER_UNINSTALLED_PREINSTALLED_WEB_APP_PREFS_H_
+
+#include "base/containers/flat_set.h"
+#include "base/memory/raw_ptr.h"
+#include "chrome/browser/web_applications/web_app_id.h"
+#include "chrome/common/pref_names.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "url/gurl.h"
+
+class GURL;
+class PrefService;
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+namespace web_app {
+// A Prefs-backed map from preinstalled web app IDs to their install URLs.
+//
+// Preinstalled apps are the only type of externally installed apps that
+// can be uninstalled by user. So if an user has uninstalled a preinstalled app,
+// then it should stay uninstalled on startup.
+// TODO(crbug.com/1029410): Ensure PreinstalledWebAppManager relies on
+// UserUninstalledPreinstalledWebAppPrefs instead of
+// ExternallyInstalledWebAppPrefs once removed.
+//
+// To prevent that, we keep track of the install URLs of preinstalled apps
+// outside of the web_app DB so that on every startup, the WebAppSystem can
+// keep the user uninstalled preinstalled apps uninstalled,
+// thereby maintaining its synchronization.
+//
+// The prefs are stored in prefs::kUserUninstalledPreinstalledWebAppPref and
+// they are stored as map<AppId, Set<Install URLs>>, e.g.
+// {"app_id": {"https://install_url1.com", "https://install_url2.com"}}
+//
+// They can be seen on chrome://web-app-internals under the
+// PreinstalledAppsUninstalledByUserConfigs json for debugging purposes.
+class UserUninstalledPreinstalledWebAppPrefs {
+ public:
+  explicit UserUninstalledPreinstalledWebAppPrefs(PrefService* pref_service);
+  UserUninstalledPreinstalledWebAppPrefs(
+      const UserUninstalledPreinstalledWebAppPrefs&) = delete;
+  UserUninstalledPreinstalledWebAppPrefs& operator=(
+      const UserUninstalledPreinstalledWebAppPrefs&) = delete;
+
+  static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
+  void Add(const AppId& app_id, base::flat_set<GURL> install_urls);
+  absl::optional<AppId> LookUpAppIdByInstallUrl(const GURL& install_url);
+  bool DoesAppIdExist(const AppId& app_id);
+  void AppendExistingInstallUrlsPerAppId(const AppId& app_id,
+                                         base::flat_set<GURL>& urls);
+
+ private:
+  const raw_ptr<PrefService> pref_service_;
+};
+
+}  // namespace web_app
+
+#endif  // CHROME_BROWSER_WEB_APPLICATIONS_USER_UNINSTALLED_PREINSTALLED_WEB_APP_PREFS_H_
diff --git a/chrome/browser/web_applications/user_uninstalled_preinstalled_web_app_prefs_unittest.cc b/chrome/browser/web_applications/user_uninstalled_preinstalled_web_app_prefs_unittest.cc
new file mode 100644
index 0000000..77823f1
--- /dev/null
+++ b/chrome/browser/web_applications/user_uninstalled_preinstalled_web_app_prefs_unittest.cc
@@ -0,0 +1,46 @@
+// Copyright 2022 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/web_applications/user_uninstalled_preinstalled_web_app_prefs.h"
+
+#include "base/containers/flat_set.h"
+#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
+#include "chrome/browser/web_applications/test/web_app_test.h"
+#include "chrome/browser/web_applications/web_app_id.h"
+#include "url/gurl.h"
+
+namespace web_app {
+
+using UserUninstalledPreinstalledWebAppPrefsUnitTest = WebAppTest;
+
+TEST_F(UserUninstalledPreinstalledWebAppPrefsUnitTest, BasicOperations) {
+  GURL url1("https://foo.com");
+  GURL url2("https://bar1.com");
+  GURL url3("https://bar2.com");
+  AppId app_id1 = "foo";
+  AppId app_id2 = "bar";
+
+  UserUninstalledPreinstalledWebAppPrefs preinstalled_prefs(
+      profile()->GetPrefs());
+  preinstalled_prefs.Add(app_id1, {url1});
+  preinstalled_prefs.Add(app_id2, {url2});
+  // To test that url3 gets appended.
+  preinstalled_prefs.Add(app_id2, {url3});
+
+  // Basic checks to verify app id exists in preinstalled prefs or not.
+  EXPECT_TRUE(preinstalled_prefs.DoesAppIdExist(app_id1));
+  EXPECT_TRUE(preinstalled_prefs.DoesAppIdExist(app_id2));
+  EXPECT_FALSE(preinstalled_prefs.DoesAppIdExist("baz"));
+
+  // Basic checks to verify if install_urls exist in preinstalled prefs or not.
+  EXPECT_EQ(app_id1, preinstalled_prefs.LookUpAppIdByInstallUrl(url1));
+  EXPECT_EQ(app_id2, preinstalled_prefs.LookUpAppIdByInstallUrl(url2));
+  EXPECT_EQ(app_id2, preinstalled_prefs.LookUpAppIdByInstallUrl(url3));
+  EXPECT_NE(app_id1, preinstalled_prefs.LookUpAppIdByInstallUrl(url3));
+  EXPECT_EQ(absl::nullopt, preinstalled_prefs.LookUpAppIdByInstallUrl(GURL()));
+  EXPECT_EQ(absl::nullopt, preinstalled_prefs.LookUpAppIdByInstallUrl(
+                               GURL("https://baz.com")));
+}
+
+}  // namespace web_app
diff --git a/chrome/browser/web_applications/web_app_provider.cc b/chrome/browser/web_applications/web_app_provider.cc
index edabd71..f1963b4 100644
--- a/chrome/browser/web_applications/web_app_provider.cc
+++ b/chrome/browser/web_applications/web_app_provider.cc
@@ -30,6 +30,7 @@
 #include "chrome/browser/web_applications/policy/web_app_policy_manager.h"
 #include "chrome/browser/web_applications/preinstalled_web_app_manager.h"
 #include "chrome/browser/web_applications/system_web_apps/system_web_app_manager.h"
+#include "chrome/browser/web_applications/user_uninstalled_preinstalled_web_app_prefs.h"
 #include "chrome/browser/web_applications/web_app_audio_focus_id_map.h"
 #include "chrome/browser/web_applications/web_app_command_manager.h"
 #include "chrome/browser/web_applications/web_app_database_factory.h"
@@ -373,6 +374,7 @@
 // static
 void WebAppProvider::RegisterProfilePrefs(
     user_prefs::PrefRegistrySyncable* registry) {
+  UserUninstalledPreinstalledWebAppPrefs::RegisterProfilePrefs(registry);
   ExternallyInstalledWebAppPrefs::RegisterProfilePrefs(registry);
   PreinstalledWebAppManager::RegisterProfilePrefs(registry);
   WebAppPolicyManager::RegisterProfilePrefs(registry);
diff --git a/chrome/build/linux.pgo.txt b/chrome/build/linux.pgo.txt
index a98f748..3284a23 100644
--- a/chrome/build/linux.pgo.txt
+++ b/chrome/build/linux.pgo.txt
@@ -1 +1 @@
-chrome-linux-main-1652269806-70ba31ee48b8a53593f704b3174c3b1cc51360b9.profdata
+chrome-linux-main-1652335186-e2fc489b68b69703960db4c4bfba8ba52f3918d7.profdata
diff --git a/chrome/build/mac-arm.pgo.txt b/chrome/build/mac-arm.pgo.txt
index 22084d99..c7fddaf 100644
--- a/chrome/build/mac-arm.pgo.txt
+++ b/chrome/build/mac-arm.pgo.txt
@@ -1 +1 @@
-chrome-mac-arm-main-1652269806-483aeb805a4a3dd692cf6c5b420efcfdc5595bbd.profdata
+chrome-mac-arm-main-1652335186-557524042df99304562b3db171493f3dc31b68f1.profdata
diff --git a/chrome/build/mac.pgo.txt b/chrome/build/mac.pgo.txt
index 599768c..20c68d6 100644
--- a/chrome/build/mac.pgo.txt
+++ b/chrome/build/mac.pgo.txt
@@ -1 +1 @@
-chrome-mac-main-1652269806-a9422fa24b4f4debb0fbaa8278d186efb9d4f936.profdata
+chrome-mac-main-1652313520-f15fd03ecd1599ec1bc08e9b0da3ea76a68cef1f.profdata
diff --git a/chrome/build/win32.pgo.txt b/chrome/build/win32.pgo.txt
index 900ed41..018e1d4 100644
--- a/chrome/build/win32.pgo.txt
+++ b/chrome/build/win32.pgo.txt
@@ -1 +1 @@
-chrome-win32-main-1652281130-548231baae8bd6719cebf30c54e84ee6477dbcb0.profdata
+chrome-win32-main-1652324361-728ba3460d95824eef93aaae7ef864a26521c357.profdata
diff --git a/chrome/build/win64.pgo.txt b/chrome/build/win64.pgo.txt
index 8f4f880..9fe1b69 100644
--- a/chrome/build/win64.pgo.txt
+++ b/chrome/build/win64.pgo.txt
@@ -1 +1 @@
-chrome-win64-main-1652281130-ca75a3ac44f3a696888ee7d473f8f28b8f7f45d7.profdata
+chrome-win64-main-1652324361-d9249b70d6116f632b46822e7de006c66a998134.profdata
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 979b5a9..0f555df 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -416,12 +416,6 @@
 const base::Feature kExternalExtensionDefaultButtonControl{
     "ExternalExtensionDefaultButtonControl", base::FEATURE_DISABLED_BY_DEFAULT};
 
-#if !BUILDFLAG(IS_ANDROID)
-// Field trial boolean parameter which indicates whether FedCM desktop settings
-// are enabled.
-const char kFedCmDesktopSettingsFieldTrialParamName[] = "DesktopSettings";
-#endif  // !BUILDFLAG(IS_ANDROID)
-
 #if BUILDFLAG(ENABLE_PLUGINS)
 // Show Flash deprecation warning to users who have manually enabled Flash.
 // https://crbug.com/918428
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index c200d19..89ef2c4 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -293,11 +293,6 @@
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kExternalExtensionDefaultButtonControl;
 
-#if !BUILDFLAG(IS_ANDROID)
-COMPONENT_EXPORT(CHROME_FEATURES)
-extern const char kFedCmDesktopSettingsFieldTrialParamName[];
-#endif  // !BUILDFLAG(IS_ANDROID)
-
 #if BUILDFLAG(ENABLE_PLUGINS)
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kFlashDeprecationWarning;
diff --git a/chrome/common/extensions/api/file_manager_private.idl b/chrome/common/extensions/api/file_manager_private.idl
index 8f6e711c4..e026cd5 100644
--- a/chrome/common/extensions/api/file_manager_private.idl
+++ b/chrome/common/extensions/api/file_manager_private.idl
@@ -1301,7 +1301,7 @@
 
   // Unmounts a mounted resource.
   // |volumeId| An ID of the volume.
-  static void removeMount(DOMString volumeId);
+  static void removeMount(DOMString volumeId, SimpleCallback callback);
 
   // Get the list of mounted volumes.
   // |callback|
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 8d0699a..83247d5 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -2057,13 +2057,19 @@
 // A list of dictionaries for managing Web Apps.
 const char kWebAppSettings[] = "profile.web_app.policy_settings";
 
-// A list of dictionaries for managed configurations. Each dictionary contains
-// 3 strings -- origin to be configured, link to the configuration, and the
-// hashed value to that configuration.
+// A map of App ID to install URLs to keep track of preinstalled web apps
+// after they have been deleted.
+const char kUserUninstalledPreinstalledWebAppPref[] =
+    "web_app.app_id.install_url";
+
+// A list of dictionaries for managed configurations. Each dictionary
+// contains 3 strings -- origin to be configured, link to the configuration,
+// and the hashed value to that configuration.
 const char kManagedConfigurationPerOrigin[] =
     "profile.managed_configuration.list";
-// Dictionary that maps the hash of the last downloded managed configuration for
-// a particular origin.
+
+// Dictionary that maps the hash of the last downloaded managed configuration
+// for a particular origin.
 const char kLastManagedConfigurationHashForOrigin[] =
     "profile.managed_configuration.last_hash";
 
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 46199d3..48cce58 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -686,6 +686,7 @@
 extern const char kDiceSigninUserMenuPromoCount[];
 #endif
 
+extern const char kUserUninstalledPreinstalledWebAppPref[];
 extern const char kManagedConfigurationPerOrigin[];
 extern const char kLastManagedConfigurationHashForOrigin[];
 
diff --git a/chrome/services/file_util/public/mojom/safe_archive_analyzer_param_traits.h b/chrome/services/file_util/public/mojom/safe_archive_analyzer_param_traits.h
index 4c378e9..51a7c5c 100644
--- a/chrome/services/file_util/public/mojom/safe_archive_analyzer_param_traits.h
+++ b/chrome/services/file_util/public/mojom/safe_archive_analyzer_param_traits.h
@@ -21,6 +21,9 @@
     safe_browsing::ClientDownloadRequest_DownloadType,
     safe_browsing::ClientDownloadRequest_DownloadType_IsValid(value))
 
+IPC_ENUM_TRAITS_MAX_VALUE(safe_browsing::ArchiveAnalysisResult,
+                          safe_browsing::ArchiveAnalysisResult::kMaxValue)
+
 IPC_PROTOBUF_MESSAGE_TRAITS_BEGIN(safe_browsing::ClientDownloadRequest_Digests)
   IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_COMPLEX_MEMBER(sha256)
   IPC_PROTOBUF_MESSAGE_TRAITS_OPTIONAL_COMPLEX_MEMBER(sha1)
@@ -110,4 +113,5 @@
 #endif  // BUILDFLAG(IS_MAC)
   IPC_STRUCT_TRAITS_MEMBER(file_count)
   IPC_STRUCT_TRAITS_MEMBER(directory_count)
+  IPC_STRUCT_TRAITS_MEMBER(analysis_result)
 IPC_STRUCT_TRAITS_END()
diff --git a/chrome/services/speech/soda/soda_client.cc b/chrome/services/speech/soda/soda_client.cc
index e4c09ba..97e503b 100644
--- a/chrome/services/speech/soda/soda_client.cc
+++ b/chrome/services/speech/soda/soda_client.cc
@@ -11,6 +11,7 @@
 #include "build/build_config.h"
 
 #if BUILDFLAG(IS_MAC)
+#include "base/debug/dump_without_crashing.h"
 #include "base/mac/mac_util.h"
 #endif
 
@@ -62,6 +63,15 @@
                              lib_.GetError()->code);
   }
 #endif  // BUILDFLAG(IS_WIN)
+
+#if BUILDFLAG(IS_MAC)
+  if (load_soda_result_ == LoadSodaResultValue::kBinaryInvalid) {
+    base::UmaHistogramSparse(
+        "Accessibility.LiveCaption.LoadSodaErrorMacOsVersion",
+        base::mac::internal::MacOSVersion());
+    base::debug::DumpWithoutCrashing();
+  }
+#endif  // BUILDFLAG(IS_MAC)
 }
 
 NO_SANITIZE("cfi-icall")
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 1b3b6de..868326d 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -839,6 +839,7 @@
       "../browser/browsing_data/chrome_browsing_data_lifetime_manager_browsertest.cc",
       "../browser/engagement/important_sites_util_browsertest.cc",
       "../browser/metrics/metrics_service_user_demographics_browsertest.cc",
+      "../browser/metrics/sampled_out_client_id_saved_browsertest.cc",
       "../browser/metrics/startup_metrics_browsertest.cc",
       "../browser/metrics/ukm_browsertest.cc",
       "../browser/net/cert_verify_proc_browsertest.cc",
@@ -1800,6 +1801,7 @@
       "../browser/metrics/metrics_service_user_demographics_browsertest.cc",
       "../browser/metrics/oom/out_of_memory_reporter_browsertest.cc",
       "../browser/metrics/process_memory_metrics_emitter_browsertest.cc",
+      "../browser/metrics/sampled_out_client_id_saved_browsertest.cc",
       "../browser/metrics/startup_metrics_browsertest.cc",
       "../browser/metrics/tab_stats/tab_stats_tracker_browsertest.cc",
       "../browser/metrics/ukm_background_recorder_browsertest.cc",
@@ -2655,9 +2657,6 @@
       sources += [
         "../browser/external_protocol/external_protocol_policy_browsertest.cc",
         "../browser/lifetime/application_lifetime_browsertest.cc",
-
-        # crbug.com/1323505 Enable DownloadBubble tests on ChromeOS.
-        "../browser/ui/views/download/bubble/download_bubble_dialog_browsertest.cc",
       ]
     }
 
@@ -4002,6 +4001,7 @@
         "//ash/components/drivefs:test_support",
         "//ash/components/drivefs/mojom",
         "//ash/components/geolocation",
+        "//ash/components/hid_detection:hid_detection",
         "//ash/components/login/auth",
         "//ash/components/login/auth:challenge_response_key",
         "//ash/components/login/auth:test_support",
@@ -5524,6 +5524,7 @@
     "//chrome/browser/safe_browsing:advanced_protection",
     "//chrome/browser/safe_browsing:metrics_collector",
     "//chrome/browser/safe_browsing:verdict_cache_manager_factory",
+    "//chrome/browser/segmentation_platform:test_utils",
     "//chrome/browser/share",
     "//chrome/browser/sharing/proto",
     "//chrome/browser/storage_access_api:permissions",
@@ -7983,6 +7984,7 @@
       "../common/safe_browsing/ipc_protobuf_message_test_messages.h",
       "../common/safe_browsing/ipc_protobuf_message_unittest.cc",
     ]
+    deps += [ "../common/safe_browsing:archive_analyzer_results" ]
     if (is_mac) {
       sources += [
         "../browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac_unittest.cc",
diff --git a/chrome/test/data/extensions/api_test/file_browser/mount_test/test.js b/chrome/test/data/extensions/api_test/file_browser/mount_test/test.js
index 9a8167f..bba9d87 100644
--- a/chrome/test/data/extensions/api_test/file_browser/mount_test/test.js
+++ b/chrome/test/data/extensions/api_test/file_browser/mount_test/test.js
@@ -192,19 +192,32 @@
 
 chrome.test.runTests([
   function removeMount() {
-    chrome.fileManagerPrivate.removeMount('removable:mount_path1');
-
-    // We actually check this one on C++ side. If MountLibrary.RemoveMount
-    // doesn't get called, test will fail.
-    chrome.test.succeed();
+    chrome.fileManagerPrivate.removeMount('removable:mount_path1', () => {
+      chrome.test.assertNoLastError();
+      chrome.test.succeed();
+    });
   },
 
   function removeMountArchive() {
-    chrome.fileManagerPrivate.removeMount('archive:archive_mount_path');
+    chrome.fileManagerPrivate.removeMount('archive:archive_mount_path', () => {
+      chrome.test.assertNoLastError();
+      chrome.test.succeed();
+    });
+  },
 
-    // We actually check this one on C++ side. If MountLibrary.RemoveMount
-    // doesn't get called, test will fail.
-    chrome.test.succeed();
+  function removeMount() {
+    chrome.fileManagerPrivate.removeMount('removable:mount_path1', () => {
+      chrome.test.assertEq(chrome.runtime.lastError.message, 'error_cancelled');
+      chrome.test.succeed();
+    });
+  },
+
+  function removeMountArchive() {
+    chrome.fileManagerPrivate.removeMount('archive:archive_mount_path', () => {
+      chrome.test.assertEq(
+          chrome.runtime.lastError.message, 'error_need_password');
+      chrome.test.succeed();
+    });
   },
 
   function getVolumeMetadataList() {
diff --git a/chrome/test/data/extensions/api_test/file_system_provider/unmount/test.js b/chrome/test/data/extensions/api_test/file_system_provider/unmount/test.js
index 6684e3f..53af53de 100644
--- a/chrome/test/data/extensions/api_test/file_system_provider/unmount/test.js
+++ b/chrome/test/data/extensions/api_test/file_system_provider/unmount/test.js
@@ -93,7 +93,9 @@
 
       test_util.getVolumeInfo(SECOND_FILE_SYSTEM_ID, function(volumeInfo) {
         chrome.test.assertTrue(!!volumeInfo);
-        chrome.fileManagerPrivate.removeMount(volumeInfo.volumeId);
+        chrome.fileManagerPrivate.removeMount(volumeInfo.volumeId, () => {
+          chrome.test.assertNoLastError();
+        });
       });
     },
 
@@ -125,7 +127,9 @@
         chrome.test.assertTrue(unmountRequested);
 
         // Remove the handlers and mark the test as succeeded.
-        chrome.fileManagerPrivate.removeMount(SECOND_FILE_SYSTEM_ID);
+        chrome.fileManagerPrivate.removeMount(SECOND_FILE_SYSTEM_ID, () => {
+          chrome.test.assertNoLastError();
+        });
         chrome.fileManagerPrivate.onMountCompleted.removeListener(
             onMountCompleted);
       });
@@ -136,7 +140,9 @@
 
       test_util.getVolumeInfo(SECOND_FILE_SYSTEM_ID, function(volumeInfo) {
         chrome.test.assertTrue(!!volumeInfo);
-        chrome.fileManagerPrivate.removeMount(volumeInfo.volumeId);
+        chrome.fileManagerPrivate.removeMount(volumeInfo.volumeId, () => {
+          chrome.test.assertNoLastError();
+        });
       });
     }
   ]);
diff --git a/chrome/test/data/webui/access_code_cast/access_code_cast_app_test.ts b/chrome/test/data/webui/access_code_cast/access_code_cast_app_test.ts
index ed5bd516..5291cb56 100644
--- a/chrome/test/data/webui/access_code_cast/access_code_cast_app_test.ts
+++ b/chrome/test/data/webui/access_code_cast/access_code_cast_app_test.ts
@@ -260,4 +260,38 @@
     await app.addSinkAndCast();
     assertTrue(app.$.codeInput.focused);
   });
+
+  // Split up footnote tests to limit number of await statements used in a
+  // single test, since this contributes to test flakiness.
+  test('managed footnote is correctly created for short times', async () => {
+    assertEquals(app.getManagedFootnoteForTest(), undefined);
+
+    await app.createManagedFootnote(3600 /* One hour */);
+    assertTrue(app.getManagedFootnoteForTest().includes('1 hour '));
+
+    await app.createManagedFootnote(7200 /* Two hours */);
+    assertTrue(app.getManagedFootnoteForTest().includes('2 hours '));
+
+    await app.createManagedFootnote(86400 /* 1 day */);
+    assertTrue(app.getManagedFootnoteForTest().includes('1 day '));
+
+    await app.createManagedFootnote(172800 /* 2 days */);
+    assertTrue(app.getManagedFootnoteForTest().includes('2 days '));
+  });
+
+  test('managed footnote is correctly created for long times', async () => {
+    assertEquals(app.getManagedFootnoteForTest(), undefined);
+
+    await app.createManagedFootnote(2764800 /* 32 days */);
+    assertTrue(app.getManagedFootnoteForTest().includes('1 month '));
+
+    await app.createManagedFootnote(5529600 /* 64 days */);
+    assertTrue(app.getManagedFootnoteForTest().includes('2 months '));
+
+    await app.createManagedFootnote(31540000 /* 1 year */);
+    assertTrue(app.getManagedFootnoteForTest().includes('1 year '));
+
+    await app.createManagedFootnote(63080000 /* 2 years */);
+    assertTrue(app.getManagedFootnoteForTest().includes('2 years '));
+  });
 });
diff --git a/chrome/test/data/webui/chromeos/personalization_app/google_photos_albums_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/google_photos_albums_element_test.ts
index f443d9f..e4c1a09 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/google_photos_albums_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/google_photos_albums_element_test.ts
@@ -174,9 +174,15 @@
     const selector = 'wallpaper-grid-item:not([hidden]).album';
     const albumSelector = `${selector}:not([placeholder])`;
     const placeholderSelector = `${selector}[placeholder]`;
+    const albumListSelector = 'iron-list:not([hidden])#grid';
     assertEquals(querySelectorAll(albumSelector)!.length, 0);
     const placeholderEls = querySelectorAll(placeholderSelector);
     assertNotEquals(placeholderEls!.length, 0);
+    let albumListEl = querySelectorAll(albumListSelector);
+    assertEquals(albumListEl!.length, 1);
+    assertEquals(
+        albumListEl![0]!.getAttribute('aria-setsize'),
+        placeholderEls!.length.toString());
 
     // Placeholders should be aria-labeled.
     placeholderEls!.forEach(placeholderEl => {
@@ -208,9 +214,18 @@
     assertNotEquals(albumEls!.length, 0);
     assertEquals(querySelectorAll(placeholderSelector)!.length, 0);
 
+    // The album list's aria-setsize should be consistent with the number of
+    // albums.
+    albumListEl = querySelectorAll(albumListSelector);
+    assertEquals(albumListEl!.length, 1);
+    assertEquals(
+        albumListEl![0]!.getAttribute('aria-setsize'),
+        albums.length.toString());
+
     // Albums should be aria-labeled.
     albumEls!.forEach((albumEl, i) => {
       assertEquals(albumEl.getAttribute('aria-label'), albums[i]!.title);
+      assertEquals(albumEl.getAttribute('aria-posinset'), (i + 1).toString());
     });
 
     // Clicking an album should do something.
diff --git a/chrome/test/data/webui/chromeos/personalization_app/wallpaper_grid_item_element_test.ts b/chrome/test/data/webui/chromeos/personalization_app/wallpaper_grid_item_element_test.ts
index af1b248..efc9206d 100644
--- a/chrome/test/data/webui/chromeos/personalization_app/wallpaper_grid_item_element_test.ts
+++ b/chrome/test/data/webui/chromeos/personalization_app/wallpaper_grid_item_element_test.ts
@@ -6,7 +6,6 @@
 import 'chrome://webui-test/mojo_webui_test_support.js';
 
 import {WallpaperGridItem} from 'chrome://personalization/trusted/personalization_app.js';
-
 import {assertEquals, assertNotEquals} from 'chrome://webui-test/chai_assert.js';
 import {waitAfterNextRender} from 'chrome://webui-test/test_util.js';
 
@@ -34,27 +33,49 @@
     await waitAfterNextRender(wallpaperGridItemElement);
 
     // Verify state.
-    assertEquals(querySelector('img'), null);
+    const img = querySelector('img');
+    assertEquals(img?.getAttribute('auto-src'), null);
+    assertEquals(img?.getAttribute('aria-hidden'), 'true');
+    assertEquals(img?.hasAttribute('clear-src'), true);
+    assertEquals(img?.hasAttribute('hidden'), true);
+    assertEquals(img?.hasAttribute('is-google-photos'), true);
     assertEquals(querySelector('.text'), null);
     assertEquals(querySelector('.primary-text'), null);
     assertEquals(querySelector('.secondary-text'), null);
   });
 
   test('displays image', async () => {
-    const imageSrc = 'foo.com';
+    let imageSrc = 'data:image/svg+xml;utf8,' +
+        '<svg xmlns="http://www.w3.org/2000/svg" height="100px" width="100px">' +
+        '<rect fill="red" height="100px" width="100px"></rect>' +
+        '</svg>';
 
     // Initialize |wallpaperGridItemElement|.
     wallpaperGridItemElement = initElement(WallpaperGridItem, {imageSrc});
     await waitAfterNextRender(wallpaperGridItemElement);
 
-    // Verify state.
-    assertEquals(querySelector('img')?.getAttribute('auto-src'), imageSrc);
-    assertEquals(querySelector('img')?.getAttribute('aria-hidden'), 'true');
-    assertEquals(querySelector('img')?.hasAttribute('clear-src'), true);
-    assertEquals(querySelector('img')?.hasAttribute('is-google-photos'), true);
-    assertEquals(querySelector('.text'), null);
-    assertEquals(querySelector('.primary-text'), null);
-    assertEquals(querySelector('.secondary-text'), null);
+    // Verify state. Note that |img| is shown as |imageSrc| has already loaded.
+    const img = querySelector('img');
+    assertEquals(img?.getAttribute('auto-src'), imageSrc);
+    assertEquals(img?.getAttribute('aria-hidden'), 'true');
+    assertEquals(img?.hasAttribute('clear-src'), true);
+    assertEquals(img?.hasAttribute('hidden'), false);
+    assertEquals(img?.hasAttribute('is-google-photos'), true);
+
+    // Update state. Note that |img| is hidden as |imageSrc| hasn't yet loaded.
+    imageSrc = imageSrc.replace('red', 'blue');
+    wallpaperGridItemElement.imageSrc = imageSrc;
+    assertEquals(img?.getAttribute('auto-src'), imageSrc);
+    assertEquals(img?.hasAttribute('hidden'), true);
+
+    // Verify that once |imageSrc| has loaded, |img| will be shown.
+    await new Promise<void>(resolve => {
+      setInterval(() => {
+        if (!img?.hasAttribute('hidden')) {
+          resolve();
+        }
+      }, 100);
+    });
   });
 
   test('displays primary text', async () => {
@@ -65,7 +86,6 @@
     await waitAfterNextRender(wallpaperGridItemElement);
 
     // Verify state.
-    assertEquals(querySelector('img'), null);
     assertNotEquals(querySelector('.text'), null);
     assertEquals(querySelector('.primary-text')?.innerHTML, primaryText);
     assertEquals(querySelector('.secondary-text'), null);
@@ -79,7 +99,6 @@
     await waitAfterNextRender(wallpaperGridItemElement);
 
     // Verify state.
-    assertEquals(querySelector('img'), null);
     assertNotEquals(querySelector('.text'), null);
     assertEquals(querySelector('.primary-text'), null);
     assertEquals(querySelector('.secondary-text')?.innerHTML, secondaryText);
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/reimaging_firmware_update_page_test.js b/chrome/test/data/webui/chromeos/shimless_rma/reimaging_firmware_update_page_test.js
index cac6811..c74f36e7 100644
--- a/chrome/test/data/webui/chromeos/shimless_rma/reimaging_firmware_update_page_test.js
+++ b/chrome/test/data/webui/chromeos/shimless_rma/reimaging_firmware_update_page_test.js
@@ -51,9 +51,7 @@
     const updateStatus =
         component.shadowRoot.querySelector('#firmwareUpdateStatus');
     assertFalse(updateStatus.hidden);
-    assertEquals(
-        loadTimeData.getString('firmwareUpdateWaitForUsbText'),
-        updateStatus.textContent.trim());
+    assertEquals('', updateStatus.textContent.trim());
   });
 
   test('RoFirmwareUpdateStartingDisablesNext', async () => {
diff --git a/chrome/test/data/webui/chromeos/shimless_rma/reimaging_provisioning_page_test.js b/chrome/test/data/webui/chromeos/shimless_rma/reimaging_provisioning_page_test.js
index ff6dbcc..5af4e02 100644
--- a/chrome/test/data/webui/chromeos/shimless_rma/reimaging_provisioning_page_test.js
+++ b/chrome/test/data/webui/chromeos/shimless_rma/reimaging_provisioning_page_test.js
@@ -61,14 +61,6 @@
     return flushTasks();
   }
 
-  test('WaitForProvisioningPageInitializes', async () => {
-    await initializeWaitForProvisioningPage();
-    const provisioningComponent =
-        component.shadowRoot.querySelector('#provisioningDeviceStatus');
-    assertFalse(provisioningComponent.hidden);
-  });
-
-
   test('ProvisioningCompleteTransitionsState', async () => {
     const resolver = new PromiseResolver();
     await initializeWaitForProvisioningPage();
@@ -86,64 +78,4 @@
 
     assertTrue(provisioningComplete);
   });
-
-  test('ProvisioningFailedBlockingRetry', async () => {
-    const resolver = new PromiseResolver();
-    await initializeWaitForProvisioningPage();
-
-    const retryButton =
-        component.shadowRoot.querySelector('#retryProvisioningButton');
-    assertTrue(retryButton.hidden);
-
-    let callCount = 0;
-    service.retryProvisioning = () => {
-      callCount++;
-      return resolver.promise;
-    };
-    service.triggerProvisioningObserver(
-        ProvisioningStatus.kFailedBlocking, /* progress= */ 1.0,
-        /* delayMs= */ 0);
-    await flushTasks();
-
-    assertFalse(retryButton.hidden);
-    retryButton.click();
-
-    await flushTasks();
-    assertEquals(1, callCount);
-  });
-
-  test('ProvisioningFailedNonBlockingRetry', async () => {
-    const resolver = new PromiseResolver();
-    await initializeWaitForProvisioningPage();
-
-    const retryButton =
-        component.shadowRoot.querySelector('#retryProvisioningButton');
-    assertTrue(retryButton.hidden);
-
-    let callCount = 0;
-    service.retryProvisioning = () => {
-      callCount++;
-      return resolver.promise;
-    };
-    service.triggerProvisioningObserver(
-        ProvisioningStatus.kFailedNonBlocking, /* progress= */ 1.0,
-        /* delayMs= */ 0);
-    await flushTasks();
-
-    assertFalse(retryButton.hidden);
-    retryButton.click();
-
-    await flushTasks();
-    assertEquals(1, callCount);
-  });
-
-  test('ProvisioningFailedRetryDisabled', async () => {
-    await initializeWaitForProvisioningPage();
-
-    const retryButton =
-        component.shadowRoot.querySelector('#retryProvisioningButton');
-    assertFalse(retryButton.disabled);
-    component.allButtonsDisabled = true;
-    assertTrue(retryButton.disabled);
-  });
 }
diff --git a/chrome/test/data/webui/settings/chromeos/crostini_extra_containers_subpage_test.js b/chrome/test/data/webui/settings/chromeos/crostini_extra_containers_subpage_test.js
index 375ae138..acdf564 100644
--- a/chrome/test/data/webui/settings/chromeos/crostini_extra_containers_subpage_test.js
+++ b/chrome/test/data/webui/settings/chromeos/crostini_extra_containers_subpage_test.js
@@ -2,13 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {TestCrostiniBrowserProxy} from './test_crostini_browser_proxy.js';
-import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
-import {assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
-import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
-import {Router, routes} from 'chrome://os-settings/chromeos/os_settings.js';
-import {flushTasks} from 'chrome://test/test_util.js';
 import {CrostiniBrowserProxyImpl} from 'chrome://os-settings/chromeos/lazy_load.js';
+import {Router, routes} from 'chrome://os-settings/chromeos/os_settings.js';
+import {webUIListenerCallback} from 'chrome://resources/js/cr.m.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {flushTasks} from 'chrome://test/test_util.js';
+
+import {assertEquals, assertFalse, assertTrue} from '../../chai_assert.js';
+
+import {TestCrostiniBrowserProxy} from './test_crostini_browser_proxy.js';
 
 suite('CrostiniExtraContainersSubpageTests', function() {
   /** @type {?SettingsCrostiniPageElement} */
@@ -21,12 +24,6 @@
   let subpage;
 
   /** @type {?Element} */
-  let containerNameInput;
-
-  /** @type {?Element} */
-  let vmNameInput;
-
-  /** @type {?Element} */
   let createButton;
 
   setup(async function() {
@@ -60,14 +57,6 @@
             {'container_name': 'custom_container_2', 'vm_name': 'not_termina'},
       },
     ];
-    subpage.$$('#create').click();
-
-    await flushTasks();
-    subpage = subpage.$$('settings-crostini-create-container-dialog');
-
-    containerNameInput = subpage.root.querySelector('#containerNameInput');
-    vmNameInput = subpage.root.querySelector('#vmNameInput');
-    createButton = subpage.root.querySelector('#create');
   });
 
   teardown(function() {
@@ -75,122 +64,187 @@
     Router.getInstance().resetRouteForTesting();
   });
 
+  suite('CreateContainerDialog', function() {
+    /** @type {?Element} */
+    let containerNameInput;
 
-  /**
-   * Helper function to enter |inputValue| in the element |input| and fire an
-   * input event.
-   * @param {!Element} inputElement
-   * @param {string} inputValue
-   */
-  function setInput(inputElement, inputValue) {
-    inputElement.value = inputValue;
-    inputElement.dispatchEvent(new Event('input'));
-  }
+    /** @type {?Element} */
+    let vmNameInput;
 
-  /**
-   * Helper function to check that the containerNameInput is valid and
-   * createButton is enabled.
-   */
-  function assertValidAndEnabled() {
-    assertFalse(containerNameInput.invalid);
-    assertFalse(createButton.disabled);
-  }
+    setup(async function() {
+      subpage.$$('#create').click();
 
-  /**
-   * Helper function to check that the containerNameInput is invalid with
-   * |errorMsgName|, and createButton is disabled.
-   * @param {string} errorMsg
-   */
-  function assertInvalidAndDisabled(errorMsgName) {
-    assertTrue(containerNameInput.invalid);
-    assertTrue(createButton.disabled);
-    assertEquals(
-        containerNameInput.errorMessage, loadTimeData.getString(errorMsgName));
-  }
+      await flushTasks();
+      subpage = subpage.$$('settings-crostini-create-container-dialog');
 
-  test('AddContainerValidInDefaultVm', async function() {
-    setInput(containerNameInput, 'custom_container_2');
-    assertValidAndEnabled();
+      containerNameInput = subpage.root.querySelector('#containerNameInput');
+      vmNameInput = subpage.root.querySelector('#vmNameInput');
+      createButton = subpage.root.querySelector('#create');
+    });
 
-    createButton.click();
-    assertEquals(1, crostiniBrowserProxy.getCallCount('createContainer'));
+    /**
+     * Helper function to enter |inputValue| in the element |input| and fire an
+     * input event.
+     * @param {!Element} inputElement
+     * @param {string} inputValue
+     */
+    function setInput(inputElement, inputValue) {
+      inputElement.value = inputValue;
+      inputElement.dispatchEvent(new Event('input'));
+    }
+
+    /**
+     * Helper function to check that the containerNameInput is valid and
+     * createButton is enabled.
+     */
+    function assertValidAndEnabled() {
+      assertFalse(containerNameInput.invalid);
+      assertFalse(createButton.disabled);
+    }
+
+    /**
+     * Helper function to check that the containerNameInput is invalid with
+     * |errorMsgName|, and createButton is disabled.
+     * @param {string} errorMsg
+     */
+    function assertInvalidAndDisabled(errorMsgName) {
+      assertTrue(containerNameInput.invalid);
+      assertTrue(createButton.disabled);
+      assertEquals(
+          containerNameInput.errorMessage,
+          loadTimeData.getString(errorMsgName));
+    }
+
+    test('AddContainerValidInDefaultVm', async function() {
+      setInput(containerNameInput, 'custom_container_2');
+      assertValidAndEnabled();
+
+      createButton.click();
+      assertEquals(1, crostiniBrowserProxy.getCallCount('createContainer'));
+    });
+
+    test('AddContainerValidInNonDefaultVm', async function() {
+      setInput(containerNameInput, 'custom_container_1');
+      setInput(vmNameInput, 'not_termina');
+      assertValidAndEnabled();
+
+      createButton.click();
+      assertEquals(1, crostiniBrowserProxy.getCallCount('createContainer'));
+    });
+
+    test(
+        'ErrorAndDisabledCreateForDefaultContainerNameInDefaultVm',
+        async function() {
+          setInput(containerNameInput, 'penguin');
+
+          assertInvalidAndDisabled(
+              'crostiniExtraContainersCreateDialogContainerExistsError');
+        });
+
+    test(
+        'ErrorAndDisabledCreateForDefaultContainerNameInNonDefaultVm',
+        async function() {
+          setInput(containerNameInput, 'penguin');
+          setInput(vmNameInput, 'not_termina');
+
+          assertInvalidAndDisabled(
+              'crostiniExtraContainersCreateDialogContainerExistsError');
+        });
+
+    test(
+        'ErrorAndDisabledCreateForDuplicateContainerNameInDefaultVm',
+        async function() {
+          setInput(containerNameInput, 'custom_container_1');
+
+          assertInvalidAndDisabled(
+              'crostiniExtraContainersCreateDialogContainerExistsError');
+        });
+
+    test(
+        'ErrorAndDisabledCreateForDuplicateContainerNameInNonDefaultVm',
+        async function() {
+          setInput(containerNameInput, 'custom_container_2');
+          setInput(vmNameInput, 'not_termina');
+
+          assertInvalidAndDisabled(
+              'crostiniExtraContainersCreateDialogContainerExistsError');
+        });
+
+    test(
+        'ErrorAndDisabledCreateForEmptyContainerNameInDefaultVm',
+        async function() {
+          setInput(containerNameInput, '');
+
+          assertInvalidAndDisabled(
+              'crostiniExtraContainersCreateDialogEmptyContainerNameError');
+        });
+
+    test(
+        'ErrorAndDisabledCreateForEmptyContainerNameInNonDefaultVm',
+        async function() {
+          setInput(containerNameInput, '');
+          setInput(vmNameInput, 'not_termina');
+
+          assertInvalidAndDisabled(
+              'crostiniExtraContainersCreateDialogEmptyContainerNameError');
+        });
+
+    test('ReenabledButtonAfterError', async function() {
+      setInput(containerNameInput, 'penguin');
+      assertInvalidAndDisabled(
+          'crostiniExtraContainersCreateDialogContainerExistsError');
+
+      setInput(containerNameInput, 'custom_container_2');
+      assertValidAndEnabled();
+
+      createButton.click();
+      assertEquals(1, crostiniBrowserProxy.getCallCount('createContainer'));
+    });
   });
 
-  test('AddContainerValidInNonDefaultVm', async function() {
-    setInput(containerNameInput, 'custom_container_1');
-    setInput(vmNameInput, 'not_termina');
-    assertValidAndEnabled();
+  suite('ExportContainer', function() {
+    test('Export', async function() {
+      subpage.$$('#showContainerMenu1').click();
 
-    createButton.click();
-    assertEquals(1, crostiniBrowserProxy.getCallCount('createContainer'));
-  });
+      await flushTasks();
+      assertTrue(!!subpage.$$('#exportContainerButton'));
+      subpage.$$('#exportContainerButton').click();
+      assertEquals(
+          1, crostiniBrowserProxy.getCallCount('exportCrostiniContainer'));
+    });
 
-  test(
-      'ErrorAndDisabledCreateForDefaultContainerNameInDefaultVm',
-      async function() {
-        setInput(containerNameInput, 'penguin');
+    test('ExportImportButtonsGetDisabledOnOperationStatus', async function() {
+      subpage.$$('#showContainerMenu1').click();
 
-        assertInvalidAndDisabled(
-            'crostiniExtraContainersCreateDialogContainerExistsError');
-      });
+      await flushTasks();
+      assertFalse(subpage.$$('#exportContainerButton').disabled);
+      webUIListenerCallback(
+          'crostini-export-import-operation-status-changed', true);
 
-  test(
-      'ErrorAndDisabledCreateForDefaultContainerNameInNonDefaultVm',
-      async function() {
-        setInput(containerNameInput, 'penguin');
-        setInput(vmNameInput, 'not_termina');
+      await flushTasks();
+      assertTrue(subpage.$$('#exportContainerButton').disabled);
+      webUIListenerCallback(
+          'crostini-export-import-operation-status-changed', false);
 
-        assertInvalidAndDisabled(
-            'crostiniExtraContainersCreateDialogContainerExistsError');
-      });
+      await flushTasks();
+      assertFalse(subpage.$$('#exportContainerButton').disabled);
+    });
 
-  test(
-      'ErrorAndDisabledCreateForDuplicateContainerNameInDefaultVm',
-      async function() {
-        setInput(containerNameInput, 'custom_container_1');
+    test(
+        'ExportImportButtonsDisabledOnWhenInstallingCrostini',
+        async function() {
+          subpage.$$('#showContainerMenu1').click();
 
-        assertInvalidAndDisabled(
-            'crostiniExtraContainersCreateDialogContainerExistsError');
-      });
+          await flushTasks();
+          assertFalse(subpage.$$('#exportContainerButton').disabled);
+          webUIListenerCallback('crostini-installer-status-changed', true);
 
-  test(
-      'ErrorAndDisabledCreateForDuplicateContainerNameInNonDefaultVm',
-      async function() {
-        setInput(containerNameInput, 'custom_container_2');
-        setInput(vmNameInput, 'not_termina');
+          await flushTasks();
+          assertTrue(subpage.$$('#exportContainerButton').disabled);
+          webUIListenerCallback('crostini-installer-status-changed', false);
 
-        assertInvalidAndDisabled(
-            'crostiniExtraContainersCreateDialogContainerExistsError');
-      });
-
-  test(
-      'ErrorAndDisabledCreateForEmptyContainerNameInDefaultVm',
-      async function() {
-        setInput(containerNameInput, '');
-
-        assertInvalidAndDisabled(
-            'crostiniExtraContainersCreateDialogEmptyContainerNameError');
-      });
-
-  test(
-      'ErrorAndDisabledCreateForEmptyContainerNameInNonDefaultVm',
-      async function() {
-        setInput(containerNameInput, '');
-        setInput(vmNameInput, 'not_termina');
-
-        assertInvalidAndDisabled(
-            'crostiniExtraContainersCreateDialogEmptyContainerNameError');
-      });
-
-  test('ReenabledButtonAfterError', async function() {
-    setInput(containerNameInput, 'penguin');
-    assertInvalidAndDisabled(
-        'crostiniExtraContainersCreateDialogContainerExistsError');
-
-    setInput(containerNameInput, 'custom_container_2');
-    assertValidAndEnabled();
-
-    createButton.click();
-    assertEquals(1, crostiniBrowserProxy.getCallCount('createContainer'));
+          await flushTasks();
+          assertFalse(subpage.$$('#exportContainerButton').disabled);
+        });
   });
 });
diff --git a/chrome/utility/services.cc b/chrome/utility/services.cc
index 85ccea70..e7cb4c6 100644
--- a/chrome/utility/services.cc
+++ b/chrome/utility/services.cc
@@ -117,8 +117,6 @@
 #include "chromeos/assistant/buildflags.h"  // nogncheck
 #include "chromeos/components/local_search_service/local_search_service.h"
 #include "chromeos/components/local_search_service/public/mojom/local_search_service.mojom.h"
-#include "chromeos/components/quick_answers/public/cpp/service/spell_check_service.h"
-#include "chromeos/components/quick_answers/public/mojom/spell_check.mojom.h"
 #include "chromeos/services/tts/public/mojom/tts_service.mojom.h"
 #include "chromeos/services/tts/tts_service.h"
 
@@ -128,6 +126,11 @@
 #endif  // BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+#if BUILDFLAG(IS_CHROMEOS)
+#include "chromeos/components/quick_answers/public/cpp/service/spell_check_service.h"
+#include "chromeos/components/quick_answers/public/mojom/spell_check.mojom.h"
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
 namespace {
 
 auto RunFilePatcher(mojo::PendingReceiver<patch::mojom::FilePatcher> receiver) {
@@ -352,12 +355,6 @@
       std::move(receiver));
 }
 
-auto RunQuickAnswersSpellCheckService(
-    mojo::PendingReceiver<quick_answers::mojom::SpellCheckService> receiver) {
-  return std::make_unique<quick_answers::SpellCheckService>(
-      std::move(receiver));
-}
-
 #if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
 auto RunAssistantAudioDecoder(
     mojo::PendingReceiver<
@@ -375,6 +372,14 @@
 #endif  // BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
+#if BUILDFLAG(IS_CHROMEOS)
+auto RunQuickAnswersSpellCheckService(
+    mojo::PendingReceiver<quick_answers::mojom::SpellCheckService> receiver) {
+  return std::make_unique<quick_answers::SpellCheckService>(
+      std::move(receiver));
+}
+#endif  // BUILDFLAG(IS_CHROMEOS)
+
 }  // namespace
 
 void RegisterElevatedMainThreadServices(mojo::ServiceFactory& services) {
@@ -465,12 +470,15 @@
   services.Add(RunTtsService);
   services.Add(RunLocalSearchService);
   services.Add(RunQuickPairService);
-  services.Add(RunQuickAnswersSpellCheckService);
 #if BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
   services.Add(RunAssistantAudioDecoder);
   services.Add(RunLibassistantService);
 #endif  // BUILDFLAG(ENABLE_CROS_LIBASSISTANT)
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
+
+#if BUILDFLAG(IS_CHROMEOS)
+  services.Add(RunQuickAnswersSpellCheckService);
+#endif  // BUILDFLAG(IS_CHROMEOS)
 }
 
 void RegisterIOThreadServices(mojo::ServiceFactory& services) {
diff --git a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsComponent.java b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsComponent.java
index 1d342642..669cb90 100644
--- a/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsComponent.java
+++ b/chromecast/browser/android/apk/src/org/chromium/chromecast/shell/CastWebContentsComponent.java
@@ -9,18 +9,14 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.media.AudioManager;
 import android.net.Uri;
 import android.os.IBinder;
 import android.os.PatternMatcher;
 
 import androidx.annotation.VisibleForTesting;
 
-import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.chromecast.base.Controller;
-import org.chromium.chromecast.base.Observable;
-import org.chromium.chromecast.base.Observers;
 import org.chromium.content_public.browser.WebContents;
 
 /**
@@ -117,15 +113,6 @@
                 context, webContents, enableTouch, isRemoteControlMode, turnOnScreen, mSessionId);
         if (DEBUG) Log.d(TAG, "start activity by intent: " + intent);
         sResumeIntent.set(intent);
-
-        CastAudioManager audioManager =
-                CastAudioManager.getAudioManager(ContextUtils.getApplicationContext());
-        Observable<CastAudioManager.AudioFocusLoss> focusLoss =
-                audioManager.requestAudioFocusWhen(mAudioFocusRequestState)
-                        .filter(state -> state == CastAudioManager.AudioFocusLoss.NORMAL);
-        mAudioFocusRequestState.andThen(focusLoss).subscribe(
-                Observers.onEnter(x -> mComponentClosedHandler.onComponentClosed()));
-
         context.startActivity(intent);
     }
 
@@ -181,8 +168,6 @@
     private final boolean mIsRemoteControlMode;
     private final boolean mTurnOnScreen;
 
-    private final Controller<CastAudioFocusRequest> mAudioFocusRequestState = new Controller<>();
-
     public CastWebContentsComponent(String sessionId,
             OnComponentClosedHandler onComponentClosedHandler,
             SurfaceEventHandler surfaceEventHandler, boolean enableTouchInput,
@@ -280,9 +265,6 @@
                             + "; Visibility Priority: " + params.visibilityPriority);
         }
         mHasWebContentsState.set(params.webContents);
-        mAudioFocusRequestState.set(new CastAudioFocusRequest.Builder()
-                                            .setFocusGain(AudioManager.AUDIOFOCUS_GAIN)
-                                            .build());
         mDelegate.start(params);
         mStarted = true;
     }
@@ -294,7 +276,6 @@
                     "stop with delegate: " + mDelegate.getClass().getSimpleName()
                             + "; Instance ID: " + mSessionId);
         }
-        mAudioFocusRequestState.reset();
         mHasWebContentsState.reset();
         if (DEBUG) Log.d(TAG, "Call delegate to stop");
         mDelegate.stop(context);
diff --git a/chromecast/browser/cast_content_browser_client.cc b/chromecast/browser/cast_content_browser_client.cc
index f23c28f..03136bb 100644
--- a/chromecast/browser/cast_content_browser_client.cc
+++ b/chromecast/browser/cast_content_browser_client.cc
@@ -178,8 +178,6 @@
 
 #if BUILDFLAG(IS_ANDROID)
   cast_feature_list_creator_->SetExtraDisableFeatures({
-      ::media::kAudioFocusLossSuspendMediaSession,
-      ::media::kRequestSystemAudioFocus,
       // Disable AAudio improve AV sync performance.
       ::features::kUseAAudioDriver,
   });
diff --git a/chromecast/browser/extensions/api/tabs/tabs_api.cc b/chromecast/browser/extensions/api/tabs/tabs_api.cc
index f2944648..862bb47 100644
--- a/chromecast/browser/extensions/api/tabs/tabs_api.cc
+++ b/chromecast/browser/extensions/api/tabs/tabs_api.cc
@@ -140,12 +140,12 @@
   return tab_object;
 }
 
-std::unique_ptr<base::ListValue> CreateTabList(
-    const std::vector<CastWebContents*>& webviews,
-    const Extension* extension) {
-  std::unique_ptr<base::ListValue> tab_list(new base::ListValue());
+base::Value::List CreateTabList(const std::vector<CastWebContents*>& webviews,
+                                const Extension* extension) {
+  base::Value::List tab_list;
   for (size_t i = 0; i < webviews.size(); i++) {
-    tab_list->Append(CreateTabObject(webviews[i], extension, i)->ToValue());
+    tab_list.Append(base::Value::FromUniquePtrValue(
+        CreateTabObject(webviews[i], extension, i)->ToValue()));
   }
   return tab_list;
 }
@@ -209,27 +209,27 @@
   return -1;
 }
 
-std::unique_ptr<base::DictionaryValue> CreateWindowValueForExtension(
+base::Value::Dict CreateWindowValueForExtension(
     content::BrowserContext* browser_context,
     const Extension* extension,
     ExtensionTabUtil::PopulateTabBehavior populate_tab_behavior) {
-  auto result = std::make_unique<base::DictionaryValue>();
+  base::Value::Dict result;
 
-  result->SetInteger(keys::kIdKey, 0);
-  result->SetString(keys::kWindowTypeKey, "normal");
-  result->SetBoolean(keys::kFocusedKey, true);
-  result->SetBoolean(keys::kIncognitoKey, browser_context->IsOffTheRecord());
-  result->SetBoolean(keys::kAlwaysOnTopKey, true);
-  result->SetString(keys::kShowStateKey, "locked-fullscreen");
+  result.Set(keys::kIdKey, 0);
+  result.Set(keys::kWindowTypeKey, "normal");
+  result.Set(keys::kFocusedKey, true);
+  result.Set(keys::kIncognitoKey, browser_context->IsOffTheRecord());
+  result.Set(keys::kAlwaysOnTopKey, true);
+  result.Set(keys::kShowStateKey, "locked-fullscreen");
 
   gfx::Rect bounds(0, 0, 640, 480);
-  result->SetInteger(keys::kLeftKey, bounds.x());
-  result->SetInteger(keys::kTopKey, bounds.y());
-  result->SetInteger(keys::kWidthKey, bounds.width());
-  result->SetInteger(keys::kHeightKey, bounds.height());
+  result.Set(keys::kLeftKey, bounds.x());
+  result.Set(keys::kTopKey, bounds.y());
+  result.Set(keys::kWidthKey, bounds.width());
+  result.Set(keys::kHeightKey, bounds.height());
 
   if (populate_tab_behavior == ExtensionTabUtil::kPopulateTabs)
-    result->Set(keys::kTabsKey, CreateTabList(GetTabList(), extension));
+    result.Set(keys::kTabsKey, CreateTabList(GetTabList(), extension));
 
   return result;
 }
@@ -274,11 +274,9 @@
   ExtensionTabUtil::PopulateTabBehavior populate_tab_behavior =
       extractor.populate_tabs() ? ExtensionTabUtil::kPopulateTabs
                                 : ExtensionTabUtil::kDontPopulateTabs;
-  std::unique_ptr<base::DictionaryValue> windows =
-      CreateWindowValueForExtension(browser_context(), extension(),
-                                    populate_tab_behavior);
-  return RespondNow(
-      OneArgument(base::Value::FromUniquePtrValue(std::move(windows))));
+  base::Value::Dict windows = CreateWindowValueForExtension(
+      browser_context(), extension(), populate_tab_behavior);
+  return RespondNow(OneArgument(base::Value(std::move(windows))));
 }
 
 ExtensionFunction::ResponseAction WindowsGetCurrentFunction::Run() {
@@ -291,11 +289,9 @@
   ExtensionTabUtil::PopulateTabBehavior populate_tab_behavior =
       extractor.populate_tabs() ? ExtensionTabUtil::kPopulateTabs
                                 : ExtensionTabUtil::kDontPopulateTabs;
-  std::unique_ptr<base::DictionaryValue> windows =
-      CreateWindowValueForExtension(browser_context(), extension(),
-                                    populate_tab_behavior);
-  return RespondNow(
-      OneArgument(base::Value::FromUniquePtrValue(std::move(windows))));
+  base::Value::Dict windows = CreateWindowValueForExtension(
+      browser_context(), extension(), populate_tab_behavior);
+  return RespondNow(OneArgument(base::Value(std::move(windows))));
 }
 
 ExtensionFunction::ResponseAction WindowsGetLastFocusedFunction::Run() {
@@ -308,11 +304,9 @@
   ExtensionTabUtil::PopulateTabBehavior populate_tab_behavior =
       extractor.populate_tabs() ? ExtensionTabUtil::kPopulateTabs
                                 : ExtensionTabUtil::kDontPopulateTabs;
-  std::unique_ptr<base::DictionaryValue> windows =
-      CreateWindowValueForExtension(browser_context(), extension(),
-                                    populate_tab_behavior);
-  return RespondNow(
-      OneArgument(base::Value::FromUniquePtrValue(std::move(windows))));
+  base::Value::Dict windows = CreateWindowValueForExtension(
+      browser_context(), extension(), populate_tab_behavior);
+  return RespondNow(OneArgument(base::Value(std::move(windows))));
 }
 
 ExtensionFunction::ResponseAction WindowsGetAllFunction::Run() {
@@ -321,15 +315,14 @@
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
   ApiParameterExtractor<windows::GetAll::Params> extractor(params.get());
-  std::unique_ptr<base::ListValue> window_list(new base::ListValue());
+  base::Value::List window_list;
   ExtensionTabUtil::PopulateTabBehavior populate_tab_behavior =
       extractor.populate_tabs() ? ExtensionTabUtil::kPopulateTabs
                                 : ExtensionTabUtil::kDontPopulateTabs;
-  window_list->Append(CreateWindowValueForExtension(
+  window_list.Append(CreateWindowValueForExtension(
       browser_context(), extension(), populate_tab_behavior));
 
-  return RespondNow(
-      OneArgument(base::Value::FromUniquePtrValue(std::move(window_list))));
+  return RespondNow(OneArgument(base::Value(std::move(window_list))));
 }
 
 ExtensionFunction::ResponseAction WindowsCreateFunction::Run() {
@@ -350,9 +343,8 @@
         keys::kWindowNotFoundError, base::NumberToString(params->window_id))));
   }
 
-  return RespondNow(OneArgument(base::Value::FromUniquePtrValue(
-      CreateWindowValueForExtension(browser_context(), extension(),
-                                    ExtensionTabUtil::kDontPopulateTabs))));
+  return RespondNow(OneArgument(base::Value(CreateWindowValueForExtension(
+      browser_context(), extension(), ExtensionTabUtil::kDontPopulateTabs))));
 }
 
 ExtensionFunction::ResponseAction WindowsRemoveFunction::Run() {
@@ -399,8 +391,8 @@
     return RespondNow(Error(ErrorUtils::FormatErrorMessage(
         keys::kWindowNotFoundError, base::NumberToString(window_id))));
 
-  return RespondNow(OneArgument(base::Value::FromUniquePtrValue(
-      CreateTabList(GetTabList(), extension()))));
+  return RespondNow(
+      OneArgument(base::Value(CreateTabList(GetTabList(), extension()))));
 }
 
 ExtensionFunction::ResponseAction TabsQueryFunction::Run() {
@@ -441,8 +433,8 @@
 
   // For now, pretend that all tabs will match the query.
   // TODO(achaulk): make this actually execute the query.
-  return RespondNow(OneArgument(base::Value::FromUniquePtrValue(
-      CreateTabList(GetTabList(), extension()))));
+  return RespondNow(
+      OneArgument(base::Value(CreateTabList(GetTabList(), extension()))));
 }
 
 ExtensionFunction::ResponseAction TabsCreateFunction::Run() {
@@ -537,9 +529,8 @@
   selection.set_active(active_index);
   // TODO(achaulk): figure out what tab focus means for cast.
   NOTIMPLEMENTED() << "not changing tab focus";
-  return RespondNow(
-      OneArgument(base::Value::FromUniquePtrValue(CreateWindowValueForExtension(
-          browser_context(), extension(), ExtensionTabUtil::kPopulateTabs))));
+  return RespondNow(OneArgument(base::Value(CreateWindowValueForExtension(
+      browser_context(), extension(), ExtensionTabUtil::kPopulateTabs))));
 }
 
 bool TabsHighlightFunction::HighlightTab(
diff --git a/chromecast/browser/extensions/api/tts/tts_extension_api.cc b/chromecast/browser/extensions/api/tts/tts_extension_api.cc
index aeb84fb..be7da11 100644
--- a/chromecast/browser/extensions/api/tts/tts_extension_api.cc
+++ b/chromecast/browser/extensions/api/tts/tts_extension_api.cc
@@ -129,25 +129,24 @@
   }
 
   const char* event_type_string = TtsEventTypeToString(event_type);
-  std::unique_ptr<base::DictionaryValue> details(new base::DictionaryValue());
+  base::Value::Dict details;
   if (char_index >= 0)
-    details->SetInteger(constants::kCharIndexKey, char_index);
+    details.Set(constants::kCharIndexKey, char_index);
   if (length >= 0)
-    details->SetInteger(constants::kLengthKey, length);
-  details->SetString(constants::kEventTypeKey, event_type_string);
+    details.Set(constants::kLengthKey, length);
+  details.Set(constants::kEventTypeKey, event_type_string);
   if (event_type == content::TTS_EVENT_ERROR) {
-    details->SetString(constants::kErrorMessageKey, error_message);
+    details.Set(constants::kErrorMessageKey, error_message);
   }
-  details->SetInteger(constants::kSrcIdKey, utterance->GetSrcId());
-  details->SetBoolean(constants::kIsFinalEventKey, utterance->IsFinished());
+  details.Set(constants::kSrcIdKey, utterance->GetSrcId());
+  details.Set(constants::kIsFinalEventKey, utterance->IsFinished());
 
-  std::unique_ptr<base::ListValue> arguments(new base::ListValue());
-  arguments->Append(std::move(details));
+  std::vector<base::Value> arguments;
+  arguments.emplace_back(std::move(details));
 
   auto event = std::make_unique<extensions::Event>(
       ::extensions::events::TTS_ON_EVENT, ::events::kOnEvent,
-      std::move(*arguments).TakeListDeprecated(),
-      utterance->GetBrowserContext());
+      std::move(arguments), utterance->GetBrowserContext());
   event->event_url = utterance->GetSrcUrl();
   extensions::EventRouter::Get(utterance->GetBrowserContext())
       ->DispatchEventToExtension(src_extension_id_, std::move(event));
@@ -315,30 +314,28 @@
   content::TtsController::GetInstance()->GetVoices(browser_context(), GURL(),
                                                    &voices);
 
-  auto result_voices = std::make_unique<base::ListValue>();
+  base::Value::List result_voices;
   for (size_t i = 0; i < voices.size(); ++i) {
     const content::VoiceData& voice = voices[i];
-    std::unique_ptr<base::DictionaryValue> result_voice(
-        new base::DictionaryValue());
-    result_voice->SetString(constants::kVoiceNameKey, voice.name);
-    result_voice->SetBoolean(constants::kRemoteKey, voice.remote);
+    base::Value::Dict result_voice;
+    result_voice.Set(constants::kVoiceNameKey, voice.name);
+    result_voice.Set(constants::kRemoteKey, voice.remote);
     if (!voice.lang.empty())
-      result_voice->SetString(constants::kLangKey, voice.lang);
+      result_voice.Set(constants::kLangKey, voice.lang);
     if (!voice.engine_id.empty())
-      result_voice->SetString(constants::kExtensionIdKey, voice.engine_id);
+      result_voice.Set(constants::kExtensionIdKey, voice.engine_id);
 
-    auto event_types = std::make_unique<base::ListValue>();
+    base::Value::List event_types;
     for (auto iter = voice.events.begin(); iter != voice.events.end(); ++iter) {
       const char* event_name_constant = TtsEventTypeToString(*iter);
-      event_types->Append(event_name_constant);
+      event_types.Append(event_name_constant);
     }
-    result_voice->Set(constants::kEventTypesKey, std::move(event_types));
+    result_voice.Set(constants::kEventTypesKey, std::move(event_types));
 
-    result_voices->Append(std::move(result_voice));
+    result_voices.Append(std::move(result_voice));
   }
 
-  return RespondNow(
-      OneArgument(base::Value::FromUniquePtrValue(std::move(result_voices))));
+  return RespondNow(OneArgument(base::Value(std::move(result_voices))));
 }
 
 TtsAPI::TtsAPI(content::BrowserContext* context) {
diff --git a/chromecast/cast_core/runtime/browser/streaming_controller_base.cc b/chromecast/cast_core/runtime/browser/streaming_controller_base.cc
index 58ba3b3..999128b 100644
--- a/chromecast/cast_core/runtime/browser/streaming_controller_base.cc
+++ b/chromecast/cast_core/runtime/browser/streaming_controller_base.cc
@@ -34,12 +34,12 @@
 
   navigation_handle->GetRenderFrameHost()
       ->GetRemoteAssociatedInterfaces()
-      ->GetInterface(&cast_streaming_receiver_);
+      ->GetInterface(&demuxer_connector_);
   navigation_handle->GetRenderFrameHost()
       ->GetRemoteAssociatedInterfaces()
       ->GetInterface(&renderer_connection_);
 
-  DCHECK(cast_streaming_receiver_);
+  DCHECK(demuxer_connector_);
   DCHECK(renderer_connection_);
 
   TryStartPlayback();
@@ -69,7 +69,7 @@
 }
 
 void StreamingControllerBase::TryStartPlayback() {
-  if (playback_started_cb_ && constraints_ && cast_streaming_receiver_) {
+  if (playback_started_cb_ && constraints_ && demuxer_connector_) {
     cast_streaming::ReceiverSession::MessagePortProvider message_port_provider =
         base::BindOnce(
             [](std::unique_ptr<cast_api_bindings::MessagePort> port) {
@@ -80,7 +80,7 @@
         std::move(constraints_), std::move(message_port_provider), client_);
     DCHECK(receiver_session_);
 
-    StartPlayback(receiver_session_.get(), std::move(cast_streaming_receiver_),
+    StartPlayback(receiver_session_.get(), std::move(demuxer_connector_),
                   std::move(renderer_connection_));
     std::move(playback_started_cb_).Run();
   }
diff --git a/chromecast/cast_core/runtime/browser/streaming_controller_base.h b/chromecast/cast_core/runtime/browser/streaming_controller_base.h
index eca17cc..f159f4e 100644
--- a/chromecast/cast_core/runtime/browser/streaming_controller_base.h
+++ b/chromecast/cast_core/runtime/browser/streaming_controller_base.h
@@ -12,7 +12,7 @@
 #include "base/sequence_checker.h"
 #include "chromecast/browser/cast_web_contents.h"
 #include "components/cast_streaming/browser/public/receiver_session.h"
-#include "components/cast_streaming/public/mojom/cast_streaming_session.mojom.h"
+#include "components/cast_streaming/public/mojom/demuxer_connector.mojom.h"
 #include "components/cast_streaming/public/mojom/renderer_controller.mojom.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 
@@ -45,8 +45,8 @@
   // Begins playback of |receiver_session|.
   virtual void StartPlayback(
       cast_streaming::ReceiverSession* receiver_session,
-      mojo::AssociatedRemote<cast_streaming::mojom::CastStreamingReceiver>
-          cast_streaming_receiver,
+      mojo::AssociatedRemote<cast_streaming::mojom::DemuxerConnector>
+          demuxer_connector,
       mojo::AssociatedRemote<cast_streaming::mojom::RendererController>
           renderer_connection) = 0;
 
@@ -86,8 +86,8 @@
   // Mojo connections. Initially populated in MainFrameReadyToCommitNavigation()
   // with connections to the Renderer process, and transferred to
   // StartPlayback() when it is first called.
-  mojo::AssociatedRemote<cast_streaming::mojom::CastStreamingReceiver>
-      cast_streaming_receiver_;
+  mojo::AssociatedRemote<cast_streaming::mojom::DemuxerConnector>
+      demuxer_connector_;
   mojo::AssociatedRemote<cast_streaming::mojom::RendererController>
       renderer_connection_;
 
diff --git a/chromecast/cast_core/runtime/browser/streaming_controller_mirroring.cc b/chromecast/cast_core/runtime/browser/streaming_controller_mirroring.cc
index 1e8f49f..fb94f71 100644
--- a/chromecast/cast_core/runtime/browser/streaming_controller_mirroring.cc
+++ b/chromecast/cast_core/runtime/browser/streaming_controller_mirroring.cc
@@ -23,11 +23,11 @@
 
 void StreamingControllerMirroring::StartPlayback(
     cast_streaming::ReceiverSession* receiver_session,
-    mojo::AssociatedRemote<cast_streaming::mojom::CastStreamingReceiver>
-        cast_streaming_receiver,
+    mojo::AssociatedRemote<cast_streaming::mojom::DemuxerConnector>
+        demuxer_connector,
     mojo::AssociatedRemote<cast_streaming::mojom::RendererController>
         renderer_connection) {
-  receiver_session->StartStreamingAsync(std::move(cast_streaming_receiver));
+  receiver_session->StartStreamingAsync(std::move(demuxer_connector));
 
   renderer_connection_ = std::move(renderer_connection);
   renderer_connection_->SetPlaybackController(
diff --git a/chromecast/cast_core/runtime/browser/streaming_controller_mirroring.h b/chromecast/cast_core/runtime/browser/streaming_controller_mirroring.h
index 45da2c8b..13d07a9 100644
--- a/chromecast/cast_core/runtime/browser/streaming_controller_mirroring.h
+++ b/chromecast/cast_core/runtime/browser/streaming_controller_mirroring.h
@@ -8,7 +8,7 @@
 #include <memory>
 
 #include "chromecast/cast_core/runtime/browser/streaming_controller_base.h"
-#include "components/cast_streaming/public/mojom/cast_streaming_session.mojom.h"
+#include "components/cast_streaming/public/mojom/demuxer_connector.mojom.h"
 #include "components/cast_streaming/public/mojom/renderer_controller.mojom.h"
 #include "media/mojo/mojom/renderer.mojom.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
@@ -40,8 +40,8 @@
   // StreamingControllerBase overrides:
   void StartPlayback(
       cast_streaming::ReceiverSession* receiver_session,
-      mojo::AssociatedRemote<cast_streaming::mojom::CastStreamingReceiver>
-          cast_streaming_receiver,
+      mojo::AssociatedRemote<cast_streaming::mojom::DemuxerConnector>
+          demuxer_connector,
       mojo::AssociatedRemote<cast_streaming::mojom::RendererController>
           renderer_connection) override;
   void ProcessAVConstraints(
diff --git a/chromecast/cast_core/runtime/browser/streaming_controller_remoting.cc b/chromecast/cast_core/runtime/browser/streaming_controller_remoting.cc
index c4fc8ed..59b4cbf8 100644
--- a/chromecast/cast_core/runtime/browser/streaming_controller_remoting.cc
+++ b/chromecast/cast_core/runtime/browser/streaming_controller_remoting.cc
@@ -19,11 +19,11 @@
 
 void StreamingControllerRemoting::StartPlayback(
     cast_streaming::ReceiverSession* receiver_session,
-    mojo::AssociatedRemote<cast_streaming::mojom::CastStreamingReceiver>
-        cast_streaming_receiver,
+    mojo::AssociatedRemote<cast_streaming::mojom::DemuxerConnector>
+        demuxer_connector,
     mojo::AssociatedRemote<cast_streaming::mojom::RendererController>
         renderer_connection) {
-  receiver_session->StartStreamingAsync(std::move(cast_streaming_receiver),
+  receiver_session->StartStreamingAsync(std::move(demuxer_connector),
                                         std::move(renderer_connection));
 
   auto* renderer_controller = receiver_session->GetRendererControls();
diff --git a/chromecast/cast_core/runtime/browser/streaming_controller_remoting.h b/chromecast/cast_core/runtime/browser/streaming_controller_remoting.h
index a918162..be19fd6 100644
--- a/chromecast/cast_core/runtime/browser/streaming_controller_remoting.h
+++ b/chromecast/cast_core/runtime/browser/streaming_controller_remoting.h
@@ -8,7 +8,7 @@
 #include <memory>
 
 #include "chromecast/cast_core/runtime/browser/streaming_controller_base.h"
-#include "components/cast_streaming/public/mojom/cast_streaming_session.mojom.h"
+#include "components/cast_streaming/public/mojom/demuxer_connector.mojom.h"
 #include "components/cast_streaming/public/mojom/renderer_controller.mojom.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 
@@ -37,8 +37,8 @@
   // StreamingControllerBase overrides:
   void StartPlayback(
       cast_streaming::ReceiverSession* receiver_session,
-      mojo::AssociatedRemote<cast_streaming::mojom::CastStreamingReceiver>
-          cast_streaming_receiver,
+      mojo::AssociatedRemote<cast_streaming::mojom::DemuxerConnector>
+          demuxer_connector,
       mojo::AssociatedRemote<cast_streaming::mojom::RendererController>
           renderer_connection) override;
   void ProcessAVConstraints(
diff --git a/chromecast/cast_core/runtime/browser/streaming_receiver_session_client_unittest.cc b/chromecast/cast_core/runtime/browser/streaming_receiver_session_client_unittest.cc
index f76dd9b..2984554 100644
--- a/chromecast/cast_core/runtime/browser/streaming_receiver_session_client_unittest.cc
+++ b/chromecast/cast_core/runtime/browser/streaming_receiver_session_client_unittest.cc
@@ -9,7 +9,6 @@
 #include "chromecast/cast_core/runtime/browser/streaming_controller.h"
 #include "chromecast/shared/platform_info_serializer.h"
 #include "components/cast_streaming/browser/public/receiver_session.h"
-#include "components/cast_streaming/public/mojom/cast_streaming_session.mojom.h"
 #include "components/cast_streaming/public/mojom/renderer_controller.mojom.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
diff --git a/chromecast/renderer/cast_content_renderer_client.cc b/chromecast/renderer/cast_content_renderer_client.cc
index 61942397..4f1a82b 100644
--- a/chromecast/renderer/cast_content_renderer_client.cc
+++ b/chromecast/renderer/cast_content_renderer_client.cc
@@ -162,10 +162,6 @@
 
   // Lifetime is tied to |render_frame| via content::RenderFrameObserver.
   if (render_frame->IsMainFrame()) {
-    if (main_frame_feature_manager_on_associated_interface_) {
-      LOG(DFATAL) << "main_frame_feature_manager_on_associated_interface_ gets "
-                     "overwritten.";
-    }
     main_frame_feature_manager_on_associated_interface_ =
         new FeatureManagerOnAssociatedInterface(render_frame);
   } else {
diff --git a/chromeos/SECURITY_OWNERS b/chromeos/SECURITY_OWNERS
index ccba198..00e488475 100644
--- a/chromeos/SECURITY_OWNERS
+++ b/chromeos/SECURITY_OWNERS
@@ -1,2 +1 @@
 jorgelo@chromium.org
-kerrnel@chromium.org
diff --git a/chromeos/ash/components/dbus/cicerone/cicerone_client.cc b/chromeos/ash/components/dbus/cicerone/cicerone_client.cc
index 7c4ecc4..5deea896 100644
--- a/chromeos/ash/components/dbus/cicerone/cicerone_client.cc
+++ b/chromeos/ash/components/dbus/cicerone/cicerone_client.cc
@@ -21,7 +21,7 @@
 #include "third_party/cros_system_api/dbus/service_constants.h"
 #include "third_party/cros_system_api/dbus/vm_cicerone/dbus-constants.h"
 
-namespace chromeos {
+namespace ash {
 
 namespace {
 
@@ -1186,4 +1186,4 @@
   return g_instance;
 }
 
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chromeos/ash/components/dbus/cicerone/cicerone_client.h b/chromeos/ash/components/dbus/cicerone/cicerone_client.h
index c5c246b0..182efd55 100644
--- a/chromeos/ash/components/dbus/cicerone/cicerone_client.h
+++ b/chromeos/ash/components/dbus/cicerone/cicerone_client.h
@@ -11,7 +11,7 @@
 #include "chromeos/dbus/common/dbus_method_call_status.h"
 #include "dbus/object_proxy.h"
 
-namespace chromeos {
+namespace ash {
 
 // CiceroneClient is used to communicate with Cicerone, which is used to
 // communicate with containers running inside VMs.
@@ -383,11 +383,6 @@
   CiceroneClient();
 };
 
-}  // namespace chromeos
-
-// TODO(https://crbug.com/1164001): remove when moved to ash.
-namespace ash {
-using ::chromeos::CiceroneClient;
 }  // namespace ash
 
 #endif  // CHROMEOS_ASH_COMPONENTS_DBUS_CICERONE_CICERONE_CLIENT_H_
diff --git a/chromeos/ash/components/dbus/cicerone/fake_cicerone_client.cc b/chromeos/ash/components/dbus/cicerone/fake_cicerone_client.cc
index f1057d4..5e73eed 100644
--- a/chromeos/ash/components/dbus/cicerone/fake_cicerone_client.cc
+++ b/chromeos/ash/components/dbus/cicerone/fake_cicerone_client.cc
@@ -10,7 +10,7 @@
 #include "base/check_op.h"
 #include "base/threading/thread_task_runner_handle.h"
 
-namespace chromeos {
+namespace ash {
 
 namespace {
 
@@ -572,4 +572,4 @@
   return g_instance;
 }
 
-}  // namespace chromeos
+}  // namespace ash
diff --git a/chromeos/ash/components/dbus/cicerone/fake_cicerone_client.h b/chromeos/ash/components/dbus/cicerone/fake_cicerone_client.h
index 65ad0cf..d6886aa 100644
--- a/chromeos/ash/components/dbus/cicerone/fake_cicerone_client.h
+++ b/chromeos/ash/components/dbus/cicerone/fake_cicerone_client.h
@@ -10,7 +10,7 @@
 #include "base/time/time.h"
 #include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
 
-namespace chromeos {
+namespace ash {
 
 // FakeCiceroneClient is a fake implementation of CiceroneClient used for
 // testing.
@@ -512,11 +512,6 @@
   base::WeakPtrFactory<FakeCiceroneClient> weak_factory_{this};
 };
 
-}  // namespace chromeos
-
-// TODO(https://crbug.com/1164001): remove when it moved to ash.
-namespace ash {
-using ::chromeos::FakeCiceroneClient;
-}
+}  // namespace ash
 
 #endif  // CHROMEOS_ASH_COMPONENTS_DBUS_CICERONE_FAKE_CICERONE_CLIENT_H_
diff --git a/chromeos/ash/components/dbus/concierge/concierge_client.h b/chromeos/ash/components/dbus/concierge/concierge_client.h
index a804d51d..6c2e7cbe 100644
--- a/chromeos/ash/components/dbus/concierge/concierge_client.h
+++ b/chromeos/ash/components/dbus/concierge/concierge_client.h
@@ -8,8 +8,6 @@
 #include "base/component_export.h"
 #include "base/files/scoped_file.h"
 #include "base/observer_list.h"
-// TODO(https://crbug.com/1164001): move to forward declaration
-#include "chromeos/ash/components/dbus/cicerone/fake_cicerone_client.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_service.pb.h"
 #include "chromeos/dbus/common/dbus_client.h"
 #include "chromeos/dbus/common/dbus_method_call_status.h"
@@ -17,6 +15,8 @@
 
 namespace ash {
 
+class FakeCiceroneClient;
+
 // ConciergeClient is used to communicate with Concierge, which is used to
 // start and stop VMs, as well as for disk image management.
 class COMPONENT_EXPORT(CONCIERGE) ConciergeClient : public DBusClient {
diff --git a/chromeos/ash/components/dbus/concierge/fake_concierge_client.h b/chromeos/ash/components/dbus/concierge/fake_concierge_client.h
index e36caed..7345e91 100644
--- a/chromeos/ash/components/dbus/concierge/fake_concierge_client.h
+++ b/chromeos/ash/components/dbus/concierge/fake_concierge_client.h
@@ -11,13 +11,13 @@
 #include "base/observer_list.h"
 #include "base/time/time.h"
 #include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
-// TODO(https://crbug.com/1164001): move to forward declaration
-#include "chromeos/ash/components/dbus/cicerone/fake_cicerone_client.h"
 #include "chromeos/ash/components/dbus/concierge/concierge_client.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace ash {
 
+class FakeCiceroneClient;
+
 // FakeConciergeClient is a light mock of ConciergeClient used for testing.
 class COMPONENT_EXPORT(CONCIERGE) FakeConciergeClient : public ConciergeClient {
  public:
diff --git a/chromeos/chromeos_strings.grd b/chromeos/chromeos_strings.grd
index b97738f..035834f1 100644
--- a/chromeos/chromeos_strings.grd
+++ b/chromeos/chromeos_strings.grd
@@ -2890,24 +2890,9 @@
         Retry finalization
       </message>
       <!-- Device provisioning page -->
-      <message name="IDS_SHIMLESS_RMA_PROVISIONING_TITLE" translateable="false" desc="Title for the device provisioning page. Provisioning is when component specific data is set e.g. erasing fingerprint data from the thumb reader or regenerating the device stable secret before returning the device to a new owner.">
+      <message name="IDS_SHIMLESS_RMA_PROVISIONING_TITLE" desc="Title for the device provisioning page. Provisioning is when component specific data is set e.g. erasing fingerprint data from the thumb reader or regenerating the device stable secret before returning the device to a new owner.">
         Provisioning the device...
       </message>
-      <message name="IDS_SHIMLESS_RMA_PROVISIONING_IN_PROGRESS" translateable="false" desc="Message to display while the device is provisioning.">
-        Provisioning the device. Do not turn off the power.
-      </message>
-      <message name="IDS_SHIMLESS_RMA_PROVISIONING_COMPLETE" translateable="false" desc="Message to display when provisioning is complete.">
-        Complete.
-      </message>
-      <message name="IDS_SHIMLESS_RMA_PROVISIONING_FAILED_BLOCKING" translateable="false" desc="Message to display when provisioning failed and RMA cannot complete.">
-        Failed, blocking.
-      </message>
-      <message name="IDS_SHIMLESS_RMA_PROVISIONING_FAILED_NON_BLOCKING" translateable="false" desc="Message to display when provisioning failed, but RMA can continue.">
-        Failed, non blocking.
-      </message>
-      <message name="IDS_SHIMLESS_RMA_PROVISIONING_FAILED_RETRY_BUTTON_LABEL" translateable="false" desc="The label for the button to retry provisioning.">
-        Retry provisioning
-      </message>
       <!-- Repair complete page -->
       <message name="IDS_SHIMLESS_RMA_REPAIR_COMPLETED" desc="Title for the page shown when the RMA process repair process is completed.">
         Repair is complete
@@ -3045,57 +3030,54 @@
         Enable write-protect to continue to the next screen. Learn how to enable write-protect for this device by viewing the manufacturer's instructions.
       </message>
       <!-- Confirm device information page -->
-      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_TITLE" translateable="false" desc="The title for the page for confirming the device info.">
+      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_TITLE" desc="The title for the page for confirming the device info.">
         Please confirm device information
       </message>
-      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_INSTRUCTIONS" translateable="false" desc="The instructions for the page for confirming the device info.">
+      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_INSTRUCTIONS" desc="The instructions for the page for confirming the device info.">
         Change device information as needed.
       </message>
-      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_SERIAL_NUMBER_LABEL" translateable="false" desc="The label for the text input showing the device's serial number.">
+      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_SERIAL_NUMBER_LABEL" desc="The label for the text input showing the device's serial number.">
         Serial number
       </message>
-      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_REGION_LABEL" translateable="false" desc="The label for the text input showing the device's current region.">
+      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_REGION_LABEL" desc="The label for the text input showing the device's current region.">
         Region
       </message>
-      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_WHITE_LABEL_LABEL" translateable="false" desc="The label for the text input showing the device's white-label.">
+      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_WHITE_LABEL_LABEL" desc="The label for the text input showing the device's white-label.">
         White-label
       </message>
-      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_EMPTY_WHITE_LABEL_LABEL" translateable="false" desc="The label for the selection option that clears the device's white-label.">
+      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_EMPTY_WHITE_LABEL_LABEL" desc="The label for the selection option that clears the device's white-label.">
         --no white-label--
       </message>
-      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_SKU_LABEL" translateable="false" desc="The label for the text input showing the device's SKU.">
+      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_SKU_LABEL" desc="The label for the text input showing the device's SKU.">
         SKU
       </message>
-      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_RESET_BUTTON_LABEL" translateable="false" desc="The label for the button that resets the text input.">
+      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_RESET_BUTTON_LABEL" desc="The label for the button that resets the text input.">
         Reset
       </message>
-      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_SKU_WARNING" translateable="false" desc="The text warning explaining when the device's SKU should be changed.">
+      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_SKU_WARNING" desc="The text warning explaining when the device's SKU should be changed.">
         The SKU should only be changed if the new component(s) are different from the ones they replaced. For example, a touchscreen replacing a non-touchscreen, or memory being upgraded from 8GB to 16GB.
       </message>
-      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_DRAM_PART_NUMBER_LABEL" translateable="false" desc="The label for the text input showing the device's dram part number.">
+      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_DRAM_PART_NUMBER_LABEL" desc="The label for the text input showing the device's dram part number.">
         DRAM part number
       </message>
-      <message name="IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_DRAM_PART_NUMBER_PLACEHOLDER_LABEL" translateable="false" desc="The placeholder label for the text input box for the device's dram part number.">
-        Enter DRAM part number
-      </message>
       <!-- RO firmware reimaging page -->
-      <message name="IDS_SHIMLESS_RMA_FIRMWARE_UPDATE_TITLE" translateable="false" desc="The title for the page when reimaging is in progress.">
-      Install firmware image
+      <message name="IDS_SHIMLESS_RMA_FIRMWARE_UPDATE_TITLE" desc="The title for the page when reimaging is in progress.">
+        Install firmware image
       </message>
-      <message name="IDS_SHIMLESS_RMA_FIRMWARE_WAIT_FOR_USB" translateable="false" desc="The prompt to insert a thumb drive with the recovery image for this device.">
-        Insert your thumb drive with the downloaded recovery image.
+      <message name="IDS_SHIMLESS_RMA_FIRMWARE_WAIT_FOR_USB" desc="The prompt to insert a thumb drive with the recovery image for this device.">
+        Insert external storage, such as a USB drive or an SD card, with the downloaded Chromebook Recovery Utility image
       </message>
-      <message name="IDS_SHIMLESS_RMA_FIRMWARE_FILE_NOT_FOUND" translateable="false" desc="The message when a thumb drive is detected but it does not conatin a valid recovery image for this device.">
-        No image found. Insert a valid USB thumb drive
+      <message name="IDS_SHIMLESS_RMA_FIRMWARE_FILE_NOT_FOUND" desc="The message when a thumb drive is detected but it does not conatin a valid recovery image for this device.">
+        Couldn’t find firmware image. Insert external storage, such as a USB drive or an SD card, with the downloaded Chromebook Recovery Utility image.
       </message>
-      <message name="IDS_SHIMLESS_RMA_FIRMWARE_UPDATING" translateable="false" desc="The message when the firmware is being installed.">
-      Installing firmware...
+      <message name="IDS_SHIMLESS_RMA_FIRMWARE_UPDATING" desc="The message when the firmware is being installed.">
+        Installing firmware...
       </message>
-      <message name="IDS_SHIMLESS_RMA_FIRMWARE_REBOOT" translateable="false" desc="The message when firmware is installed and the required reboot is about to take place.">
-      About to reboot...
+      <message name="IDS_SHIMLESS_RMA_FIRMWARE_REBOOT" desc="The message when firmware is installed and the required reboot is about to take place.">
+        Install is complete. Preparing to restart...
       </message>
-      <message name="IDS_SHIMLESS_RMA_FIRMWARE_UPDATE_COMPLETE" translateable="false" desc="The message when firmware installation is complete and the device has rebooted.">
-      Installation complete.
+      <message name="IDS_SHIMLESS_RMA_FIRMWARE_UPDATE_COMPLETE" desc="The message when firmware installation is complete and the device has rebooted.">
+        Installation complete.
       </message>
       <!-- Onboarding update page -->
       <message name="IDS_SHIMLESS_RMA_ONBOARDING_UPDATE_CONNECT" translateable="false" desc="The message that you can connect to the internet on the prevous page.">
diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_DRAM_PART_NUMBER_LABEL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_DRAM_PART_NUMBER_LABEL.png.sha1
new file mode 100644
index 0000000..cfd6be603
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_DRAM_PART_NUMBER_LABEL.png.sha1
@@ -0,0 +1 @@
+15ee5ce32817c48119fb40c00836fbbe472ae4b8
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_EMPTY_WHITE_LABEL_LABEL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_EMPTY_WHITE_LABEL_LABEL.png.sha1
new file mode 100644
index 0000000..503a65b
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_EMPTY_WHITE_LABEL_LABEL.png.sha1
@@ -0,0 +1 @@
+d40ba385baf7956435f90e0c806d9a396c9efb34
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_INSTRUCTIONS.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_INSTRUCTIONS.png.sha1
new file mode 100644
index 0000000..513f5ff
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_INSTRUCTIONS.png.sha1
@@ -0,0 +1 @@
+dcc547d7dd4bf72c6a3881557047ccf9bb1b817d
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_REGION_LABEL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_REGION_LABEL.png.sha1
new file mode 100644
index 0000000..e3f9e7a5
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_REGION_LABEL.png.sha1
@@ -0,0 +1 @@
+45472eb934d28d8f1e31341e03b9aed3965683dc
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_RESET_BUTTON_LABEL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_RESET_BUTTON_LABEL.png.sha1
new file mode 100644
index 0000000..dc085ed8
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_RESET_BUTTON_LABEL.png.sha1
@@ -0,0 +1 @@
+dec93a194e8e868a3b48f4fcfcc060bd585d1c8c
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_SERIAL_NUMBER_LABEL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_SERIAL_NUMBER_LABEL.png.sha1
new file mode 100644
index 0000000..c85cc52e
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_SERIAL_NUMBER_LABEL.png.sha1
@@ -0,0 +1 @@
+f5afacc7bc844c2e5c20fb680e87deac59db3da7
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_SKU_LABEL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_SKU_LABEL.png.sha1
new file mode 100644
index 0000000..328d855
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_SKU_LABEL.png.sha1
@@ -0,0 +1 @@
+9c43ebbae20f53cb8593cfb0b5edd44fd44da9c2
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_SKU_WARNING.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_SKU_WARNING.png.sha1
new file mode 100644
index 0000000..12e160c
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_SKU_WARNING.png.sha1
@@ -0,0 +1 @@
+5ef862f4ca3a8ae9a7074e8a7ab51fd2975a85f4
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_TITLE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_TITLE.png.sha1
new file mode 100644
index 0000000..e8ef8c2
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_TITLE.png.sha1
@@ -0,0 +1 @@
+d7d6f0e111b4f76064b2c0dd458781bc9fe6fb23
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_WHITE_LABEL_LABEL.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_WHITE_LABEL_LABEL.png.sha1
new file mode 100644
index 0000000..5d91619
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_CONFIRM_DEVICE_INFO_WHITE_LABEL_LABEL.png.sha1
@@ -0,0 +1 @@
+6ff10e13f25f61d409bcff0e68b59d6caeb8b4f4
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_FILE_NOT_FOUND.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_FILE_NOT_FOUND.png.sha1
new file mode 100644
index 0000000..51c0b544
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_FILE_NOT_FOUND.png.sha1
@@ -0,0 +1 @@
+96e0bd61aa547d89fc89537fc4fffff8042205a2
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_REBOOT.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_REBOOT.png.sha1
new file mode 100644
index 0000000..d5ee931c
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_REBOOT.png.sha1
@@ -0,0 +1 @@
+7cb2ecfcedf78f17b2eed4d514696ab4cf8dbcc8
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_UPDATE_COMPLETE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_UPDATE_COMPLETE.png.sha1
new file mode 100644
index 0000000..f08963f
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_UPDATE_COMPLETE.png.sha1
@@ -0,0 +1 @@
+752fac072ce209d2098651bc8f10558d36f9e3bc
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_UPDATE_TITLE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_UPDATE_TITLE.png.sha1
new file mode 100644
index 0000000..030ed992
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_UPDATE_TITLE.png.sha1
@@ -0,0 +1 @@
+7b480e0ce5a6ada4df55eca10915bbe92c652d70
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_UPDATING.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_UPDATING.png.sha1
new file mode 100644
index 0000000..7db156e
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_UPDATING.png.sha1
@@ -0,0 +1 @@
+16e5a0e914aa97d8edf0bbf0b87d144244ec89be
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_WAIT_FOR_USB.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_WAIT_FOR_USB.png.sha1
new file mode 100644
index 0000000..030ed992
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_FIRMWARE_WAIT_FOR_USB.png.sha1
@@ -0,0 +1 @@
+7b480e0ce5a6ada4df55eca10915bbe92c652d70
\ No newline at end of file
diff --git a/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_PROVISIONING_TITLE.png.sha1 b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_PROVISIONING_TITLE.png.sha1
new file mode 100644
index 0000000..6adf0e3
--- /dev/null
+++ b/chromeos/chromeos_strings_grd/IDS_SHIMLESS_RMA_PROVISIONING_TITLE.png.sha1
@@ -0,0 +1 @@
+fc64cb72b0c455c7131677c98b2ac3617c39f952
\ No newline at end of file
diff --git a/chromeos/components/cros_elements/tsconfig_base.json b/chromeos/components/cros_elements/tsconfig_base.json
index 7a822603..9ac4e72 100644
--- a/chromeos/components/cros_elements/tsconfig_base.json
+++ b/chromeos/components/cros_elements/tsconfig_base.json
@@ -12,7 +12,6 @@
     "experimentalDecorators": true,
     "strict": true,
     "noImplicitAny": false,
-    "composite": true,
     "importHelpers": true,
     "plugins": [
       {
diff --git a/chromeos/components/quick_answers/understanding/intent_generator.cc b/chromeos/components/quick_answers/understanding/intent_generator.cc
index a74fc8ad..2195420 100644
--- a/chromeos/components/quick_answers/understanding/intent_generator.cc
+++ b/chromeos/components/quick_answers/understanding/intent_generator.cc
@@ -4,6 +4,7 @@
 
 #include "chromeos/components/quick_answers/understanding/intent_generator.h"
 
+#include <cctype>
 #include <map>
 
 #include "base/i18n/break_iterator.h"
@@ -125,6 +126,14 @@
   return false;
 }
 
+bool HasDigits(const std::string& word) {
+  for (const auto& character : word) {
+    if (std::isdigit(character))
+      return true;
+  }
+  return false;
+}
+
 }  // namespace
 
 IntentGenerator::IntentGenerator(base::WeakPtr<SpellChecker> spell_checker,
@@ -191,7 +200,11 @@
 void IntentGenerator::CheckSpellingCallback(const QuickAnswersRequest& request,
                                             bool correctness) {
   // Generate dictionary intent if the selected word passed spell check.
-  if (correctness) {
+  // The dictionaries treat digits as valid words, while we will not be able to
+  // grab any useful information from the Search server for words like that.
+  // Thus we filter out the words containing digits. We still fallback to the
+  // text classifier for unit conversion intent.
+  if (correctness && !HasDigits(request.selected_text)) {
     std::move(complete_callback_)
         .Run(IntentInfo(request.selected_text, IntentType::kDictionary,
                         QuickAnswersState::Get()->application_locale()));
diff --git a/chromeos/components/quick_answers/understanding/intent_generator_unittest.cc b/chromeos/components/quick_answers/understanding/intent_generator_unittest.cc
index 0d8c346..cac724b6 100644
--- a/chromeos/components/quick_answers/understanding/intent_generator_unittest.cc
+++ b/chromeos/components/quick_answers/understanding/intent_generator_unittest.cc
@@ -577,6 +577,34 @@
   EXPECT_EQ(kWord, intent_info_.intent_text);
 }
 
+TEST_F(IntentGeneratorTest, ShouldTriggerForSingleWordInDictionaryWithDigits) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(
+      chromeos::features::kQuickAnswersAlwaysTriggerForSingleWord);
+
+  const std::string kWord = "1st";
+
+  // No Annotation provided.
+  std::vector<TextAnnotationPtr> annotations;
+  UseFakeServiceConnection(annotations);
+
+  // Add word to the dictionary.
+  spell_checker()->AddWordToDictionary(kWord);
+
+  // Word selected.
+  std::unique_ptr<QuickAnswersRequest> quick_answers_request =
+      std::make_unique<QuickAnswersRequest>();
+  quick_answers_request->selected_text = kWord;
+
+  intent_generator_->GenerateIntent(*quick_answers_request);
+  task_environment_.RunUntilIdle();
+
+  // Should not generate dictionary intent if the word contains digits even if
+  // it is in the dictionary.
+  EXPECT_EQ(IntentType::kUnknown, intent_info_.intent_type);
+  EXPECT_EQ(kWord, intent_info_.intent_text);
+}
+
 TEST_F(IntentGeneratorTest,
        ShouldFallbackToAnnotationsForWordNotInDictionaryNoAnnotation) {
   base::test::ScopedFeatureList feature_list;
diff --git a/components/app_restore/app_restore_utils.cc b/components/app_restore/app_restore_utils.cc
index 070be6a..2a38ed2 100644
--- a/components/app_restore/app_restore_utils.cc
+++ b/components/app_restore/app_restore_utils.cc
@@ -5,7 +5,6 @@
 #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/app_restore_info.h"
 #include "components/app_restore/desk_template_read_handler.h"
@@ -27,10 +26,8 @@
 // Always use the full restore ARC data if ARC apps for desks templates is not
 // enabled.
 bool ShouldUseFullRestoreArcData() {
-  return ash::features::AreDesksTemplatesEnabled()
-             ? full_restore::FullRestoreReadHandler::GetInstance()
-                   ->IsFullRestoreRunning()
-             : true;
+  return full_restore::FullRestoreReadHandler::GetInstance()
+      ->IsFullRestoreRunning();
 }
 
 }  // namespace
diff --git a/components/app_restore/desk_template_read_handler.cc b/components/app_restore/desk_template_read_handler.cc
index 1b7815d..ae0ff6e 100644
--- a/components/app_restore/desk_template_read_handler.cc
+++ b/components/app_restore/desk_template_read_handler.cc
@@ -5,7 +5,6 @@
 #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"
@@ -63,9 +62,6 @@
 
   restore_data_[launch_id] = std::move(restore_data);
 
-  if (!ash::features::AreDesksTemplatesEnabled())
-    return;
-
   // Set up mapping from restore window IDs to launch ID. Create an ARC read
   // handler and add restore data to it if we have at least one ARC app.
   for (const auto& [app_id, launch_list] : rd->app_id_to_launch_list()) {
diff --git a/components/app_restore/full_restore_read_and_save_unittest.cc b/components/app_restore/full_restore_read_and_save_unittest.cc
index 92f6165..132be9d9 100644
--- a/components/app_restore/full_restore_read_and_save_unittest.cc
+++ b/components/app_restore/full_restore_read_and_save_unittest.cc
@@ -812,6 +812,11 @@
   ASSERT_TRUE(restore_data);
 
   FullRestoreReadHandler* read_handler = FullRestoreReadHandler::GetInstance();
+  // The following is necessary for making `ShouldUseFullRestoreArcData()` and
+  // `read_handler->IsFullRestoreRunning()` return true;
+  read_handler->SetActiveProfilePath(GetPath());
+  read_handler->SetStartTimeForProfile(GetPath());
+
   FullRestoreReadHandlerTestApi read_test_api(read_handler);
   ASSERT_TRUE(read_test_api.GetArcReadHander());
   EXPECT_EQ(1u, read_test_api.GetArcWindowIdMap().size());
diff --git a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerExternalUma.java b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerExternalUma.java
index 35d3545..55aa3c09 100644
--- a/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerExternalUma.java
+++ b/components/background_task_scheduler/android/java/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerExternalUma.java
@@ -143,4 +143,71 @@
         // Returning a value that is not expected to ever be reported.
         return BACKGROUND_TASK_NOT_FOUND;
     }
+
+    /**
+     * Keep this in sync with TaskType variant in
+     * //tools/metrics/histograms/metadata/android/histograms.xml.
+     * @return The histogram pattern to be used for the given {@code taskId}.
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
+    public String getHistogramPatternForTaskId(int taskId) {
+        switch (taskId) {
+            case TaskIds.TEST:
+                return "Test";
+            case TaskIds.OMAHA_JOB_ID:
+                return "Omaha";
+            case TaskIds.GCM_BACKGROUND_TASK_JOB_ID:
+                return "Gcm";
+            case TaskIds.NOTIFICATION_SERVICE_JOB_ID:
+                return "NotificationService";
+            case TaskIds.WEBVIEW_MINIDUMP_UPLOADING_JOB_ID:
+                return "WebviewMinidumpUploading";
+            case TaskIds.CHROME_MINIDUMP_UPLOADING_JOB_ID:
+                return "ChromeMinidumpUploading";
+            case TaskIds.OFFLINE_PAGES_BACKGROUND_JOB_ID:
+                return "OfflinePages";
+            case TaskIds.OFFLINE_PAGES_PREFETCH_JOB_ID:
+                return "OfflinePagesPrefetch";
+            case TaskIds.DOWNLOAD_SERVICE_JOB_ID:
+                return "DownloadService";
+            case TaskIds.DOWNLOAD_CLEANUP_JOB_ID:
+                return "DownloadCleanup";
+            case TaskIds.DOWNLOAD_AUTO_RESUMPTION_JOB_ID:
+                return "DownloadAutoResumption";
+            case TaskIds.DOWNLOAD_LATER_JOB_ID:
+                return "DownloadLater";
+            case TaskIds.WEBVIEW_VARIATIONS_SEED_FETCH_JOB_ID:
+                return "WebviewVariationsSeedFetch";
+            case TaskIds.OFFLINE_PAGES_PREFETCH_NOTIFICATION_JOB_ID:
+                return "OfflinePagesPrefetchNotification";
+            case TaskIds.WEBAPK_UPDATE_JOB_ID:
+                return "WebApkUpdate";
+            case TaskIds.DEPRECATED_DOWNLOAD_RESUMPTION_JOB_ID:
+                return "DeprecatedDownloadResumption";
+            case TaskIds.FEED_REFRESH_JOB_ID:
+                return "FeedRefresh";
+            case TaskIds.COMPONENT_UPDATE_JOB_ID:
+                return "ComponentUpdate";
+            case TaskIds.DEPRECATED_EXPLORE_SITES_REFRESH_JOB_ID:
+                return "DeprecatedExploreSitesRefresh";
+            case TaskIds.EXPLORE_SITES_REFRESH_JOB_ID:
+                return "ExploreSitesRefresh";
+            case TaskIds.BACKGROUND_SYNC_ONE_SHOT_JOB_ID:
+                return "BackgroundSyncOneShot";
+            case TaskIds.NOTIFICATION_SCHEDULER_JOB_ID:
+                return "NotificationScheduler";
+            case TaskIds.NOTIFICATION_TRIGGER_JOB_ID:
+                return "NotificationTrigger";
+            case TaskIds.PERIODIC_BACKGROUND_SYNC_CHROME_WAKEUP_TASK_JOB_ID:
+                return "PeriodicBackgroundSyncChromeWakeup";
+            case TaskIds.QUERY_TILE_JOB_ID:
+                return "QueryTile";
+            case TaskIds.FEEDV2_REFRESH_JOB_ID:
+                return "FeedV2Refresh";
+            case TaskIds.WEBVIEW_COMPONENT_UPDATE_JOB_ID:
+                return "WebviewComponentUpdate";
+        }
+        assert false;
+        return null;
+    }
 }
diff --git a/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskBroadcastReceiver.java b/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskBroadcastReceiver.java
index a6d0185..7aa42354 100644
--- a/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskBroadcastReceiver.java
+++ b/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskBroadcastReceiver.java
@@ -14,6 +14,7 @@
 import android.os.BatteryManager;
 import android.os.Build;
 import android.os.PowerManager;
+import android.os.SystemClock;
 import android.text.format.DateUtils;
 
 import androidx.annotation.Nullable;
@@ -49,6 +50,7 @@
         private final PowerManager.WakeLock mWakeLock;
         private final TaskParameters mTaskParams;
         private final BackgroundTask mBackgroundTask;
+        private final long mTaskStartTimeMs;
 
         private boolean mHasExecuted;
 
@@ -58,6 +60,7 @@
             mWakeLock = wakeLock;
             mTaskParams = taskParams;
             mBackgroundTask = backgroundTask;
+            mTaskStartTimeMs = SystemClock.uptimeMillis();
         }
 
         public void execute() {
@@ -99,7 +102,8 @@
                 BackgroundTaskSchedulerUma.getInstance().reportTaskRescheduled();
                 mBackgroundTask.reschedule(mContext);
             }
-            // TODO(crbug.com/970160): Add UMA to record how long the tasks need to complete.
+            BackgroundTaskSchedulerUma.getInstance().reportTaskFinished(
+                    mTaskParams.getTaskId(), SystemClock.uptimeMillis() - mTaskStartTimeMs);
         }
     }
 
diff --git a/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskJobService.java b/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskJobService.java
index 89df437..925a293d 100644
--- a/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskJobService.java
+++ b/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskJobService.java
@@ -6,6 +6,7 @@
 
 import android.app.job.JobParameters;
 import android.app.job.JobService;
+import android.os.SystemClock;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -34,12 +35,17 @@
         private final BackgroundTaskJobService mJobService;
         private final BackgroundTask mBackgroundTask;
         private final JobParameters mParams;
+        private final long mTaskStartTimeMs;
 
         TaskFinishedCallbackJobService(BackgroundTaskJobService jobService,
                 BackgroundTask backgroundTask, JobParameters params) {
             mJobService = jobService;
             mBackgroundTask = backgroundTask;
             mParams = params;
+
+            // We are using uptimeMillis here to record the exact amount of time needed for the task
+            // to run that excludes the time spent during deep sleep.
+            mTaskStartTimeMs = SystemClock.uptimeMillis();
         }
 
         @Override
@@ -60,6 +66,8 @@
 
                     mJobService.mCurrentTasks.remove(mParams.getJobId());
                     mJobService.jobFinished(mParams, needsReschedule);
+                    BackgroundTaskSchedulerUma.getInstance().reportTaskFinished(
+                            mParams.getJobId(), SystemClock.uptimeMillis() - mTaskStartTimeMs);
                 }
             });
         }
diff --git a/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskSchedulerUma.java b/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskSchedulerUma.java
index 039544f8..9841a70 100644
--- a/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskSchedulerUma.java
+++ b/components/background_task_scheduler/internal/android/java/src/org/chromium/components/background_task_scheduler/internal/BackgroundTaskSchedulerUma.java
@@ -5,6 +5,7 @@
 package org.chromium.components.background_task_scheduler.internal;
 
 import android.content.SharedPreferences;
+import android.text.format.DateUtils;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -155,6 +156,13 @@
         cacheEvent("Android.BackgroundTaskScheduler.TaskStopped", toUmaEnumValueFromTaskId(taskId));
     }
 
+    /** Reports metrics for finishing a task. */
+    public void reportTaskFinished(int taskId, long taskDurationMs) {
+        RecordHistogram.recordCustomTimesHistogram("Android.BackgroundTaskScheduler.TaskFinished."
+                        + getHistogramPatternForTaskId(taskId),
+                taskDurationMs, 1, DateUtils.DAY_IN_MILLIS, 50);
+    }
+
     /** Reports metrics for rescheduling a task. */
     public void reportTaskRescheduled() {
         cacheEvent("Android.BackgroundTaskScheduler.TaskRescheduled", 0);
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/gesture/BackPressHandler.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/gesture/BackPressHandler.java
index c5d0f30..09cb5b23e 100644
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/gesture/BackPressHandler.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/gesture/BackPressHandler.java
@@ -20,16 +20,17 @@
  */
 public interface BackPressHandler {
     // The smaller the value is, the higher the priority is.
-    @IntDef({Type.TEXT_BUBBLE, Type.AR_DELEGATE, Type.LAYOUT_MANAGER, Type.MANUAL_FILLING,
-            Type.TAB_MODAL_HANDLER, Type.FULLSCREEN})
+    @IntDef({Type.TEXT_BUBBLE, Type.VR_DELEGATE, Type.AR_DELEGATE, Type.LAYOUT_MANAGER,
+            Type.MANUAL_FILLING, Type.TAB_MODAL_HANDLER, Type.FULLSCREEN})
     @Retention(RetentionPolicy.SOURCE)
     @interface Type {
         int TEXT_BUBBLE = 0;
-        int AR_DELEGATE = 1;
-        int LAYOUT_MANAGER = 2;
-        int MANUAL_FILLING = 3;
-        int FULLSCREEN = 4;
-        int TAB_MODAL_HANDLER = 5;
+        int VR_DELEGATE = 1;
+        int AR_DELEGATE = 2;
+        int LAYOUT_MANAGER = 3;
+        int MANUAL_FILLING = 4;
+        int FULLSCREEN = 5;
+        int TAB_MODAL_HANDLER = 6;
         int NUM_TYPES = TAB_MODAL_HANDLER + 1;
     }
 
diff --git a/components/cast_streaming/browser/demuxer_stream_data_provider.h b/components/cast_streaming/browser/demuxer_stream_data_provider.h
index 90755f698..7d024cb 100644
--- a/components/cast_streaming/browser/demuxer_stream_data_provider.h
+++ b/components/cast_streaming/browser/demuxer_stream_data_provider.h
@@ -7,7 +7,7 @@
 
 #include "base/callback_forward.h"
 #include "base/sequence_checker.h"
-#include "components/cast_streaming/public/mojom/cast_streaming_session.mojom.h"
+#include "components/cast_streaming/public/mojom/demuxer_connector.mojom.h"
 #include "mojo/public/cpp/bindings/message.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
diff --git a/components/cast_streaming/browser/public/receiver_session.h b/components/cast_streaming/browser/public/receiver_session.h
index 4b03a3bd7..77b0b93 100644
--- a/components/cast_streaming/browser/public/receiver_session.h
+++ b/components/cast_streaming/browser/public/receiver_session.h
@@ -9,7 +9,7 @@
 
 #include "base/callback.h"
 #include "base/time/time.h"
-#include "components/cast_streaming/public/mojom/cast_streaming_session.mojom.h"
+#include "components/cast_streaming/public/mojom/demuxer_connector.mojom.h"
 #include "components/cast_streaming/public/mojom/renderer_controller.mojom.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
 #include "third_party/openscreen/src/cast/streaming/receiver_session.h"
@@ -26,7 +26,7 @@
 namespace cast_streaming {
 
 // This interface handles a single Cast Streaming Receiver Session over a given
-// |message_port| and with a given |cast_streaming_receiver|. On destruction,
+// |message_port| and with a given |demuxer_connector|. On destruction,
 // the Cast Streaming Receiver Session will be terminated if it was ever
 // started.
 // TODO(1220176): Forward declare ReceiverSession::Preferences instead of
@@ -92,15 +92,13 @@
   // |PlaybackCommandForwardingRenderer| is being used, the below overload is
   // recommended instead.
   virtual void StartStreamingAsync(
-      mojo::AssociatedRemote<mojom::CastStreamingReceiver>
-          cast_streaming_receiver) = 0;
+      mojo::AssociatedRemote<mojom::DemuxerConnector> demuxer_connector) = 0;
 
   // As above, but also sets the |renderer_controller| to be used to control a
   // renderer-process |PlaybackCommandForwardingRenderer|. This control may then
   // be done through the RenderControls returned by GetRendererControls() below.
   virtual void StartStreamingAsync(
-      mojo::AssociatedRemote<mojom::CastStreamingReceiver>
-          cast_streaming_receiver,
+      mojo::AssociatedRemote<mojom::DemuxerConnector> demuxer_connector,
       mojo::AssociatedRemote<mojom::RendererController>
           renderer_controller) = 0;
 
diff --git a/components/cast_streaming/browser/receiver_session_impl.cc b/components/cast_streaming/browser/receiver_session_impl.cc
index 2f840263..05a40204 100644
--- a/components/cast_streaming/browser/receiver_session_impl.cc
+++ b/components/cast_streaming/browser/receiver_session_impl.cc
@@ -36,22 +36,20 @@
 ReceiverSessionImpl::~ReceiverSessionImpl() = default;
 
 void ReceiverSessionImpl::StartStreamingAsync(
-    mojo::AssociatedRemote<mojom::CastStreamingReceiver>
-        cast_streaming_receiver) {
+    mojo::AssociatedRemote<mojom::DemuxerConnector> demuxer_connector) {
   DCHECK(HasNetworkContextGetter());
 
   DVLOG(1) << __func__;
-  cast_streaming_receiver_ = std::move(cast_streaming_receiver);
+  demuxer_connector_ = std::move(demuxer_connector);
 
-  cast_streaming_receiver_->EnableReceiver(base::BindOnce(
+  demuxer_connector_->EnableReceiver(base::BindOnce(
       &ReceiverSessionImpl::OnReceiverEnabled, weak_factory_.GetWeakPtr()));
-  cast_streaming_receiver_.set_disconnect_handler(base::BindOnce(
+  demuxer_connector_.set_disconnect_handler(base::BindOnce(
       &ReceiverSessionImpl::OnMojoDisconnect, weak_factory_.GetWeakPtr()));
 }
 
 void ReceiverSessionImpl::StartStreamingAsync(
-    mojo::AssociatedRemote<mojom::CastStreamingReceiver>
-        cast_streaming_receiver,
+    mojo::AssociatedRemote<mojom::DemuxerConnector> demuxer_connector,
     mojo::AssociatedRemote<mojom::RendererController> renderer_controller) {
   DCHECK(!renderer_control_config_);
   external_renderer_controls_ =
@@ -60,7 +58,7 @@
   renderer_control_config_.emplace(std::move(renderer_controller),
                                    external_renderer_controls_->Bind());
 
-  StartStreamingAsync(std::move(cast_streaming_receiver));
+  StartStreamingAsync(std::move(demuxer_connector));
 }
 
 ReceiverSession::RendererController*
@@ -131,8 +129,8 @@
             std::move(std::move(video_pipe_consumer.value()))));
   }
 
-  cast_streaming_receiver_->OnStreamsInitialized(std::move(audio_info),
-                                                 std::move(video_info));
+  demuxer_connector_->OnStreamsInitialized(std::move(audio_info),
+                                           std::move(video_info));
 
   InformClientOfConfigChange();
 }
@@ -216,7 +214,7 @@
   DVLOG(1) << __func__;
 
   // Tear down the Mojo connection.
-  cast_streaming_receiver_.reset();
+  demuxer_connector_.reset();
 
   // Tear down all remaining Mojo objects if needed. This is necessary if the
   // Cast Streaming Session ending was initiated by the receiver component.
diff --git a/components/cast_streaming/browser/receiver_session_impl.h b/components/cast_streaming/browser/receiver_session_impl.h
index 4676205e..b982e486 100644
--- a/components/cast_streaming/browser/receiver_session_impl.h
+++ b/components/cast_streaming/browser/receiver_session_impl.h
@@ -10,7 +10,7 @@
 #include "components/cast_streaming/browser/cast_streaming_session.h"
 #include "components/cast_streaming/browser/demuxer_stream_data_provider.h"
 #include "components/cast_streaming/browser/public/receiver_session.h"
-#include "components/cast_streaming/public/mojom/cast_streaming_session.mojom.h"
+#include "components/cast_streaming/public/mojom/demuxer_connector.mojom.h"
 #include "components/cast_streaming/public/mojom/renderer_controller.mojom.h"
 #include "media/mojo/mojom/media_types.mojom.h"
 #include "mojo/public/cpp/bindings/associated_remote.h"
@@ -37,12 +37,12 @@
   ReceiverSessionImpl& operator=(const ReceiverSessionImpl&) = delete;
 
   // ReceiverSession implementation.
-  void StartStreamingAsync(mojo::AssociatedRemote<mojom::CastStreamingReceiver>
-                               cast_streaming_receiver) override;
-  void StartStreamingAsync(mojo::AssociatedRemote<mojom::CastStreamingReceiver>
-                               cast_streaming_receiver,
-                           mojo::AssociatedRemote<mojom::RendererController>
-                               renderer_controller) override;
+  void StartStreamingAsync(mojo::AssociatedRemote<mojom::DemuxerConnector>
+                               demuxer_connector) override;
+  void StartStreamingAsync(
+      mojo::AssociatedRemote<mojom::DemuxerConnector> demuxer_connector,
+      mojo::AssociatedRemote<mojom::RendererController> renderer_controller)
+      override;
   RendererController* GetRendererControls() override;
 
  private:
@@ -66,10 +66,10 @@
     mojo::Remote<media::mojom::Renderer> renderer_controls_;
   };
 
-  // Handler for |cast_streaming_receiver_| disconnect.
+  // Handler for |demuxer_connector_| disconnect.
   void OnMojoDisconnect();
 
-  // Callback for mojom::CastStreamingReceiver::EnableReceiver()
+  // Callback for mojom::DemuxerConnector::EnableReceiver()
   void OnReceiverEnabled();
 
   // Informs the client of updated configs.
@@ -95,7 +95,7 @@
   MessagePortProvider message_port_provider_;
   std::unique_ptr<ReceiverSession::AVConstraints> av_constraints_;
 
-  mojo::AssociatedRemote<mojom::CastStreamingReceiver> cast_streaming_receiver_;
+  mojo::AssociatedRemote<mojom::DemuxerConnector> demuxer_connector_;
   cast_streaming::CastStreamingSession cast_streaming_session_;
 
   std::unique_ptr<AudioDemuxerStreamDataProvider>
diff --git a/components/cast_streaming/public/mojom/BUILD.gn b/components/cast_streaming/public/mojom/BUILD.gn
index 3aa2b09..44c4ad3 100644
--- a/components/cast_streaming/public/mojom/BUILD.gn
+++ b/components/cast_streaming/public/mojom/BUILD.gn
@@ -6,7 +6,7 @@
 
 mojom("mojom") {
   sources = [
-    "cast_streaming_session.mojom",
+    "demuxer_connector.mojom",
     "renderer_controller.mojom",
   ]
   public_deps = [
diff --git a/components/cast_streaming/public/mojom/cast_streaming_session.mojom b/components/cast_streaming/public/mojom/demuxer_connector.mojom
similarity index 97%
rename from components/cast_streaming/public/mojom/cast_streaming_session.mojom
rename to components/cast_streaming/public/mojom/demuxer_connector.mojom
index 1de3d69..d7c8ae0a 100644
--- a/components/cast_streaming/public/mojom/cast_streaming_session.mojom
+++ b/components/cast_streaming/public/mojom/demuxer_connector.mojom
@@ -81,7 +81,7 @@
 // Implemented by the renderer, used to start the Cast Streaming Session.
 // Closure of the Mojo channel will trigger the end of the Cast Streaming
 // Session.
-interface CastStreamingReceiver {
+interface DemuxerConnector {
   // Used for synchronization between the browser and the renderer. The browser
   // should invoke this after binding the interface, and wait for the reply
   // callback to know when the renderer is ready to receive and render frames.
@@ -89,7 +89,7 @@
 
   // Called when the streams have been successfully initialized. At least one of
   // |audio_buffer_requester| or |video_buffer_requester| must be set. This will
-  // only be called once per the lifetime of CastStreamingReceiver.
+  // only be called once per the lifetime of DemuxerConnector.
   OnStreamsInitialized(
       AudioStreamInitializationInfo? audio_buffer_requester,
       VideoStreamInitializationInfo? video_buffer_requester);
diff --git a/components/cast_streaming/renderer/BUILD.gn b/components/cast_streaming/renderer/BUILD.gn
index 63aff514..c381c0c5 100644
--- a/components/cast_streaming/renderer/BUILD.gn
+++ b/components/cast_streaming/renderer/BUILD.gn
@@ -58,8 +58,8 @@
   sources = [
     "cast_streaming_demuxer.cc",
     "cast_streaming_demuxer.h",
-    "cast_streaming_receiver.cc",
-    "cast_streaming_receiver.h",
+    "demuxer_connector.cc",
+    "demuxer_connector.h",
     "renderer_controller_proxy.cc",
     "renderer_controller_proxy.h",
     "resource_provider_impl.cc",
diff --git a/components/cast_streaming/renderer/cast_streaming_demuxer.cc b/components/cast_streaming/renderer/cast_streaming_demuxer.cc
index 44f39b9..50e2610d 100644
--- a/components/cast_streaming/renderer/cast_streaming_demuxer.cc
+++ b/components/cast_streaming/renderer/cast_streaming_demuxer.cc
@@ -10,8 +10,8 @@
 #include "base/bind.h"
 #include "base/sequence_checker.h"
 #include "base/task/single_thread_task_runner.h"
-#include "components/cast_streaming/renderer/cast_streaming_receiver.h"
 #include "components/cast_streaming/renderer/decoder_buffer_reader.h"
+#include "components/cast_streaming/renderer/demuxer_connector.h"
 #include "media/base/audio_decoder_config.h"
 #include "media/base/decoder_buffer.h"
 #include "media/base/timestamp_constants.h"
@@ -284,14 +284,14 @@
 };
 
 CastStreamingDemuxer::CastStreamingDemuxer(
-    CastStreamingReceiver* receiver,
+    DemuxerConnector* demuxer_connector,
     scoped_refptr<base::SingleThreadTaskRunner> media_task_runner)
     : media_task_runner_(std::move(media_task_runner)),
       original_task_runner_(base::SequencedTaskRunnerHandle::Get()),
-      receiver_(receiver),
+      demuxer_connector_(demuxer_connector),
       weak_factory_(this) {
   DVLOG(1) << __func__;
-  DCHECK(receiver_);
+  DCHECK(demuxer_connector_);
 }
 
 CastStreamingDemuxer::~CastStreamingDemuxer() {
@@ -299,8 +299,8 @@
 
   if (was_initialization_successful_) {
     original_task_runner_->PostTask(
-        FROM_HERE, base::BindOnce(&CastStreamingReceiver::OnDemuxerDestroyed,
-                                  base::Unretained(receiver_)));
+        FROM_HERE, base::BindOnce(&DemuxerConnector::OnDemuxerDestroyed,
+                                  base::Unretained(demuxer_connector_)));
   }
 }
 
@@ -371,9 +371,9 @@
   initialized_cb_ = std::move(status_cb);
 
   original_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(&CastStreamingReceiver::SetDemuxer,
-                     base::Unretained(receiver_), base::Unretained(this)));
+      FROM_HERE, base::BindOnce(&DemuxerConnector::SetDemuxer,
+                                base::Unretained(demuxer_connector_),
+                                base::Unretained(this)));
 }
 
 void CastStreamingDemuxer::AbortPendingReads() {
diff --git a/components/cast_streaming/renderer/cast_streaming_demuxer.h b/components/cast_streaming/renderer/cast_streaming_demuxer.h
index ae6ab59..8a7585ba 100644
--- a/components/cast_streaming/renderer/cast_streaming_demuxer.h
+++ b/components/cast_streaming/renderer/cast_streaming_demuxer.h
@@ -5,7 +5,7 @@
 #ifndef COMPONENTS_CAST_STREAMING_RENDERER_CAST_STREAMING_DEMUXER_H_
 #define COMPONENTS_CAST_STREAMING_RENDERER_CAST_STREAMING_DEMUXER_H_
 
-#include "components/cast_streaming/public/mojom/cast_streaming_session.mojom.h"
+#include "components/cast_streaming/public/mojom/demuxer_connector.mojom.h"
 #include "media/base/demuxer.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -17,9 +17,9 @@
 
 namespace cast_streaming {
 
-class CastStreamingReceiver;
 class CastStreamingAudioDemuxerStream;
 class CastStreamingVideoDemuxerStream;
+class DemuxerConnector;
 
 // media::Demuxer implementation for a Cast Streaming Receiver.
 // This object is instantiated on the main thread, whose task runner is stored
@@ -27,11 +27,11 @@
 // on the main thread. Every other method is called on the media thread, whose
 // task runner is |media_task_runner_|.
 // TODO(crbug.com/1082821): Simplify the CastStreamingDemuxer initialization
-// sequence when the CastStreamingReceiver Component has been implemented.
+// sequence when the DemuxerConnector Component has been implemented.
 class CastStreamingDemuxer final : public media::Demuxer {
  public:
   CastStreamingDemuxer(
-      CastStreamingReceiver* receiver,
+      DemuxerConnector* demuxer_connector,
       scoped_refptr<base::SingleThreadTaskRunner> media_task_runner);
   ~CastStreamingDemuxer() override;
 
@@ -81,7 +81,7 @@
   // Set to true if the Demuxer was successfully initialized.
   bool was_initialization_successful_ = false;
   media::PipelineStatusCallback initialized_cb_;
-  CastStreamingReceiver* const receiver_;
+  DemuxerConnector* const demuxer_connector_;
 
   base::WeakPtrFactory<CastStreamingDemuxer> weak_factory_;
 };
diff --git a/components/cast_streaming/renderer/cast_streaming_receiver.cc b/components/cast_streaming/renderer/demuxer_connector.cc
similarity index 72%
rename from components/cast_streaming/renderer/cast_streaming_receiver.cc
rename to components/cast_streaming/renderer/demuxer_connector.cc
index 52de587..bc37f19 100644
--- a/components/cast_streaming/renderer/cast_streaming_receiver.cc
+++ b/components/cast_streaming/renderer/demuxer_connector.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/cast_streaming/renderer/cast_streaming_receiver.h"
+#include "components/cast_streaming/renderer/demuxer_connector.h"
 
 #include "components/cast_streaming/renderer/cast_streaming_demuxer.h"
 #include "content/public/renderer/render_frame.h"
@@ -10,8 +10,7 @@
 
 namespace cast_streaming {
 
-CastStreamingReceiver::CastStreamingReceiver(
-    content::RenderFrame* render_frame) {
+DemuxerConnector::DemuxerConnector(content::RenderFrame* render_frame) {
   DVLOG(1) << __func__;
   DCHECK(render_frame);
 
@@ -19,15 +18,15 @@
   // AssociatedInterfaceRegistry, owned by |render_frame| will be torn-down at
   // the same time as |this|.
   render_frame->GetAssociatedInterfaceRegistry()->AddInterface(
-      base::BindRepeating(&CastStreamingReceiver::BindToReceiver,
+      base::BindRepeating(&DemuxerConnector::BindToReceiver,
                           base::Unretained(this)));
 }
 
-CastStreamingReceiver::~CastStreamingReceiver() {
+DemuxerConnector::~DemuxerConnector() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 }
 
-void CastStreamingReceiver::SetDemuxer(CastStreamingDemuxer* demuxer) {
+void DemuxerConnector::SetDemuxer(CastStreamingDemuxer* demuxer) {
   DVLOG(1) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(demuxer);
@@ -53,38 +52,38 @@
   }
 }
 
-void CastStreamingReceiver::OnDemuxerDestroyed() {
+void DemuxerConnector::OnDemuxerDestroyed() {
   DVLOG(1) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(demuxer_);
 
   demuxer_ = nullptr;
   is_demuxer_initialized_ = false;
-  cast_streaming_receiver_receiver_.reset();
+  demuxer_connector_receiver_.reset();
 }
 
-void CastStreamingReceiver::BindToReceiver(
-    mojo::PendingAssociatedReceiver<mojom::CastStreamingReceiver> receiver) {
+void DemuxerConnector::BindToReceiver(
+    mojo::PendingAssociatedReceiver<mojom::DemuxerConnector> receiver) {
   DVLOG(1) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  DCHECK(!cast_streaming_receiver_receiver_.is_bound());
+  DCHECK(!demuxer_connector_receiver_.is_bound());
 
-  cast_streaming_receiver_receiver_.Bind(std::move(receiver));
+  demuxer_connector_receiver_.Bind(std::move(receiver));
 
   // Mojo service disconnection means the Cast Streaming Session ended or the
   // Cast Streaming Sender disconnected.
-  cast_streaming_receiver_receiver_.set_disconnect_handler(base::BindOnce(
-      &CastStreamingReceiver::OnReceiverDisconnected, base::Unretained(this)));
+  demuxer_connector_receiver_.set_disconnect_handler(base::BindOnce(
+      &DemuxerConnector::OnReceiverDisconnected, base::Unretained(this)));
 }
 
-bool CastStreamingReceiver::IsBound() const {
+bool DemuxerConnector::IsBound() const {
   DVLOG(2) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  return cast_streaming_receiver_receiver_.is_bound();
+  return demuxer_connector_receiver_.is_bound();
 }
 
-void CastStreamingReceiver::MaybeCallEnableReceiverCallback() {
+void DemuxerConnector::MaybeCallEnableReceiverCallback() {
   DVLOG(2) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -92,11 +91,11 @@
     std::move(enable_receiver_callback_).Run();
 }
 
-void CastStreamingReceiver::OnReceiverDisconnected() {
+void DemuxerConnector::OnReceiverDisconnected() {
   DVLOG(1) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  cast_streaming_receiver_receiver_.reset();
+  demuxer_connector_receiver_.reset();
   enable_receiver_callback_.Reset();
 
   if (demuxer_ && !is_demuxer_initialized_) {
@@ -105,7 +104,7 @@
   }
 }
 
-void CastStreamingReceiver::EnableReceiver(EnableReceiverCallback callback) {
+void DemuxerConnector::EnableReceiver(EnableReceiverCallback callback) {
   DVLOG(1) << __func__;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!enable_receiver_callback_);
@@ -115,7 +114,7 @@
   MaybeCallEnableReceiverCallback();
 }
 
-void CastStreamingReceiver::OnStreamsInitialized(
+void DemuxerConnector::OnStreamsInitialized(
     mojom::AudioStreamInitializationInfoPtr audio_stream_info,
     mojom::VideoStreamInitializationInfoPtr video_stream_info) {
   DVLOG(1) << __func__;
diff --git a/components/cast_streaming/renderer/cast_streaming_receiver.h b/components/cast_streaming/renderer/demuxer_connector.h
similarity index 63%
rename from components/cast_streaming/renderer/cast_streaming_receiver.h
rename to components/cast_streaming/renderer/demuxer_connector.h
index fd46397e..b7d903e 100644
--- a/components/cast_streaming/renderer/cast_streaming_receiver.h
+++ b/components/cast_streaming/renderer/demuxer_connector.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_CAST_STREAMING_RENDERER_CAST_STREAMING_RECEIVER_H_
-#define COMPONENTS_CAST_STREAMING_RENDERER_CAST_STREAMING_RECEIVER_H_
+#ifndef COMPONENTS_CAST_STREAMING_RENDERER_DEMUXER_CONNECTOR_H_
+#define COMPONENTS_CAST_STREAMING_RENDERER_DEMUXER_CONNECTOR_H_
 
 #include "base/callback.h"
 #include "base/sequence_checker.h"
-#include "components/cast_streaming/public/mojom/cast_streaming_session.mojom.h"
+#include "components/cast_streaming/public/mojom/demuxer_connector.mojom.h"
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 
@@ -21,18 +21,18 @@
 
 // Handles initiating the streaming session between the browser-process sender
 // and renderer-process receiver of the Cast Streaming Session. Specifically,
-// this class manages the CastStreamingReceiver's lifetime in the renderer
+// this class manages the DemuxerConnector's lifetime in the renderer
 // process. The lifetime of this object should match that of |render_frame| with
 // which it is associated, and is guaranteed to outlive the CastStreamingDemuxer
 // that uses it, as the RenderFrame destruction will have triggered its
 // destruction first.
-class CastStreamingReceiver final : public mojom::CastStreamingReceiver {
+class DemuxerConnector final : public mojom::DemuxerConnector {
  public:
-  explicit CastStreamingReceiver(content::RenderFrame* render_frame);
-  ~CastStreamingReceiver() override;
+  explicit DemuxerConnector(content::RenderFrame* render_frame);
+  ~DemuxerConnector() override;
 
-  CastStreamingReceiver(const CastStreamingReceiver&) = delete;
-  CastStreamingReceiver& operator=(const CastStreamingReceiver&) = delete;
+  DemuxerConnector(const DemuxerConnector&) = delete;
+  DemuxerConnector& operator=(const DemuxerConnector&) = delete;
 
   void SetDemuxer(CastStreamingDemuxer* demuxer);
   void OnDemuxerDestroyed();
@@ -42,20 +42,20 @@
 
  private:
   void BindToReceiver(
-      mojo::PendingAssociatedReceiver<mojom::CastStreamingReceiver> receiver);
+      mojo::PendingAssociatedReceiver<mojom::DemuxerConnector> connector);
 
   void MaybeCallEnableReceiverCallback();
 
   void OnReceiverDisconnected();
 
-  // mojom::CastStreamingReceiver implementation.
+  // mojom::DemuxerConnector implementation.
   void EnableReceiver(EnableReceiverCallback callback) override;
   void OnStreamsInitialized(
       mojom::AudioStreamInitializationInfoPtr audio_stream_info,
       mojom::VideoStreamInitializationInfoPtr video_stream_info) override;
 
-  mojo::AssociatedReceiver<mojom::CastStreamingReceiver>
-      cast_streaming_receiver_receiver_{this};
+  mojo::AssociatedReceiver<mojom::DemuxerConnector> demuxer_connector_receiver_{
+      this};
 
   EnableReceiverCallback enable_receiver_callback_;
   CastStreamingDemuxer* demuxer_ = nullptr;
@@ -66,4 +66,4 @@
 
 }  // namespace cast_streaming
 
-#endif  // COMPONENTS_CAST_STREAMING_RENDERER_CAST_STREAMING_RECEIVER_H_
+#endif  // COMPONENTS_CAST_STREAMING_RENDERER_DEMUXER_CONNECTOR_H_
diff --git a/components/cast_streaming/renderer/resource_provider_impl.cc b/components/cast_streaming/renderer/resource_provider_impl.cc
index 7c5ea463..2e619c8 100644
--- a/components/cast_streaming/renderer/resource_provider_impl.cc
+++ b/components/cast_streaming/renderer/resource_provider_impl.cc
@@ -61,14 +61,14 @@
 
     // Do not create a CastStreamingDemuxer if the Cast Streaming MessagePort
     // was not set in the browser process. This will manifest as an unbound
-    // CastStreamingReceiver object in the renderer process.
+    // DemuxerProvider object in the renderer process.
     // TODO(crbug.com/1082821): Simplify the instantiation conditions for the
     // CastStreamingDemuxer.
     DCHECK(iter->second);
-    CastStreamingReceiver& receiver = iter->second->cast_streaming_receiver();
-    if (receiver.IsBound()) {
+    DemuxerConnector& demuxer_connector = iter->second->demuxer_connector();
+    if (demuxer_connector.IsBound()) {
       return std::make_unique<CastStreamingDemuxer>(
-          &receiver, std::move(media_task_runner));
+          &demuxer_connector, std::move(media_task_runner));
     }
   }
 
@@ -90,7 +90,7 @@
     content::RenderFrame* render_frame,
     EndOfLifeCB end_of_life_callback)
     : content::RenderFrameObserver(render_frame),
-      cast_streaming_receiver_(render_frame),
+      demuxer_connector_(render_frame),
       end_of_life_cb_(std::move(end_of_life_callback)) {
   DCHECK(render_frame);
   DCHECK(end_of_life_cb_);
diff --git a/components/cast_streaming/renderer/resource_provider_impl.h b/components/cast_streaming/renderer/resource_provider_impl.h
index 78e7e62..cc7846f 100644
--- a/components/cast_streaming/renderer/resource_provider_impl.h
+++ b/components/cast_streaming/renderer/resource_provider_impl.h
@@ -11,7 +11,7 @@
 #include "base/callback.h"
 #include "base/memory/scoped_refptr.h"
 #include "components/cast_streaming/public/mojom/renderer_controller.mojom.h"
-#include "components/cast_streaming/renderer/cast_streaming_receiver.h"
+#include "components/cast_streaming/renderer/demuxer_connector.h"
 #include "components/cast_streaming/renderer/public/resource_provider.h"
 #include "components/cast_streaming/renderer/renderer_controller_proxy.h"
 #include "content/public/renderer/render_frame_observer.h"
@@ -61,9 +61,7 @@
                             EndOfLifeCB end_of_life_cb);
     ~PerRenderFrameResources() override;
 
-    CastStreamingReceiver& cast_streaming_receiver() {
-      return cast_streaming_receiver_;
-    }
+    DemuxerConnector& demuxer_connector() { return demuxer_connector_; }
 
     RendererControllerProxy& renderer_controller_proxy() {
       DCHECK(renderer_controller_proxy_);
@@ -81,7 +79,7 @@
     // The singleton associated with forming the mojo connection used to pass
     // DecoderBuffers from the browser process into the renderer process's
     // DemuxerStream used by the media pipeline.
-    CastStreamingReceiver cast_streaming_receiver_;
+    DemuxerConnector demuxer_connector_;
 
     // The singleton associated with sending playback commands from the browser
     // to the renderer process. Only populated if remoting is enabled.
diff --git a/components/chromeos_camera/common/mjpeg_decode_accelerator_mojom_traits.cc b/components/chromeos_camera/common/mjpeg_decode_accelerator_mojom_traits.cc
index 393200a..e68b05a 100644
--- a/components/chromeos_camera/common/mjpeg_decode_accelerator_mojom_traits.cc
+++ b/components/chromeos_camera/common/mjpeg_decode_accelerator_mojom_traits.cc
@@ -5,7 +5,10 @@
 #include "components/chromeos_camera/common/mjpeg_decode_accelerator_mojom_traits.h"
 
 #include "base/check.h"
+#include "base/memory/platform_shared_memory_region.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/notreached.h"
+#include "base/numerics/checked_math.h"
 #include "base/time/time.h"
 #include "media/base/ipc/media_param_traits_macros.h"
 #include "mojo/public/cpp/base/time_mojom_traits.h"
@@ -69,7 +72,9 @@
 mojo::ScopedSharedBufferHandle StructTraits<
     chromeos_camera::mojom::BitstreamBufferDataView,
     media::BitstreamBuffer>::memory_handle(media::BitstreamBuffer& input) {
-  base::subtle::PlatformSharedMemoryRegion input_region = input.TakeRegion();
+  base::subtle::PlatformSharedMemoryRegion input_region =
+      base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
+          input.TakeRegion());
   DCHECK(input_region.IsValid()) << "Bad BitstreamBuffer handle";
 
   // TODO(https://crbug.com/793446): Split BitstreamBuffers into ReadOnly and
@@ -105,14 +110,18 @@
   if (!handle.is_valid())
     return false;
 
-  auto memory_region =
-      mojo::UnwrapPlatformSharedMemoryRegion(std::move(handle));
-  if (!memory_region.IsValid())
+  auto region = base::UnsafeSharedMemoryRegion::Deserialize(
+      mojo::UnwrapPlatformSharedMemoryRegion(std::move(handle)));
+  if (!region.IsValid())
     return false;
 
-  media::BitstreamBuffer bitstream_buffer(
-      input.id(), std::move(memory_region), input.size(),
-      base::checked_cast<off_t>(input.offset()), timestamp);
+  auto offset = base::MakeCheckedNum(input.offset()).Cast<uint64_t>();
+  if (!offset.IsValid())
+    return false;
+
+  media::BitstreamBuffer bitstream_buffer(input.id(), std::move(region),
+                                          input.size(), offset.ValueOrDie(),
+                                          timestamp);
   if (key_id.size()) {
     // Note that BitstreamBuffer currently ignores how each buffer is
     // encrypted and uses the settings from the Audio/VideoDecoderConfig.
diff --git a/components/chromeos_camera/fake_mjpeg_decode_accelerator.cc b/components/chromeos_camera/fake_mjpeg_decode_accelerator.cc
index 4f8c6dc..1a0f9f12 100644
--- a/components/chromeos_camera/fake_mjpeg_decode_accelerator.cc
+++ b/components/chromeos_camera/fake_mjpeg_decode_accelerator.cc
@@ -8,10 +8,11 @@
 
 #include "base/bind.h"
 #include "base/logging.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "media/base/bind_to_current_loop.h"
-#include "media/base/unaligned_shared_memory.h"
 #include "media/base/video_frame.h"
 #include "media/base/video_types.h"
 
@@ -60,10 +61,10 @@
     scoped_refptr<media::VideoFrame> video_frame) {
   DCHECK(io_task_runner_->BelongsToCurrentThread());
 
-  auto src_shm = std::make_unique<media::UnalignedSharedMemory>(
-      bitstream_buffer.TakeRegion(), bitstream_buffer.size(),
-      false /* read_only */);
-  if (!src_shm->MapAt(bitstream_buffer.offset(), bitstream_buffer.size())) {
+  base::UnsafeSharedMemoryRegion src_shm_region = bitstream_buffer.TakeRegion();
+  base::WritableSharedMemoryMapping src_shm_mapping =
+      src_shm_region.MapAt(bitstream_buffer.offset(), bitstream_buffer.size());
+  if (!src_shm_mapping.IsValid()) {
     DLOG(ERROR) << "Unable to map shared memory in FakeMjpegDecodeAccelerator";
     NotifyError(bitstream_buffer.id(),
                 MjpegDecodeAccelerator::UNREADABLE_INPUT);
@@ -75,7 +76,7 @@
       FROM_HERE,
       base::BindOnce(&FakeMjpegDecodeAccelerator::DecodeOnDecoderThread,
                      base::Unretained(this), bitstream_buffer.id(),
-                     std::move(video_frame), std::move(src_shm)));
+                     std::move(video_frame), std::move(src_shm_mapping)));
 }
 
 void FakeMjpegDecodeAccelerator::Decode(
@@ -90,7 +91,7 @@
 void FakeMjpegDecodeAccelerator::DecodeOnDecoderThread(
     int32_t task_id,
     scoped_refptr<media::VideoFrame> video_frame,
-    std::unique_ptr<media::UnalignedSharedMemory> src_shm) {
+    base::WritableSharedMemoryMapping src_shm_mapping) {
   DCHECK(decoder_task_runner_->BelongsToCurrentThread());
 
   // Do not actually decode the Jpeg data.
diff --git a/components/chromeos_camera/fake_mjpeg_decode_accelerator.h b/components/chromeos_camera/fake_mjpeg_decode_accelerator.h
index 25f155f1..b18f9552 100644
--- a/components/chromeos_camera/fake_mjpeg_decode_accelerator.h
+++ b/components/chromeos_camera/fake_mjpeg_decode_accelerator.h
@@ -7,8 +7,7 @@
 
 #include <stdint.h>
 
-#include <memory>
-
+#include "base/memory/shared_memory_mapping.h"
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread.h"
 #include "components/chromeos_camera/mjpeg_decode_accelerator.h"
@@ -47,10 +46,9 @@
   bool IsSupported() override;
 
  private:
-  void DecodeOnDecoderThread(
-      int32_t task_id,
-      scoped_refptr<media::VideoFrame> video_frame,
-      std::unique_ptr<media::UnalignedSharedMemory> src_shm);
+  void DecodeOnDecoderThread(int32_t task_id,
+                             scoped_refptr<media::VideoFrame> video_frame,
+                             base::WritableSharedMemoryMapping src_shm_mapping);
   void NotifyError(int32_t task_id, Error error);
   void NotifyErrorOnClientThread(int32_t task_id, Error error);
   void OnDecodeDoneOnClientThread(int32_t task_id);
diff --git a/components/chromeos_camera/mjpeg_decode_accelerator_unittest.cc b/components/chromeos_camera/mjpeg_decode_accelerator_unittest.cc
index a65d033..1082d19 100644
--- a/components/chromeos_camera/mjpeg_decode_accelerator_unittest.cc
+++ b/components/chromeos_camera/mjpeg_decode_accelerator_unittest.cc
@@ -23,7 +23,6 @@
 #include "base/gtest_prod_util.h"
 #include "base/json/json_writer.h"
 #include "base/logging.h"
-#include "base/memory/platform_shared_memory_region.h"
 #include "base/memory/shared_memory_mapping.h"
 #include "base/memory/unsafe_shared_memory_region.h"
 #include "base/numerics/safe_conversions.h"
@@ -851,11 +850,8 @@
                      task.image->data_str.size(), 0 /* src_offset */,
                      hw_out_dmabuf_frame_);
   } else {
-    base::subtle::PlatformSharedMemoryRegion dup_region =
-        base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-            in_shm_.Duplicate());
-    ASSERT_EQ(dup_region.GetSize(), task.image->data_str.size());
-    media::BitstreamBuffer bitstream_buffer(task_id, std::move(dup_region),
+    ASSERT_EQ(in_shm_.GetSize(), task.image->data_str.size());
+    media::BitstreamBuffer bitstream_buffer(task_id, in_shm_.Duplicate(),
                                             task.image->data_str.size());
     decoder_->Decode(std::move(bitstream_buffer), hw_out_frame_);
   }
diff --git a/components/chromeos_camera/mojo_jpeg_encode_accelerator_service.cc b/components/chromeos_camera/mojo_jpeg_encode_accelerator_service.cc
index e978755..aa303a7 100644
--- a/components/chromeos_camera/mojo_jpeg_encode_accelerator_service.cc
+++ b/components/chromeos_camera/mojo_jpeg_encode_accelerator_service.cc
@@ -213,21 +213,23 @@
               base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
               input_buffer_size, base::UnguessableToken::Create()));
 
-  base::subtle::PlatformSharedMemoryRegion output_shm_region =
-      base::subtle::PlatformSharedMemoryRegion::Take(
-          std::move(output_fd),
-          base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
-          output_buffer_size, base::UnguessableToken::Create());
+  base::UnsafeSharedMemoryRegion output_shm_region =
+      base::UnsafeSharedMemoryRegion::Deserialize(
+          base::subtle::PlatformSharedMemoryRegion::Take(
+              std::move(output_fd),
+              base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
+              output_buffer_size, base::UnguessableToken::Create()));
 
   media::BitstreamBuffer output_buffer(task_id, std::move(output_shm_region),
                                        output_buffer_size);
   std::unique_ptr<media::BitstreamBuffer> exif_buffer;
   if (exif_buffer_size > 0) {
-    base::subtle::PlatformSharedMemoryRegion exif_shm_region =
-        base::subtle::PlatformSharedMemoryRegion::Take(
-            base::subtle::ScopedFDPair(std::move(exif_fd), base::ScopedFD()),
-            base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
-            exif_buffer_size, base::UnguessableToken::Create());
+    base::UnsafeSharedMemoryRegion exif_shm_region =
+        base::UnsafeSharedMemoryRegion::Deserialize(
+            base::subtle::PlatformSharedMemoryRegion::Take(
+                std::move(exif_fd),
+                base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
+                exif_buffer_size, base::UnguessableToken::Create()));
     exif_buffer = std::make_unique<media::BitstreamBuffer>(
         task_id, std::move(exif_shm_region), exif_buffer_size);
   }
@@ -336,11 +338,12 @@
   if (exif_buffer_size > 0) {
     // Currently we use our zero-based |task_id| as id of |exif_buffer| to track
     // the encode task process from both Chrome OS and Chrome side.
-    base::subtle::PlatformSharedMemoryRegion exif_shm_region =
-        base::subtle::PlatformSharedMemoryRegion::Take(
-            base::subtle::ScopedFDPair(std::move(exif_fd), base::ScopedFD()),
-            base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
-            exif_buffer_size, base::UnguessableToken::Create());
+    base::UnsafeSharedMemoryRegion exif_shm_region =
+        base::UnsafeSharedMemoryRegion::Deserialize(
+            base::subtle::PlatformSharedMemoryRegion::Take(
+                std::move(exif_fd),
+                base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
+                exif_buffer_size, base::UnguessableToken::Create()));
     exif_buffer = std::make_unique<media::BitstreamBuffer>(
         task_id, std::move(exif_shm_region), exif_buffer_size);
   }
diff --git a/components/chromeos_camera/mojo_mjpeg_decode_accelerator_service_unittest.cc b/components/chromeos_camera/mojo_mjpeg_decode_accelerator_service_unittest.cc
index ed5c282..4a15f4f 100644
--- a/components/chromeos_camera/mojo_mjpeg_decode_accelerator_service_unittest.cc
+++ b/components/chromeos_camera/mojo_mjpeg_decode_accelerator_service_unittest.cc
@@ -6,7 +6,7 @@
 
 #include "base/bind.h"
 #include "base/command_line.h"
-#include "base/memory/platform_shared_memory_region.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
 #include "base/threading/thread.h"
@@ -71,16 +71,14 @@
   subsamples.push_back(media::SubsampleEntry(15, 7));
 
   base::RunLoop run_loop2;
-  base::subtle::PlatformSharedMemoryRegion shm_region =
-      base::subtle::PlatformSharedMemoryRegion::CreateUnsafe(
-          kInputBufferSizeInBytes);
+  base::UnsafeSharedMemoryRegion shm_region =
+      base::UnsafeSharedMemoryRegion::Create(kInputBufferSizeInBytes);
 
   // mojo::SharedBufferHandle::Create will make a writable region, but an unsafe
   // one is needed.
   mojo::ScopedSharedBufferHandle output_frame_handle =
-      mojo::WrapPlatformSharedMemoryRegion(
-          base::subtle::PlatformSharedMemoryRegion::CreateUnsafe(
-              kOutputFrameSizeInBytes));
+      mojo::WrapUnsafeSharedMemoryRegion(
+          base::UnsafeSharedMemoryRegion::Create(kOutputFrameSizeInBytes));
 
   media::BitstreamBuffer bitstream_buffer(kArbitraryBitstreamBufferId,
                                           std::move(shm_region),
diff --git a/components/cronet/tools/api_static_checks.py b/components/cronet/tools/api_static_checks.py
index 7ced72d3..861df44 100755
--- a/components/cronet/tools/api_static_checks.py
+++ b/components/cronet/tools/api_static_checks.py
@@ -87,6 +87,8 @@
     os.path.dirname(__file__), '..', 'android',
     'implementation_api_version.txt'))
 JAR_PATH = os.path.join(build_utils.JAVA_HOME, 'bin', 'jar')
+JAVAP_PATH = os.path.join(build_utils.JAVA_HOME, 'bin', 'javap')
+
 
 def find_api_calls(dump, api_classes, bad_calls):
   # Given a dump of an implementation class, find calls through API classes.
@@ -162,10 +164,12 @@
       continue
     # Dump classes
     dump_file = os.path.join(temp_dir, 'dump.txt')
-    if os.system('javap -c %s > %s' % (
-        ' '.join(os.path.join(dirpath, f) for f in filenames).replace(
-            '$', '\\$'),
-        dump_file)):
+    javap_cmd = '%s -c %s > %s' % (
+        JAVAP_PATH,
+        ' '.join(os.path.join(dirpath, f) for f in filenames).replace('$',
+                                                                      '\\$'),
+        dump_file)
+    if os.system(javap_cmd):
       print('ERROR: javap failed on ' + ' '.join(filenames))
       return False
     # Process class dump
diff --git a/components/cronet/tools/update_api.py b/components/cronet/tools/update_api.py
index a9c3015..7707927 100755
--- a/components/cronet/tools/update_api.py
+++ b/components/cronet/tools/update_api.py
@@ -18,6 +18,12 @@
 import tempfile
 
 
+REPOSITORY_ROOT = os.path.abspath(
+    os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir))
+
+sys.path.insert(0, os.path.join(REPOSITORY_ROOT, 'build/android/gyp'))
+from util import build_utils  # pylint: disable=wrong-import-position
+
 # Filename of dump of current API.
 API_FILENAME = os.path.abspath(os.path.join(
     os.path.dirname(__file__), '..', 'android', 'api.txt'))
@@ -33,6 +39,9 @@
 # for example 'Foo$1'.
 UNNAMED_CLASS_RE = re.compile(r'.*\$[0-9]')
 
+JAR_PATH = os.path.join(build_utils.JAVA_HOME, 'bin', 'jar')
+JAVAP_PATH = os.path.join(build_utils.JAVA_HOME, 'bin', 'javap')
+
 
 def generate_api(api_jar, output_filename):
   # Dumps the API in |api_jar| into |outpuf_filename|.
@@ -45,8 +54,9 @@
   temp_dir = tempfile.mkdtemp()
   old_cwd = os.getcwd()
   api_jar_path = os.path.abspath(api_jar)
+  jar_cmd = '%s xf %s' % (os.path.relpath(JAR_PATH, temp_dir), api_jar_path)
   os.chdir(temp_dir)
-  if os.system('jar xf %s' % api_jar_path):
+  if os.system(jar_cmd):
     print('ERROR: jar failed on ' + api_jar)
     return False
   os.chdir(old_cwd)
@@ -59,8 +69,10 @@
   api_class_files.sort()
 
   # Dump API class files into |output_filename|
-  javap_cmd = ('javap -protected %s >> %s' % (' '.join(api_class_files),
-      output_filename)).replace('$', '\\$')
+  javap_cmd = (
+      '%s -protected %s >> %s' % (
+          JAVAP_PATH, ' '.join(api_class_files), output_filename)
+  ).replace('$', '\\$')
   if os.system(javap_cmd):
     print('ERROR: javap command failed: ' + javap_cmd)
     return False
diff --git a/components/desks_storage/BUILD.gn b/components/desks_storage/BUILD.gn
index 76523434..5e9baf1e 100644
--- a/components/desks_storage/BUILD.gn
+++ b/components/desks_storage/BUILD.gn
@@ -44,6 +44,7 @@
 source_set("unit_tests") {
   testonly = true
   sources = [
+    "core/desk_model_wrapper_unittests.cc",
     "core/desk_sync_bridge_unittest.cc",
     "core/desk_template_conversion_unittests.cc",
     "core/desk_template_util_unittests.cc",
diff --git a/components/desks_storage/core/desk_model_wrapper.cc b/components/desks_storage/core/desk_model_wrapper.cc
index 130f1c0..a294b35 100644
--- a/components/desks_storage/core/desk_model_wrapper.cc
+++ b/components/desks_storage/core/desk_model_wrapper.cc
@@ -42,6 +42,15 @@
 void DeskModelWrapper::GetEntryByUUID(
     const std::string& uuid,
     DeskModel::GetEntryByUuidCallback callback) {
+  // Check if this is an admin template uuid first.
+  std::unique_ptr<ash::DeskTemplate> policy_entry =
+      GetAdminDeskTemplateByUUID(uuid);
+
+  if (policy_entry) {
+    std::move(callback).Run(GetEntryByUuidStatus::kOk, std::move(policy_entry));
+    return;
+  }
+
   if (GetDeskTemplateModel()->HasUuid(uuid)) {
     GetDeskTemplateModel()->GetEntryByUUID(uuid, std::move(callback));
   } else {
diff --git a/components/desks_storage/core/desk_model_wrapper_unittests.cc b/components/desks_storage/core/desk_model_wrapper_unittests.cc
new file mode 100644
index 0000000..2355f9f0
--- /dev/null
+++ b/components/desks_storage/core/desk_model_wrapper_unittests.cc
@@ -0,0 +1,761 @@
+// Copyright 2022 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 <memory>
+
+#include "components/desks_storage/core/desk_model_wrapper.h"
+
+#include "ash/public/cpp/desk_template.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/guid.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/bind.h"
+#include "base/test/simple_test_clock.h"
+#include "base/test/task_environment.h"
+#include "components/account_id/account_id.h"
+#include "components/app_constants/constants.h"
+#include "components/app_restore/app_launch_info.h"
+#include "components/desks_storage/core/desk_model_observer.h"
+#include "components/desks_storage/core/desk_sync_bridge.h"
+#include "components/desks_storage/core/desk_template_conversion.h"
+#include "components/desks_storage/core/desk_template_util.h"
+#include "components/desks_storage/core/local_desk_data_manager.h"
+#include "components/services/app_service/public/cpp/app_registry_cache.h"
+#include "components/services/app_service/public/cpp/app_registry_cache_wrapper.h"
+#include "components/services/app_service/public/cpp/app_types.h"
+#include "components/services/app_service/public/cpp/features.h"
+#include "components/sync/model/entity_change.h"
+#include "components/sync/model/in_memory_metadata_change_list.h"
+#include "components/sync/model/metadata_batch.h"
+#include "components/sync/protocol/entity_data.h"
+#include "components/sync/protocol/model_type_state.pb.h"
+#include "components/sync/test/model/mock_model_type_change_processor.h"
+#include "components/sync/test/model/model_type_store_test_util.h"
+#include "components/sync/test/model/test_matchers.h"
+#include "desk_model_wrapper.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace desks_storage {
+
+namespace {
+
+constexpr char kTemplateFileNameFormat[] = "%s.saveddesk";
+constexpr char kUuidFormat[] = "1c186d5a-502e-49ce-9ee1-00000000000%d";
+constexpr char kTemplateNameFormat[] = "desk_%d";
+constexpr char kDeskOneTemplateDuplicateExpectedName[] = "desk_01 (1)";
+constexpr char kDeskOneTemplateDuplicateTwoExpectedName[] = "desk_01 (2)";
+const std::string kTestUuid1 = base::StringPrintf(kUuidFormat, 1);
+const std::string kTestUuid2 = base::StringPrintf(kUuidFormat, 2);
+const std::string kTestUuid3 = base::StringPrintf(kUuidFormat, 3);
+const std::string kTestUuid4 = base::StringPrintf(kUuidFormat, 4);
+const std::string kTestUuid5 = base::StringPrintf(kUuidFormat, 5);
+
+const std::string kTestFileName1 =
+    base::StringPrintf(kTemplateFileNameFormat, kTestUuid1.c_str());
+const std::string kPolicyWithOneTemplate =
+    "[{\"version\":1,\"uuid\":\"" + kTestUuid5 +
+    "\",\"name\":\""
+    "Admin Template 1"
+    "\",\"created_time_usec\":\"1633535632\",\"updated_time_usec\": "
+    "\"1633535632\",\"desk\":{\"apps\":[{\"window_"
+    "bound\":{\"left\":0,\"top\":1,\"height\":121,\"width\":120},\"window_"
+    "state\":\"NORMAL\",\"z_index\":1,\"app_type\":\"BROWSER\",\"tabs\":[{"
+    "\"url\":\"https://example.com\",\"title\":\"Example\"},{\"url\":\"https://"
+    "example.com/"
+    "2\",\"title\":\"Example2\"}],\"active_tab_index\":1,\"window_id\":0,"
+    "\"display_id\":\"100\",\"pre_minimized_window_state\":\"NORMAL\"}]}}]";
+
+// Search |entry_list| for |entry_query| as a uuid and returns true if
+// found, false if not.
+bool FindUuidInUuidList(
+    const std::string& uuid_query,
+    const std::vector<const ash::DeskTemplate*>& entry_list) {
+  base::GUID guid = base::GUID::ParseCaseInsensitive(uuid_query);
+  DCHECK(guid.is_valid());
+
+  for (auto* entry : entry_list) {
+    if (entry->uuid() == guid)
+      return true;
+  }
+
+  return false;
+}
+
+// Takes in a vector of DeskTemplate pointers and a uuid, returns a pointer to
+// the DeskTemplate with matching uuid if found in vector, nullptr if not.
+const ash::DeskTemplate* FindEntryInEntryList(
+    const std::string& uuid_string,
+    const std::vector<const ash::DeskTemplate*>& entries) {
+  base::GUID uuid = base::GUID::ParseLowercase(uuid_string);
+  auto found_entry = std::find_if(entries.begin(), entries.end(),
+                                  [&uuid](const ash::DeskTemplate* entry) {
+                                    return uuid == entry->uuid();
+                                  });
+
+  return found_entry != entries.end() ? *found_entry : nullptr;
+}
+
+// Verifies that the status passed into it is kOk
+void VerifyEntryAddedCorrectly(DeskModel::AddOrUpdateEntryStatus status) {
+  EXPECT_EQ(status, DeskModel::AddOrUpdateEntryStatus::kOk);
+}
+
+// Make test template with ID containing the index. Defaults to desk template
+// type if a type is not specified.
+
+std::unique_ptr<ash::DeskTemplate> MakeTestDeskTemplate(
+    int index,
+    ash::DeskTemplateType type) {
+  const std::string template_uuid = base::StringPrintf(kUuidFormat, index);
+  const std::string template_name =
+      base::StringPrintf(kTemplateNameFormat, index);
+  std::unique_ptr<ash::DeskTemplate> desk_template =
+      std::make_unique<ash::DeskTemplate>(
+          template_uuid, ash::DeskTemplateSource::kUser, template_name,
+          base::Time::Now(), type);
+  desk_template->set_desk_restore_data(
+      std::make_unique<app_restore::RestoreData>());
+  return desk_template;
+}
+
+// Make test template with default restore data.
+std::unique_ptr<ash::DeskTemplate> MakeTestDeskTemplate(
+    const std::string& uuid,
+    ash::DeskTemplateSource source,
+    const std::string& name,
+    const base::Time created_time) {
+  auto entry = std::make_unique<ash::DeskTemplate>(
+      uuid, source, name, created_time, ash::DeskTemplateType::kTemplate);
+  entry->set_desk_restore_data(std::make_unique<app_restore::RestoreData>());
+  return entry;
+}
+
+// Make test save and recall desk with default restore data.
+std::unique_ptr<ash::DeskTemplate> MakeTestSaveAndRecallDesk(
+    const std::string& uuid,
+    const std::string& name,
+    const base::Time created_time) {
+  auto entry = std::make_unique<ash::DeskTemplate>(
+      uuid, ash::DeskTemplateSource::kUser, name, created_time,
+      ash::DeskTemplateType::kSaveAndRecall);
+  entry->set_desk_restore_data(std::make_unique<app_restore::RestoreData>());
+  return entry;
+}
+
+}  // namespace
+
+class MockDeskModelObserver : public DeskModelObserver {
+ public:
+  MOCK_METHOD0(DeskModelLoaded, void());
+  MOCK_METHOD1(EntriesAddedOrUpdatedRemotely,
+               void(const std::vector<const ash::DeskTemplate*>&));
+  MOCK_METHOD1(EntriesRemovedRemotely, void(const std::vector<std::string>&));
+  MOCK_METHOD1(EntriesAddedOrUpdatedLocally,
+               void(const std::vector<const ash::DeskTemplate*>&));
+  MOCK_METHOD1(EntriesRemovedLocally, void(const std::vector<std::string>&));
+};
+
+// This test class only tests the overall wrapper desk model class. The
+// correctness of the underlying desk model storages that
+// `DeskModelWrapper` uses are tested in their own unittests.
+class DeskModelWrapperTest : public testing::Test {
+ public:
+  DeskModelWrapperTest()
+      : sample_desk_template_one_(
+            MakeTestDeskTemplate(kTestUuid1,
+                                 ash::DeskTemplateSource::kUser,
+                                 "desk_01",
+                                 base::Time::Now())),
+        sample_desk_template_two_(
+            MakeTestDeskTemplate(kTestUuid2,
+                                 ash::DeskTemplateSource::kUser,
+                                 "desk_02",
+                                 base::Time::Now())),
+        sample_save_and_recall_desk_one_(
+            MakeTestSaveAndRecallDesk(kTestUuid3,
+                                      "save_and_recall_desk_01",
+                                      base::Time::Now())),
+        sample_save_and_recall_desk_two_(
+            MakeTestSaveAndRecallDesk(kTestUuid4,
+                                      "save_and_recall_desk_02",
+                                      base::Time::Now())),
+        task_environment_(base::test::TaskEnvironment::MainThreadType::IO),
+        cache_(std::make_unique<apps::AppRegistryCache>()),
+        account_id_(AccountId::FromUserEmail("test@gmail.com")),
+        data_manager_(std::unique_ptr<LocalDeskDataManager>()),
+        store_(syncer::ModelTypeStoreTestUtil::CreateInMemoryStoreForTest()) {}
+
+  DeskModelWrapperTest(const DeskModelWrapperTest&) = delete;
+  DeskModelWrapperTest& operator=(const DeskModelWrapperTest&) = delete;
+
+  ~DeskModelWrapperTest() override = default;
+
+  void SetUp() override {
+    EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
+    data_manager_ = std::make_unique<LocalDeskDataManager>(temp_dir_.GetPath(),
+                                                           account_id_);
+    data_manager_->SetExcludeSaveAndRecallDeskInMaxEntryCountForTesting(false);
+    desk_template_util::PopulateAppRegistryCache(account_id_, cache_.get());
+    model_wrapper_ = std::make_unique<DeskModelWrapper>(data_manager_.get());
+    task_environment_.RunUntilIdle();
+    testing::Test::SetUp();
+  }
+
+  void CreateBridge() {
+    ON_CALL(mock_processor_, IsTrackingMetadata())
+        .WillByDefault(testing::Return(true));
+    bridge_ = std::make_unique<DeskSyncBridge>(
+        mock_processor_.CreateForwardingProcessor(),
+        syncer::ModelTypeStoreTestUtil::FactoryForForwardingStore(store_.get()),
+        account_id_);
+    bridge_->AddObserver(&mock_observer_);
+  }
+
+  void FinishInitialization() { base::RunLoop().RunUntilIdle(); }
+
+  void InitializeBridge() {
+    CreateBridge();
+    FinishInitialization();
+    model_wrapper_->SetDeskSyncBridge(bridge_.get());
+  }
+
+  void ShutdownBridge() {
+    base::RunLoop().RunUntilIdle();
+    bridge_->RemoveObserver(&mock_observer_);
+  }
+
+  void RestartBridge() {
+    ShutdownBridge();
+    InitializeBridge();
+  }
+
+  void AddTwoTemplates() {
+    base::RunLoop loop1;
+    model_wrapper_->AddOrUpdateEntry(
+        std::move(sample_desk_template_one_),
+        base::BindLambdaForTesting(
+            [&](DeskModel::AddOrUpdateEntryStatus status) {
+              EXPECT_EQ(status, DeskModel::AddOrUpdateEntryStatus::kOk);
+              loop1.Quit();
+            }));
+    loop1.Run();
+
+    base::RunLoop loop2;
+    model_wrapper_->AddOrUpdateEntry(
+        std::move(sample_desk_template_two_),
+        base::BindLambdaForTesting(
+            [&](DeskModel::AddOrUpdateEntryStatus status) {
+              EXPECT_EQ(status, DeskModel::AddOrUpdateEntryStatus::kOk);
+              loop2.Quit();
+            }));
+    loop2.Run();
+  }
+
+  void AddTwoSaveAndRecallDeskTemplates() {
+    base::RunLoop loop1;
+    model_wrapper_->AddOrUpdateEntry(
+        std::move(sample_save_and_recall_desk_one_),
+        base::BindLambdaForTesting(
+            [&](DeskModel::AddOrUpdateEntryStatus status) {
+              EXPECT_EQ(status, DeskModel::AddOrUpdateEntryStatus::kOk);
+              loop1.Quit();
+            }));
+    loop1.Run();
+
+    base::RunLoop loop2;
+    model_wrapper_->AddOrUpdateEntry(
+        std::move(sample_save_and_recall_desk_two_),
+        base::BindLambdaForTesting(
+            [&](DeskModel::AddOrUpdateEntryStatus status) {
+              EXPECT_EQ(status, DeskModel::AddOrUpdateEntryStatus::kOk);
+              loop2.Quit();
+            }));
+    loop2.Run();
+  }
+
+  void VerifyAllEntries(size_t expected_size, const std::string& trace_string) {
+    SCOPED_TRACE(trace_string);
+    base::RunLoop loop;
+
+    model_wrapper_->GetAllEntries(base::BindLambdaForTesting(
+        [&](DeskModel::GetAllEntriesStatus status,
+            const std::vector<const ash::DeskTemplate*>& entries) {
+          EXPECT_EQ(status, DeskModel::GetAllEntriesStatus::kOk);
+          EXPECT_EQ(entries.size(), expected_size);
+          loop.Quit();
+        }));
+
+    loop.Run();
+  }
+
+  base::ScopedTempDir temp_dir_;
+  std::unique_ptr<ash::DeskTemplate> sample_desk_template_one_;
+  std::unique_ptr<ash::DeskTemplate> sample_desk_template_two_;
+  std::unique_ptr<ash::DeskTemplate> sample_save_and_recall_desk_one_;
+  std::unique_ptr<ash::DeskTemplate> sample_save_and_recall_desk_two_;
+  base::test::TaskEnvironment task_environment_;
+  std::unique_ptr<apps::AppRegistryCache> cache_;
+  AccountId account_id_;
+  std::unique_ptr<LocalDeskDataManager> data_manager_;
+  std::unique_ptr<syncer::ModelTypeStore> store_;
+  testing::NiceMock<syncer::MockModelTypeChangeProcessor> mock_processor_;
+  std::unique_ptr<DeskSyncBridge> bridge_;
+  testing::NiceMock<MockDeskModelObserver> mock_observer_;
+  std::unique_ptr<DeskModelWrapper> model_wrapper_;
+};
+
+TEST_F(DeskModelWrapperTest, CanAddDeskTemplateEntry) {
+  InitializeBridge();
+
+  model_wrapper_->AddOrUpdateEntry(std::move(sample_desk_template_one_),
+                                   base::BindOnce(&VerifyEntryAddedCorrectly));
+
+  VerifyAllEntries(1ul, "Added one desk template");
+  base::RunLoop loop;
+  // Verify that it's not desk template entry in the save and recall desk
+  // storage.
+  model_wrapper_->GetAllEntries(base::BindLambdaForTesting(
+      [&](DeskModel::GetAllEntriesStatus status,
+          const std::vector<const ash::DeskTemplate*>& entries) {
+        EXPECT_EQ(status, DeskModel::GetAllEntriesStatus::kOk);
+        EXPECT_EQ(entries.size(), 1ul);
+        EXPECT_EQ(entries[0]->type(), ash::DeskTemplateType::kTemplate);
+        loop.Quit();
+      }));
+  EXPECT_EQ(model_wrapper_->GetDeskTemplateEntryCount(), 1ul);
+  EXPECT_EQ(model_wrapper_->GetSaveAndRecallDeskEntryCount(), 0ul);
+  loop.Run();
+}
+
+TEST_F(DeskModelWrapperTest, CanAddSaveAndRecallDeskEntry) {
+  InitializeBridge();
+
+  model_wrapper_->AddOrUpdateEntry(
+      MakeTestDeskTemplate(1u, ash::DeskTemplateType::kSaveAndRecall),
+      base::BindOnce(&VerifyEntryAddedCorrectly));
+
+  VerifyAllEntries(1ul, "Added one save and recall desk");
+  base::RunLoop loop;
+  // Verify that it's not SaveAndRecall entry in the desk template storage.
+  model_wrapper_->GetAllEntries(base::BindLambdaForTesting(
+      [&](DeskModel::GetAllEntriesStatus status,
+          const std::vector<const ash::DeskTemplate*>& entries) {
+        EXPECT_EQ(status, DeskModel::GetAllEntriesStatus::kOk);
+        EXPECT_EQ(entries.size(), 1ul);
+        EXPECT_EQ(entries[0]->type(), ash::DeskTemplateType::kSaveAndRecall);
+        loop.Quit();
+      }));
+  EXPECT_EQ(model_wrapper_->GetDeskTemplateEntryCount(), 0ul);
+  EXPECT_EQ(model_wrapper_->GetSaveAndRecallDeskEntryCount(), 1ul);
+  loop.Run();
+}
+
+TEST_F(DeskModelWrapperTest, CanGetAllEntries) {
+  InitializeBridge();
+
+  AddTwoTemplates();
+  base::RunLoop loop;
+  model_wrapper_->GetAllEntries(base::BindLambdaForTesting(
+      [&](DeskModel::GetAllEntriesStatus status,
+          const std::vector<const ash::DeskTemplate*>& entries) {
+        EXPECT_EQ(status, DeskModel::GetAllEntriesStatus::kOk);
+        EXPECT_EQ(entries.size(), 2ul);
+        EXPECT_TRUE(FindUuidInUuidList(kTestUuid1, entries));
+        EXPECT_TRUE(FindUuidInUuidList(kTestUuid2, entries));
+
+        // Sanity check for the search function.
+        EXPECT_FALSE(FindUuidInUuidList(kTestUuid3, entries));
+        loop.Quit();
+      }));
+  loop.Run();
+}
+
+TEST_F(DeskModelWrapperTest, GetAllEntriesIncludesPolicyValues) {
+  InitializeBridge();
+
+  AddTwoTemplates();
+  AddTwoSaveAndRecallDeskTemplates();
+  model_wrapper_->SetPolicyDeskTemplates(kPolicyWithOneTemplate);
+
+  base::RunLoop loop;
+  model_wrapper_->GetAllEntries(base::BindLambdaForTesting(
+      [&](DeskModel::GetAllEntriesStatus status,
+          const std::vector<const ash::DeskTemplate*>& entries) {
+        EXPECT_EQ(status, DeskModel::GetAllEntriesStatus::kOk);
+        EXPECT_EQ(entries.size(), 5ul);
+        EXPECT_TRUE(FindUuidInUuidList(kTestUuid1, entries));
+        EXPECT_TRUE(FindUuidInUuidList(kTestUuid2, entries));
+        EXPECT_TRUE(FindUuidInUuidList(kTestUuid3, entries));
+        EXPECT_TRUE(FindUuidInUuidList(kTestUuid4, entries));
+        EXPECT_TRUE(FindUuidInUuidList(kTestUuid5, entries));
+        // One of these templates should be from policy.
+        EXPECT_EQ(
+            base::ranges::count_if(entries,
+                                   [](const ash::DeskTemplate* entry) {
+                                     return entry->source() ==
+                                            ash::DeskTemplateSource::kPolicy;
+                                   }),
+            1l);
+
+        loop.Quit();
+      }));
+  loop.Run();
+
+  model_wrapper_->SetPolicyDeskTemplates("");
+}
+
+TEST_F(DeskModelWrapperTest, CanMarkDuplicateEntryNames) {
+  InitializeBridge();
+
+  model_wrapper_->AddOrUpdateEntry(std::move(sample_desk_template_one_),
+                                   base::BindOnce(&VerifyEntryAddedCorrectly));
+  auto dupe_template_uuid = base::StringPrintf(kUuidFormat, 6);
+  auto dupe_desk_template =
+      MakeTestDeskTemplate(dupe_template_uuid, ash::DeskTemplateSource::kUser,
+                           "desk_01", base::Time::Now());
+  model_wrapper_->AddOrUpdateEntry(std::move(dupe_desk_template),
+                                   base::BindOnce(&VerifyEntryAddedCorrectly));
+
+  auto second_dupe_template_uuid = base::StringPrintf(kUuidFormat, 7);
+  auto second_dupe_desk_template = MakeTestDeskTemplate(
+      second_dupe_template_uuid, ash::DeskTemplateSource::kUser, "desk_01",
+      base::Time::Now());
+  model_wrapper_->AddOrUpdateEntry(std::move(second_dupe_desk_template),
+                                   base::BindOnce(&VerifyEntryAddedCorrectly));
+
+  base::RunLoop loop;
+  model_wrapper_->GetAllEntries(base::BindLambdaForTesting(
+      [&](DeskModel::GetAllEntriesStatus status,
+          const std::vector<const ash::DeskTemplate*>& entries) {
+        EXPECT_EQ(status, DeskModel::GetAllEntriesStatus::kOk);
+        EXPECT_EQ(entries.size(), 3ul);
+        EXPECT_TRUE(FindUuidInUuidList(kTestUuid1, entries));
+        EXPECT_TRUE(FindUuidInUuidList(dupe_template_uuid, entries));
+        EXPECT_TRUE(FindUuidInUuidList(second_dupe_template_uuid, entries));
+        const ash::DeskTemplate* duplicate_one =
+            FindEntryInEntryList(dupe_template_uuid, entries);
+        EXPECT_NE(duplicate_one, nullptr);
+        EXPECT_EQ(base::UTF16ToUTF8(duplicate_one->template_name()),
+                  kDeskOneTemplateDuplicateExpectedName);
+
+        const ash::DeskTemplate* duplicate_two =
+            FindEntryInEntryList(second_dupe_template_uuid, entries);
+        EXPECT_NE(duplicate_two, nullptr);
+        EXPECT_EQ(base::UTF16ToUTF8(duplicate_two->template_name()),
+                  kDeskOneTemplateDuplicateTwoExpectedName);
+
+        loop.Quit();
+      }));
+  loop.Run();
+}
+
+TEST_F(DeskModelWrapperTest, CanGetDeskTemplateEntryByUuid) {
+  InitializeBridge();
+
+  model_wrapper_->AddOrUpdateEntry(std::move(sample_desk_template_one_),
+                                   base::BindOnce(&VerifyEntryAddedCorrectly));
+
+  model_wrapper_->GetEntryByUUID(
+      kTestUuid1,
+      base::BindLambdaForTesting([&](DeskModel::GetEntryByUuidStatus status,
+                                     std::unique_ptr<ash::DeskTemplate> entry) {
+        EXPECT_EQ(status, DeskModel::GetEntryByUuidStatus::kOk);
+
+        EXPECT_EQ(entry->uuid(), base::GUID::ParseCaseInsensitive(kTestUuid1));
+        EXPECT_EQ(base::UTF16ToUTF8(entry->template_name()), "desk_01");
+      }));
+
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(DeskModelWrapperTest, CanGetSaveAndRecallEntryByUuid) {
+  InitializeBridge();
+
+  model_wrapper_->AddOrUpdateEntry(std::move(sample_save_and_recall_desk_one_),
+                                   base::BindOnce(&VerifyEntryAddedCorrectly));
+
+  model_wrapper_->GetEntryByUUID(
+      kTestUuid3,
+      base::BindLambdaForTesting([&](DeskModel::GetEntryByUuidStatus status,
+                                     std::unique_ptr<ash::DeskTemplate> entry) {
+        EXPECT_EQ(status, DeskModel::GetEntryByUuidStatus::kOk);
+
+        EXPECT_EQ(entry->uuid(), base::GUID::ParseCaseInsensitive(kTestUuid3));
+        EXPECT_EQ(base::UTF16ToUTF8(entry->template_name()),
+                  "save_and_recall_desk_01");
+      }));
+
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(DeskModelWrapperTest, GetEntryByUuidShouldReturnAdminTemplate) {
+  InitializeBridge();
+
+  model_wrapper_->AddOrUpdateEntry(std::move(sample_desk_template_one_),
+                                   base::BindOnce(&VerifyEntryAddedCorrectly));
+
+  // Set admin template with UUID: kTestUuid5.
+  model_wrapper_->SetPolicyDeskTemplates(kPolicyWithOneTemplate);
+
+  model_wrapper_->GetEntryByUUID(
+      kTestUuid5,
+      base::BindLambdaForTesting([&](DeskModel::GetEntryByUuidStatus status,
+                                     std::unique_ptr<ash::DeskTemplate> entry) {
+        EXPECT_EQ(status, DeskModel::GetEntryByUuidStatus::kOk);
+        EXPECT_EQ(entry->uuid(), base::GUID::ParseCaseInsensitive(kTestUuid5));
+        EXPECT_EQ(entry->source(), ash::DeskTemplateSource::kPolicy);
+        EXPECT_EQ(base::UTF16ToUTF8(entry->template_name()),
+                  "Admin Template 1");
+      }));
+
+  task_environment_.RunUntilIdle();
+}
+
+TEST_F(DeskModelWrapperTest, GetEntryByUuidReturnsNotFoundIfEntryDoesNotExist) {
+  InitializeBridge();
+
+  base::RunLoop loop;
+
+  model_wrapper_->GetEntryByUUID(
+      kTestUuid1,
+      base::BindLambdaForTesting([&](DeskModel::GetEntryByUuidStatus status,
+                                     std::unique_ptr<ash::DeskTemplate> entry) {
+        EXPECT_EQ(status, DeskModel::GetEntryByUuidStatus::kNotFound);
+        loop.Quit();
+      }));
+  loop.Run();
+}
+
+TEST_F(DeskModelWrapperTest, CanUpdateEntry) {
+  InitializeBridge();
+
+  auto modified_desk_template = sample_desk_template_one_->Clone();
+  modified_desk_template->set_template_name(u"desk_01_mod");
+
+  model_wrapper_->AddOrUpdateEntry(std::move(sample_desk_template_one_),
+                                   base::BindOnce(&VerifyEntryAddedCorrectly));
+
+  model_wrapper_->AddOrUpdateEntry(std::move(modified_desk_template),
+                                   base::BindOnce(&VerifyEntryAddedCorrectly));
+
+  base::RunLoop loop;
+  model_wrapper_->GetEntryByUUID(
+      kTestUuid1,
+      base::BindLambdaForTesting([&](DeskModel::GetEntryByUuidStatus status,
+                                     std::unique_ptr<ash::DeskTemplate> entry) {
+        EXPECT_EQ(status, DeskModel::GetEntryByUuidStatus::kOk);
+
+        EXPECT_EQ(entry->uuid(), base::GUID::ParseCaseInsensitive(kTestUuid1));
+        EXPECT_EQ(entry->template_name(),
+                  base::UTF8ToUTF16(std::string("desk_01_mod")));
+        loop.Quit();
+      }));
+  loop.Run();
+}
+
+TEST_F(DeskModelWrapperTest, CanDeleteDeskTemplateEntry) {
+  InitializeBridge();
+
+  model_wrapper_->AddOrUpdateEntry(std::move(sample_desk_template_one_),
+                                   base::BindOnce(&VerifyEntryAddedCorrectly));
+
+  model_wrapper_->DeleteEntry(
+      kTestUuid1,
+      base::BindLambdaForTesting([&](DeskModel::DeleteEntryStatus status) {
+        EXPECT_EQ(status, DeskModel::DeleteEntryStatus::kOk);
+      }));
+
+  VerifyAllEntries(0ul, "Delete desk template");
+}
+
+TEST_F(DeskModelWrapperTest, CanDeleteSaveAndRecallDeskEntry) {
+  InitializeBridge();
+
+  model_wrapper_->AddOrUpdateEntry(std::move(sample_save_and_recall_desk_one_),
+                                   base::BindOnce(&VerifyEntryAddedCorrectly));
+
+  model_wrapper_->DeleteEntry(
+      kTestUuid3,
+      base::BindLambdaForTesting([&](DeskModel::DeleteEntryStatus status) {
+        EXPECT_EQ(status, DeskModel::DeleteEntryStatus::kOk);
+      }));
+
+  VerifyAllEntries(0ul, "Delete save and recall desk");
+}
+
+TEST_F(DeskModelWrapperTest, CanDeleteAllEntries) {
+  InitializeBridge();
+
+  AddTwoTemplates();
+  AddTwoSaveAndRecallDeskTemplates();
+
+  model_wrapper_->DeleteAllEntries(
+      base::BindLambdaForTesting([&](DeskModel::DeleteEntryStatus status) {
+        EXPECT_EQ(status, DeskModel::DeleteEntryStatus::kOk);
+      }));
+
+  VerifyAllEntries(0ul,
+                   "Delete all entries after adding two desk templates and two "
+                   "save and recall desks");
+}
+
+TEST_F(DeskModelWrapperTest,
+       GetEntryCountShouldIncludeBothUserAndAdminTemplates) {
+  InitializeBridge();
+
+  // Add two user templates.
+  AddTwoTemplates();
+  // Add two save and recall desks templates.
+  AddTwoSaveAndRecallDeskTemplates();
+
+  // Set one admin template.
+  model_wrapper_->SetPolicyDeskTemplates(kPolicyWithOneTemplate);
+
+  // There should be 5 templates: 2 user templates + 1 admin template + 2 save
+  // and recall desks.
+  EXPECT_EQ(model_wrapper_->GetEntryCount(), 5ul);
+}
+
+TEST_F(DeskModelWrapperTest, GetMaxEntryCountShouldIncreaseWithAdminTemplates) {
+  InitializeBridge();
+
+  // Add two user templates.
+  AddTwoTemplates();
+
+  std::size_t max_entry_count = model_wrapper_->GetMaxDeskTemplateEntryCount();
+
+  // Set one admin template.
+  model_wrapper_->SetPolicyDeskTemplates(kPolicyWithOneTemplate);
+
+  // The max entry count should increase by 1 since we have set an admin
+  // template.
+  EXPECT_EQ(model_wrapper_->GetMaxDeskTemplateEntryCount(),
+            max_entry_count + 1ul);
+  // Sanity check to make sure that save and recall desk max count isn't
+  // affected by the admin template.
+  EXPECT_EQ(model_wrapper_->GetMaxSaveAndRecallDeskEntryCount(), 6ul);
+}
+
+TEST_F(DeskModelWrapperTest, AddDeskTemplatesAndSaveAndRecallDeskEntries) {
+  InitializeBridge();
+
+  // Add two user templates.
+  AddTwoTemplates();
+
+  // Add two SaveAndRecall desks.
+  AddTwoSaveAndRecallDeskTemplates();
+
+  EXPECT_EQ(model_wrapper_->GetEntryCount(), 4ul);
+  EXPECT_EQ(model_wrapper_->GetDeskTemplateEntryCount(), 2ul);
+  EXPECT_EQ(model_wrapper_->GetSaveAndRecallDeskEntryCount(), 2ul);
+
+  base::RunLoop loop;
+  model_wrapper_->GetAllEntries(base::BindLambdaForTesting(
+      [&](DeskModel::GetAllEntriesStatus status,
+          const std::vector<const ash::DeskTemplate*>& entries) {
+        EXPECT_EQ(status, DeskModel::GetAllEntriesStatus::kOk);
+        loop.Quit();
+      }));
+
+  loop.Run();
+  VerifyAllEntries(4ul,
+                   "Add two desks templates and two saved and recall desks");
+}
+
+TEST_F(DeskModelWrapperTest, AddSaveAndRecallDeskEntry) {
+  InitializeBridge();
+
+  model_wrapper_->AddOrUpdateEntry(
+      MakeTestDeskTemplate(1u, ash::DeskTemplateType::kSaveAndRecall),
+      base::BindOnce(&VerifyEntryAddedCorrectly));
+
+  VerifyAllEntries(1ul, "Added one save and recall desk");
+  base::RunLoop loop;
+  // Verify that it's not SaveAndRecall entry in the desk template cache.
+  model_wrapper_->GetAllEntries(base::BindLambdaForTesting(
+      [&](DeskModel::GetAllEntriesStatus status,
+          const std::vector<const ash::DeskTemplate*>& entries) {
+        EXPECT_EQ(status, DeskModel::GetAllEntriesStatus::kOk);
+        EXPECT_EQ(entries.size(), 1ul);
+        EXPECT_EQ(entries[0]->type(), ash::DeskTemplateType::kSaveAndRecall);
+        loop.Quit();
+      }));
+  EXPECT_EQ(model_wrapper_->GetDeskTemplateEntryCount(), 0ul);
+  EXPECT_EQ(model_wrapper_->GetSaveAndRecallDeskEntryCount(), 1ul);
+  loop.Run();
+}
+
+TEST_F(DeskModelWrapperTest, CanAddMaxEntriesForBothTypes) {
+  InitializeBridge();
+
+  for (std::size_t index = 0u;
+       index < model_wrapper_->GetMaxSaveAndRecallDeskEntryCount(); ++index) {
+    model_wrapper_->AddOrUpdateEntry(
+        MakeTestDeskTemplate(index, ash::DeskTemplateType::kSaveAndRecall),
+        base::BindOnce(&VerifyEntryAddedCorrectly));
+  }
+  for (std::size_t index = 0u;
+       index < model_wrapper_->GetMaxDeskTemplateEntryCount(); ++index) {
+    model_wrapper_->AddOrUpdateEntry(
+        MakeTestDeskTemplate(index, ash::DeskTemplateType::kTemplate),
+        base::BindOnce(&VerifyEntryAddedCorrectly));
+  }
+
+  VerifyAllEntries(
+      12ul, "Added max number of save and recall desks and desk templates");
+  EXPECT_EQ(model_wrapper_->GetDeskTemplateEntryCount(), 6ul);
+  EXPECT_EQ(model_wrapper_->GetSaveAndRecallDeskEntryCount(), 6ul);
+}
+
+TEST_F(DeskModelWrapperTest,
+       CanAddMaxEntriesDeskTemplatesAndStillAddEntryForSaveAndRecallDesks) {
+  InitializeBridge();
+
+  for (std::size_t index = 0u;
+       index < model_wrapper_->GetMaxDeskTemplateEntryCount(); ++index) {
+    model_wrapper_->AddOrUpdateEntry(
+        MakeTestDeskTemplate(index, ash::DeskTemplateType::kTemplate),
+        base::BindOnce(&VerifyEntryAddedCorrectly));
+  }
+  model_wrapper_->AddOrUpdateEntry(
+      MakeTestDeskTemplate(1ul, ash::DeskTemplateType::kSaveAndRecall),
+      base::BindOnce(&VerifyEntryAddedCorrectly));
+
+  VerifyAllEntries(7ul,
+                   "Added one save and recall desk after capping "
+                   "desk template entries");
+
+  EXPECT_EQ(model_wrapper_->GetDeskTemplateEntryCount(), 6ul);
+  EXPECT_EQ(model_wrapper_->GetSaveAndRecallDeskEntryCount(), 1ul);
+}
+
+TEST_F(DeskModelWrapperTest,
+       CanAddMaxEntriesForSaveAndRecallDeskAndStillAddEntryForDeskTemplate) {
+  InitializeBridge();
+
+  for (std::size_t index = 0u;
+       index < model_wrapper_->GetMaxSaveAndRecallDeskEntryCount(); ++index) {
+    model_wrapper_->AddOrUpdateEntry(
+        MakeTestDeskTemplate(index, ash::DeskTemplateType::kSaveAndRecall),
+        base::BindOnce(&VerifyEntryAddedCorrectly));
+  }
+
+  model_wrapper_->AddOrUpdateEntry(
+      MakeTestDeskTemplate(1ul, ash::DeskTemplateType::kTemplate),
+      base::BindOnce(&VerifyEntryAddedCorrectly));
+
+  VerifyAllEntries(7ul,
+                   "Added one desk template after capping "
+                   "save and recall desk entries");
+
+  EXPECT_EQ(model_wrapper_->GetDeskTemplateEntryCount(), 1ul);
+  EXPECT_EQ(model_wrapper_->GetSaveAndRecallDeskEntryCount(), 6ul);
+}
+
+}  // namespace desks_storage
diff --git a/components/desks_storage/core/desk_sync_bridge.cc b/components/desks_storage/core/desk_sync_bridge.cc
index 5c10d2c..a8d7454 100644
--- a/components/desks_storage/core/desk_sync_bridge.cc
+++ b/components/desks_storage/core/desk_sync_bridge.cc
@@ -260,8 +260,8 @@
   }
 }
 
-// Convert Sync proto WindowState `state` to chromeos::WindowStateType used by
-// the app_restore::WindowInfo struct.
+// Convert Sync proto WindowState `state` to chromeos::WindowStateType used
+// by the app_restore::WindowInfo struct.
 chromeos::WindowStateType ToChromeOsWindowState(WindowState state) {
   switch (state) {
     case WindowState::WorkspaceDeskSpecifics_WindowState_UNKNOWN_WINDOW_STATE:
diff --git a/components/history/core/browser/history_backend_unittest.cc b/components/history/core/browser/history_backend_unittest.cc
index 097ebcf1..d655b2e4 100644
--- a/components/history/core/browser/history_backend_unittest.cc
+++ b/components/history/core/browser/history_backend_unittest.cc
@@ -613,7 +613,7 @@
 
   size_t GetNumberOfMatchingSearchTerms(const int keyword_id,
                                         const std::u16string& prefix) {
-    std::vector<KeywordSearchTermVisit> matching_terms;
+    std::vector<std::unique_ptr<KeywordSearchTermVisit>> matching_terms;
     mem_backend_->db()->GetMostRecentKeywordSearchTerms(
         keyword_id, prefix, 1, &matching_terms);
     return matching_terms.size();
diff --git a/components/history/core/browser/keyword_search_term.cc b/components/history/core/browser/keyword_search_term.cc
index 71efe44..d9074ea 100644
--- a/components/history/core/browser/keyword_search_term.cc
+++ b/components/history/core/browser/keyword_search_term.cc
@@ -32,21 +32,6 @@
 
 }  // namespace
 
-KeywordSearchTermVisit::KeywordSearchTermVisit() = default;
-KeywordSearchTermVisit::KeywordSearchTermVisit(
-    const KeywordSearchTermVisit& other) = default;
-KeywordSearchTermVisit::~KeywordSearchTermVisit() = default;
-
-double KeywordSearchTermVisit::GetFrecency(base::Time now,
-                                           int recency_decay_unit_sec,
-                                           double frequency_exponent) const {
-  const double recency_sec = base::TimeDelta(now - last_visit_time).InSeconds();
-  const double recency_decayed =
-      recency_decay_unit_sec / (recency_sec + recency_decay_unit_sec);
-  const double frequency_powered = pow(visit_count, frequency_exponent);
-  return frequency_powered * recency_decayed;
-}
-
 // KeywordSearchTermVisitEnumerator --------------------------------------------
 
 std::unique_ptr<KeywordSearchTermVisit>
diff --git a/components/history/core/browser/keyword_search_term.h b/components/history/core/browser/keyword_search_term.h
index 7783082..785c72d 100644
--- a/components/history/core/browser/keyword_search_term.h
+++ b/components/history/core/browser/keyword_search_term.h
@@ -15,28 +15,15 @@
 
 namespace history {
 
-// KeywordSearchTermVisit is returned from GetMostRecentKeywordSearchTerms()
-// and contains either the search term and the normalized search term. It also
-// contains the visit count, and the last visit time for either a single keyword
-// visit or a set of keyword visits, depending on the overloaded functions it is
-// returned from.
+// Represents one or more visits to a keyword search term. It contains the
+// search term and the normalized search term in addition to the visit count and
+// the last visit time. An optional frecency score may be provided by the
+// utility functions/helpers in keyword_search_term_util.h where applicable.
 struct KeywordSearchTermVisit {
-  KeywordSearchTermVisit();
-  KeywordSearchTermVisit(const KeywordSearchTermVisit& other);
-  ~KeywordSearchTermVisit();
-
-  // Returns the frecency score of the visit based on the following formula:
-  //            (frequency ^ frequency_exponent) * recency_decay_unit_in_seconds
-  // frecency = ————————————————————————————————————————————————————————————————
-  //                   recency_in_seconds + recency_decay_unit_in_seconds
-  // This score combines frequency and recency of the visit favoring ones that
-  // are more frequent and more recent (see go/local-zps-frecency-ranking).
-  // `recency_decay_unit_sec` is the number of seconds until the recency
-  // component of the score decays to half. `frequency_exponent` is factor by
-  // which the frequency of the visit is exponentiated.
-  double GetFrecency(base::Time now,
-                     int recency_decay_unit_sec,
-                     double frequency_exponent) const;
+  KeywordSearchTermVisit() = default;
+  KeywordSearchTermVisit(const KeywordSearchTermVisit&) = delete;
+  KeywordSearchTermVisit& operator=(const KeywordSearchTermVisit&) = delete;
+  ~KeywordSearchTermVisit() = default;
 
   std::u16string term;             // The search term that was used.
   std::u16string normalized_term;  // The search term, in lower case and with
diff --git a/components/history/core/browser/keyword_search_term_util.cc b/components/history/core/browser/keyword_search_term_util.cc
index 7a28198b..b06155ec 100644
--- a/components/history/core/browser/keyword_search_term_util.cc
+++ b/components/history/core/browser/keyword_search_term_util.cc
@@ -33,6 +33,16 @@
   return search_term.normalized_term == other_search_term.normalized_term;
 }
 
+// Return whether a visit to a search term is a duplicative visit, i.e., a visit
+// to the same search term in an interval smaller than
+// kAutocompleteDuplicateVisitIntervalThreshold.
+bool IsDuplicateVisit(const KeywordSearchTermVisit& search_term,
+                      const KeywordSearchTermVisit& other_search_term) {
+  return IsSameSearchTerm(search_term, other_search_term) &&
+         (search_term.last_visit_time - other_search_term.last_visit_time <=
+          kAutocompleteDuplicateVisitIntervalThreshold);
+}
+
 // Transforms a visit time to its timeslot, i.e., day of the viist.
 base::Time VisitTimeToTimeslot(base::Time visit_time) {
   return visit_time.LocalMidnight();
@@ -47,6 +57,111 @@
 
 }  // namespace
 
+const base::TimeDelta kAutocompleteDuplicateVisitIntervalThreshold =
+    base::Minutes(5);
+
+// Returns the frecency score of the visit based on the following formula:
+//            (frequency ^ kFrequencyExponent) * kRecencyDecayUnitSec
+// frecency = ————————————————————————————————————————————————————————————————
+//                   recency_in_seconds + kRecencyDecayUnitSec
+double GetFrecencyScore(int visit_count,
+                        base::Time visit_time,
+                        base::Time now) {
+  // The number of seconds until the recency component decays by half.
+  constexpr base::TimeDelta kRecencyDecayUnitSec = base::Seconds(60);
+  // The factor by which the frequency component is exponentiated.
+  constexpr double kFrequencyExponent = 1.15;
+
+  const double recency_decayed =
+      kRecencyDecayUnitSec /
+      (base::TimeDelta(now - visit_time) + kRecencyDecayUnitSec);
+  const double frequency_powered = pow(visit_count, kFrequencyExponent);
+  return frequency_powered * recency_decayed;
+}
+
+// SearchTermHelper ------------------------------------------------------------
+
+// A helper class to return keyword search terms with visit counts accumulated
+// across visits for use as prefix or zero-prefix suggestions in the omnibox.
+class SearchTermHelper {
+ public:
+  SearchTermHelper() = default;
+
+  SearchTermHelper(const SearchTermHelper&) = delete;
+  SearchTermHelper& operator=(const SearchTermHelper&) = delete;
+
+  ~SearchTermHelper() = default;
+
+  // |enumerator| enumerates keyword search term visits from the URLDatabase.
+  // |ignore_duplicate_visits| specifies whether duplicative visits to a search
+  // term should be ignored.
+  std::unique_ptr<KeywordSearchTermVisit> GetNextSearchTermFromEnumerator(
+      KeywordSearchTermVisitEnumerator& enumerator,
+      bool ignore_duplicate_visits) {
+    // |next_search_term| acts as the fast pointer and |last_search_term_| acts
+    // as the slow pointer accumulating the search term visit count across
+    // visits.
+    while (auto next_search_term = enumerator.GetNextVisit()) {
+      if (ignore_duplicate_visits && last_search_term_ &&
+          IsDuplicateVisit(*next_search_term, *last_search_term_)) {
+        continue;
+      }
+
+      if (last_search_term_ &&
+          IsSameSearchTerm(*next_search_term, *last_search_term_)) {
+        // We encountered the same search term:
+        // 1. Move |last_search_term_| forward.
+        // 2. Add up the search term visit count.
+        int visit_count = last_search_term_->visit_count;
+        last_search_term_ = std::move(next_search_term);
+        last_search_term_->visit_count += visit_count;
+      } else if (last_search_term_) {
+        // We encountered a new search term and |last_search_term_| has a value:
+        // 1. Move |last_search_term_| forward.
+        // 2. Return the old |last_search_term_|.
+        auto search_term_to_return = std::move(last_search_term_);
+        last_search_term_ = std::move(next_search_term);
+        return search_term_to_return;
+      } else {
+        // We encountered a new search term and |last_search_term_| has no
+        // value:
+        // 1. Move |last_search_term_| forward.
+        last_search_term_ = std::move(next_search_term);
+      }
+    }
+
+    return last_search_term_ ? std::move(last_search_term_) : nullptr;
+  }
+
+ private:
+  // The last seen search term.
+  std::unique_ptr<KeywordSearchTermVisit> last_search_term_;
+};
+
+void GetAutocompleteSearchTermsFromEnumerator(
+    KeywordSearchTermVisitEnumerator& enumerator,
+    bool ignore_duplicate_visits,
+    SearchTermRankingPolicy ranking_policy,
+    std::vector<std::unique_ptr<KeywordSearchTermVisit>>* search_terms) {
+  SearchTermHelper helper;
+  const base::Time now = base::Time::Now();
+  while (auto search_term = helper.GetNextSearchTermFromEnumerator(
+             enumerator, ignore_duplicate_visits)) {
+    if (ranking_policy == SearchTermRankingPolicy::kFrecency) {
+      search_term->score = GetFrecencyScore(search_term->visit_count,
+                                            search_term->last_visit_time, now);
+    }
+    search_terms->push_back(std::move(search_term));
+  }
+  // Order the search terms by descending recency or frecency.
+  std::stable_sort(search_terms->begin(), search_terms->end(),
+                   [&](const auto& a, const auto& b) {
+                     return ranking_policy == SearchTermRankingPolicy::kFrecency
+                                ? a->score > b->score
+                                : a->last_visit_time > b->last_visit_time;
+                   });
+}
+
 // MostRepeatedSearchTermHelper ------------------------------------------------
 
 // A helper class to return keyword search terms with frecency scores
@@ -134,6 +249,7 @@
     return last_search_term_ ? std::move(last_search_term_) : nullptr;
   }
 
+ private:
   // The last seen search term.
   std::unique_ptr<KeywordSearchTermVisit> last_search_term_;
 };
diff --git a/components/history/core/browser/keyword_search_term_util.h b/components/history/core/browser/keyword_search_term_util.h
index 8c3d5c0..a5df027 100644
--- a/components/history/core/browser/keyword_search_term_util.h
+++ b/components/history/core/browser/keyword_search_term_util.h
@@ -8,11 +8,50 @@
 #include <memory>
 #include <vector>
 
+namespace base {
+class Time;
+class TimeDelta;
+}  // namespace base
+
 namespace history {
 
 class KeywordSearchTermVisitEnumerator;
 struct KeywordSearchTermVisit;
 
+enum class SearchTermRankingPolicy {
+  kRecency,  // From the most recent to the least recent.
+  kFrecency  // By descending frecency score calculated by |GetFrecencyScore|.
+};
+
+// The time interval within which a duplicate query is considered invalid for
+// autocomplete purposes.
+// These invalid duplicates are extracted from search query URLs which are
+// identical or nearly identical to the original search query URL and issued too
+// closely to it, i.e., within this time interval. They are typically recorded
+// as a result of back/forward navigations or user interactions in the search
+// result page and are likely not newly initiated searches.
+extern const base::TimeDelta kAutocompleteDuplicateVisitIntervalThreshold;
+
+// Returns a score combining frequency and recency of the visit favoring ones
+// that are more frequent and more recent (see go/local-zps-frecency-ranking).
+double GetFrecencyScore(int visit_count, base::Time visit_time, base::Time now);
+
+// Returns keyword search terms ordered by descending recency or frecency scores
+// for use as prefix or zero-prefix suggestions in the omnibox respectively.
+// |enumerator| enumerates keyword search term visits from the URLDatabase. It
+// must return visits ordered first by |normalized_term| and then by
+// |last_visit_time| in ascending order, i.e., from the oldest to the newest.
+// |ignore_duplicate_visits| specifies whether duplicative visits to a search
+// term should be ignored. A duplicative visit is defined as a visit to the
+// same search term in an interval smaller than
+// kAutocompleteDuplicateVisitIntervalThreshold. |ranking_policy| specifies
+// how the returned keyword search terms should be ordered.
+void GetAutocompleteSearchTermsFromEnumerator(
+    KeywordSearchTermVisitEnumerator& enumerator,
+    bool ignore_duplicate_visits,
+    SearchTermRankingPolicy ranking_policy,
+    std::vector<std::unique_ptr<KeywordSearchTermVisit>>* search_terms);
+
 // Returns keyword search terms ordered by descending frecency scores
 // accumulated across days for use in the Most Visited tiles. |enumerator|
 // enumerates keyword search term visits from the URLDatabase. It must return
diff --git a/components/history/core/browser/url_database.cc b/components/history/core/browser/url_database.cc
index 6ddb7fa..43702f8 100644
--- a/components/history/core/browser/url_database.cc
+++ b/components/history/core/browser/url_database.cc
@@ -15,6 +15,7 @@
 #include "base/time/time.h"
 #include "components/database_utils/url_converter.h"
 #include "components/history/core/browser/keyword_search_term.h"
+#include "components/history/core/browser/keyword_search_term_util.h"
 #include "components/url_formatter/url_formatter.h"
 #include "sql/statement.h"
 #include "url/gurl.h"
@@ -577,7 +578,7 @@
     KeywordID keyword_id,
     const std::u16string& prefix,
     int max_count,
-    std::vector<KeywordSearchTermVisit>* visits) {
+    std::vector<std::unique_ptr<KeywordSearchTermVisit>>* visits) {
   // NOTE: the keyword_id can be zero if on first run the user does a query
   // before the TemplateURLService has finished loading. As the chances of this
   // occurring are small, we ignore it.
@@ -607,21 +608,61 @@
   statement.BindString16(2, next_prefix);
   statement.BindInt(3, max_count);
 
-  KeywordSearchTermVisit visit;
   while (statement.Step()) {
-    visit.term = statement.ColumnString16(0);
-    visit.normalized_term = statement.ColumnString16(1);
-    visit.visit_count = statement.ColumnInt(2);
-    visit.last_visit_time =
+    auto visit = std::make_unique<KeywordSearchTermVisit>();
+    visit->term = statement.ColumnString16(0);
+    visit->normalized_term = statement.ColumnString16(1);
+    visit->visit_count = statement.ColumnInt(2);
+    visit->last_visit_time =
         base::Time::FromInternalValue(statement.ColumnInt64(3));
-    visits->push_back(visit);
+    visits->push_back(std::move(visit));
   }
 }
 
+std::unique_ptr<KeywordSearchTermVisitEnumerator>
+URLDatabase::CreateKeywordSearchTermVisitEnumerator(
+    KeywordID keyword_id,
+    const std::u16string& prefix) {
+  // NOTE: the keyword_id can be zero if on first run the user does a query
+  // before the TemplateURLService has finished loading. As the chances of this
+  // occurring are small, we ignore it.
+  if (!keyword_id)
+    return nullptr;
+
+  auto enumerator = base::WrapUnique<KeywordSearchTermVisitEnumerator>(
+      new KeywordSearchTermVisitEnumerator());
+  enumerator->statement_.Assign(GetDB().GetCachedStatement(SQL_FROM_HERE,
+                                                           R"(
+      SELECT
+        kst.term,
+        kst.normalized_term,
+        u.visit_count,
+        u.last_visit_time
+      FROM
+        keyword_search_terms kst JOIN urls u ON kst.url_id = u.id
+      WHERE
+        kst.keyword_id = ? AND
+        kst.normalized_term >= ? AND
+        kst.normalized_term < ?
+      ORDER BY kst.normalized_term, u.last_visit_time
+      )"));
+  // Keep CollapseWhitespace() and ToLower() in sync with search_provider.cc.
+  std::u16string normalized_prefix =
+      base::CollapseWhitespace(base::i18n::ToLower(prefix), false);
+  // This magic gives us a prefix search.
+  std::u16string next_prefix = normalized_prefix;
+  next_prefix.back() = next_prefix.back() + 1;
+  enumerator->statement_.BindInt64(0, keyword_id);
+  enumerator->statement_.BindString16(1, normalized_prefix);
+  enumerator->statement_.BindString16(2, next_prefix);
+  enumerator->initialized_ = enumerator->statement_.is_valid();
+  return enumerator;
+}
+
 void URLDatabase::GetMostRecentKeywordSearchTerms(
     KeywordID keyword_id,
     base::Time age_threshold,
-    std::vector<KeywordSearchTermVisit>* visits) {
+    std::vector<std::unique_ptr<KeywordSearchTermVisit>>* visits) {
   // NOTE: the keyword_id can be zero if on first run the user does a query
   // before the TemplateURLService has finished loading. As the chances of this
   // occurring are small, we ignore it.
@@ -672,13 +713,13 @@
   statement.BindInt64(2, age_threshold.ToInternalValue());
 
   while (statement.Step()) {
-    KeywordSearchTermVisit visit;
-    visit.normalized_term = statement.ColumnString16(0);
-    visit.term = statement.ColumnString16(1);
-    visit.visit_count = statement.ColumnInt(2);
-    visit.last_visit_time =
+    auto visit = std::make_unique<KeywordSearchTermVisit>();
+    visit->normalized_term = statement.ColumnString16(0);
+    visit->term = statement.ColumnString16(1);
+    visit->visit_count = statement.ColumnInt(2);
+    visit->last_visit_time =
         base::Time::FromInternalValue(statement.ColumnInt64(3));
-    visits->push_back(visit);
+    visits->push_back(std::move(visit));
   }
 }
 
@@ -810,9 +851,6 @@
 const int kLowQualityMatchVisitLimit = 4;
 const int kLowQualityMatchAgeLimitInDays = 3;
 
-const base::TimeDelta kAutocompleteDuplicateVisitIntervalThreshold =
-    base::Minutes(5);
-
 base::Time AutocompleteAgeThreshold() {
   return (base::Time::Now() - base::Days(kLowQualityMatchAgeLimitInDays));
 }
diff --git a/components/history/core/browser/url_database.h b/components/history/core/browser/url_database.h
index efc1878..e4e5c86 100644
--- a/components/history/core/browser/url_database.h
+++ b/components/history/core/browser/url_database.h
@@ -219,18 +219,30 @@
 
   // Returns up to max_count of the most recent search terms for the specified
   // keyword.
+  // TODO(crbug.com/1119654): Remove this in favor of the enumerator-based
+  // function below after experimentation.
   void GetMostRecentKeywordSearchTerms(
       KeywordID keyword_id,
       const std::u16string& prefix,
       int max_count,
-      std::vector<KeywordSearchTermVisit>* visits);
+      std::vector<std::unique_ptr<KeywordSearchTermVisit>>* visits);
+
+  // Returns an enumerator to enumerate all the KeywordSearchTermVisits starting
+  // with `prefix` for the specified keyword. The visits are ordered first by
+  // |normalized_term| and then by |last_visit_time| in ascending order, i.e.,
+  // from the oldest to the newest.
+  std::unique_ptr<KeywordSearchTermVisitEnumerator>
+  CreateKeywordSearchTermVisitEnumerator(KeywordID keyword_id,
+                                         const std::u16string& prefix);
 
   // Returns the most recent (no older than `age_threshold`) search terms for
   // the specified keyword.
+  // TODO(crbug.com/1119654): Remove this in favor of the enumerator-based
+  // function below after experimentation.
   void GetMostRecentKeywordSearchTerms(
       KeywordID keyword_id,
       base::Time age_threshold,
-      std::vector<KeywordSearchTermVisit>* visits);
+      std::vector<std::unique_ptr<KeywordSearchTermVisit>>* visits);
 
   // Returns an enumerator to enumerate all the KeywordSearchTermVisits no older
   // than `age_threshold` for the given keyword. The visits are ordered first by
@@ -341,15 +353,6 @@
 extern const int kLowQualityMatchVisitLimit;
 extern const int kLowQualityMatchAgeLimitInDays;
 
-// The time interval within which a duplicate query is considered invalid for
-// autocomplete purposes.
-// These invalid duplicates are extracted from search query URLs which are
-// identical or nearly identical to the original search query URL and issued too
-// closely to it, i.e., within this time interval. They are typically recorded
-// as a result of back/forward navigations or user interactions in the search
-// result page and are likely not newly initiated searches.
-extern const base::TimeDelta kAutocompleteDuplicateVisitIntervalThreshold;
-
 // Returns the date threshold for considering an history item as significant.
 base::Time AutocompleteAgeThreshold();
 
diff --git a/components/history/core/browser/url_database_unittest.cc b/components/history/core/browser/url_database_unittest.cc
index 0c8f246..2bdb5c2 100644
--- a/components/history/core/browser/url_database_unittest.cc
+++ b/components/history/core/browser/url_database_unittest.cc
@@ -169,61 +169,251 @@
   // EXPECT_TRUE(db.GetURLInfo(url2, NULL) == NULL);
 }
 
-// Tests adding, querying and deleting keyword visits.
-TEST_F(URLDatabaseTest, KeywordSearchTermVisit) {
-  URLRow url_info1(GURL("http://www.google.com/"));
-  url_info1.set_title(u"Google");
-  url_info1.set_visit_count(4);
-  url_info1.set_typed_count(2);
-  url_info1.set_last_visit(Time::Now() - base::Days(1));
-  url_info1.set_hidden(false);
-  URLID url_id = AddURL(url_info1);
-  ASSERT_NE(0, url_id);
-
-  // Add a keyword visit.
+// Tests querying prefix keyword search terms.
+TEST_F(URLDatabaseTest, KeywordSearchTerms_Prefix) {
   KeywordID keyword_id = 100;
-  std::u16string keyword = u" VISIT ";
-  std::u16string normalized_keyword = u"visit";
-  ASSERT_TRUE(SetKeywordSearchTermsForURL(url_id, keyword_id, keyword));
+  // Choose the local midnight of yesterday as the baseline for the time.
+  base::Time local_midnight = Time::Now().LocalMidnight() - base::Days(1);
 
-  // Make sure we get it back.
-  std::vector<KeywordSearchTermVisit> matches;
-  GetMostRecentKeywordSearchTerms(keyword_id, u"vi", 10, &matches);
-  ASSERT_EQ(1U, matches.size());
-  ASSERT_EQ(keyword, matches[0].term);
-  ASSERT_EQ(normalized_keyword, matches[0].normalized_term);
+  // First search for "foo".
+  URLRow foo_url_1(GURL("https://www.google.com/search?q=Foo&num=1"));
+  foo_url_1.set_visit_count(1);
+  foo_url_1.set_last_visit(local_midnight + base::Hours(1));
+  URLID foo_url_1_id = AddURL(foo_url_1);
+  ASSERT_NE(0, foo_url_1_id);
+  ASSERT_TRUE(SetKeywordSearchTermsForURL(foo_url_1_id, keyword_id, u"Foo"));
 
-  std::vector<KeywordSearchTermVisit> zero_prefix_matches;
-  GetMostRecentKeywordSearchTerms(
-      keyword_id, history::AutocompleteAgeThreshold(), &zero_prefix_matches);
-  ASSERT_EQ(1U, zero_prefix_matches.size());
-  ASSERT_EQ(keyword, zero_prefix_matches[0].term);
-  ASSERT_EQ(normalized_keyword, zero_prefix_matches[0].normalized_term);
+  // Second search for "foo".
+  URLRow foo_url_2(GURL("https://www.google.com/search?q=FOo&num=2"));
+  foo_url_2.set_visit_count(1);
+  foo_url_2.set_last_visit(local_midnight + base::Hours(2));
+  URLID foo_url_2_id = AddURL(foo_url_2);
+  ASSERT_NE(0, foo_url_2_id);
+  ASSERT_TRUE(SetKeywordSearchTermsForURL(foo_url_2_id, keyword_id, u"FOo"));
+
+  // Third search for "foo".
+  URLRow foo_url_3(GURL("https://www.google.com/search?q=FOO&num=3"));
+  foo_url_3.set_visit_count(1);
+  foo_url_3.set_last_visit(local_midnight + base::Hours(3));
+  URLID foo_url_3_id = AddURL(foo_url_3);
+  ASSERT_NE(0, foo_url_3_id);
+  ASSERT_TRUE(SetKeywordSearchTermsForURL(foo_url_3_id, keyword_id, u"FOO"));
+
+  // First search for "bar".
+  URLRow bar_url_1(GURL("https://www.google.com/search?q=BAR&num=4"));
+  bar_url_1.set_visit_count(1);
+  bar_url_1.set_last_visit(local_midnight + base::Hours(4));
+  URLID bar_url_1_id = AddURL(bar_url_1);
+  ASSERT_NE(0, bar_url_1_id);
+  ASSERT_TRUE(SetKeywordSearchTermsForURL(bar_url_1_id, keyword_id, u"BAR"));
+
+  // First search for "food".
+  URLRow food_url_1(GURL("https://www.google.com/search?q=Food&num=1"));
+  food_url_1.set_visit_count(1);
+  food_url_1.set_last_visit(local_midnight + base::Hours(5));
+  URLID food_url_1_id = AddURL(food_url_1);
+  ASSERT_NE(0, food_url_1_id);
+  ASSERT_TRUE(SetKeywordSearchTermsForURL(food_url_1_id, keyword_id, u"Food"));
+
+  // Make sure we get "food" and "foo" back with the last term and visit time
+  // that generated the normalized search terms.
+  // In fact we get near-duplicate matches if different search terms generated
+  // the same normalized search term.
+  std::vector<std::unique_ptr<KeywordSearchTermVisit>> matches;
+  GetMostRecentKeywordSearchTerms(keyword_id, u"f", 10, &matches);
+  ASSERT_EQ(4U, matches.size());
+  EXPECT_EQ(u"Food", matches[0]->term);
+  EXPECT_EQ(u"food", matches[0]->normalized_term);
+  EXPECT_EQ(1, matches[0]->visit_count);
+  EXPECT_EQ(local_midnight + base::Hours(5), matches[0]->last_visit_time);
+  EXPECT_EQ(u"FOO", matches[1]->term);
+  EXPECT_EQ(u"foo", matches[1]->normalized_term);
+  EXPECT_EQ(1, matches[1]->visit_count);
+  EXPECT_EQ(local_midnight + base::Hours(3), matches[1]->last_visit_time);
+  EXPECT_EQ(u"FOo", matches[2]->term);
+  EXPECT_EQ(u"foo", matches[2]->normalized_term);
+  EXPECT_EQ(1, matches[2]->visit_count);
+  EXPECT_EQ(local_midnight + base::Hours(2), matches[2]->last_visit_time);
+  EXPECT_EQ(u"Foo", matches[3]->term);
+  EXPECT_EQ(u"foo", matches[3]->normalized_term);
+  EXPECT_EQ(1, matches[3]->visit_count);
+  EXPECT_EQ(local_midnight + base::Hours(1), matches[3]->last_visit_time);
+
+  // We could miss out on potential visits and/or matches if not requesting
+  // enough matches.
+  matches.clear();
+  GetMostRecentKeywordSearchTerms(keyword_id, u"f", 2, &matches);
+  ASSERT_EQ(2U, matches.size());
+  EXPECT_EQ(u"Food", matches[0]->term);
+  EXPECT_EQ(u"food", matches[0]->normalized_term);
+  EXPECT_EQ(1, matches[0]->visit_count);
+  EXPECT_EQ(local_midnight + base::Hours(5), matches[0]->last_visit_time);
+  EXPECT_EQ(u"FOO", matches[1]->term);
+  EXPECT_EQ(u"foo", matches[1]->normalized_term);
+  EXPECT_EQ(1, matches[1]->visit_count);
+  EXPECT_EQ(local_midnight + base::Hours(3), matches[1]->last_visit_time);
+
+  // CreateKeywordSearchTermVisitEnumerator solves that problem by accumulating
+  // the visits to unique normalized search terms.
+  auto enumerator = CreateKeywordSearchTermVisitEnumerator(keyword_id, u"f");
+  ASSERT_TRUE(enumerator);
+  std::vector<std::unique_ptr<KeywordSearchTermVisit>> matches_v2;
+  GetAutocompleteSearchTermsFromEnumerator(
+      *enumerator, /*ignore_duplicate_visits=*/false,
+      SearchTermRankingPolicy::kRecency, &matches_v2);
+  ASSERT_EQ(2U, matches_v2.size());
+  EXPECT_EQ(u"Food", matches_v2[0]->term);
+  EXPECT_EQ(u"food", matches_v2[0]->normalized_term);
+  EXPECT_EQ(1, matches_v2[0]->visit_count);
+  EXPECT_EQ(local_midnight + base::Hours(5), matches_v2[0]->last_visit_time);
+  EXPECT_EQ(u"FOO", matches_v2[1]->term);
+  EXPECT_EQ(u"foo", matches_v2[1]->normalized_term);
+  EXPECT_EQ(3, matches_v2[1]->visit_count);
+  EXPECT_EQ(local_midnight + base::Hours(3), matches_v2[1]->last_visit_time);
 
   KeywordSearchTermRow keyword_search_term_row;
-  ASSERT_TRUE(GetKeywordSearchTermRow(url_id, &keyword_search_term_row));
+  ASSERT_TRUE(GetKeywordSearchTermRow(foo_url_3_id, &keyword_search_term_row));
   EXPECT_EQ(keyword_id, keyword_search_term_row.keyword_id);
-  EXPECT_EQ(url_id, keyword_search_term_row.url_id);
-  EXPECT_EQ(keyword, keyword_search_term_row.term);
+  EXPECT_EQ(foo_url_3_id, keyword_search_term_row.url_id);
+  EXPECT_EQ(u"FOO", keyword_search_term_row.term);
+  ASSERT_TRUE(GetKeywordSearchTermRow(food_url_1_id, &keyword_search_term_row));
+  EXPECT_EQ(keyword_id, keyword_search_term_row.keyword_id);
+  EXPECT_EQ(food_url_1_id, keyword_search_term_row.url_id);
+  EXPECT_EQ(u"Food", keyword_search_term_row.term);
 
-  // Delete the keyword visit.
+  // Delete all the search terms for the keyword.
   DeleteAllSearchTermsForKeyword(keyword_id);
 
-  // Make sure we don't get it back when querying.
+  // Make sure we get nothing back.
   matches.clear();
-  GetMostRecentKeywordSearchTerms(keyword_id, keyword, 10, &matches);
+  GetMostRecentKeywordSearchTerms(keyword_id, u"f", 10, &matches);
   ASSERT_EQ(0U, matches.size());
 
-  zero_prefix_matches.clear();
-  GetMostRecentKeywordSearchTerms(
-      keyword_id, history::AutocompleteAgeThreshold(), &zero_prefix_matches);
-  ASSERT_EQ(0U, zero_prefix_matches.size());
+  enumerator = CreateKeywordSearchTermVisitEnumerator(keyword_id, u"f");
+  ASSERT_TRUE(enumerator);
+  matches_v2.clear();
+  GetAutocompleteSearchTermsFromEnumerator(
+      *enumerator, /*ignore_duplicate_visits=*/false,
+      SearchTermRankingPolicy::kRecency, &matches_v2);
+  ASSERT_EQ(0U, matches_v2.size());
 
-  ASSERT_FALSE(GetKeywordSearchTermRow(url_id, &keyword_search_term_row));
+  ASSERT_FALSE(GetKeywordSearchTermRow(foo_url_3_id, &keyword_search_term_row));
 }
 
-// Tests querying most repeated search terms.
-TEST_F(URLDatabaseTest, MostRepeatedSearchTerms) {
+// Tests querying zero-prefix keyword search terms.
+TEST_F(URLDatabaseTest, KeywordSearchTerms_ZeroPrefix) {
+  KeywordID keyword_id = 100;
+  // Choose the local midnight of yesterday as the baseline for the time.
+  base::Time local_midnight = Time::Now().LocalMidnight() - base::Days(1);
+
+  // First search for "foo".
+  URLRow foo_url_1(GURL("https://www.google.com/search?q=Foo&num=1"));
+  foo_url_1.set_visit_count(1);
+  foo_url_1.set_last_visit(local_midnight + base::Hours(1));
+  URLID foo_url_1_id = AddURL(foo_url_1);
+  ASSERT_NE(0, foo_url_1_id);
+  ASSERT_TRUE(SetKeywordSearchTermsForURL(foo_url_1_id, keyword_id, u"Foo"));
+
+  // Second search for "foo".
+  URLRow foo_url_2(GURL("https://www.google.com/search?q=FOo&num=2"));
+  foo_url_2.set_visit_count(1);
+  foo_url_2.set_last_visit(local_midnight + base::Hours(2));
+  URLID foo_url_2_id = AddURL(foo_url_2);
+  ASSERT_NE(0, foo_url_2_id);
+  ASSERT_TRUE(SetKeywordSearchTermsForURL(foo_url_2_id, keyword_id, u"FOo"));
+
+  // Third search for "foo".
+  URLRow foo_url_3(GURL("https://www.google.com/search?q=FOO&num=3"));
+  foo_url_3.set_visit_count(1);
+  foo_url_3.set_last_visit(local_midnight + base::Hours(3));
+  URLID foo_url_3_id = AddURL(foo_url_3);
+  ASSERT_NE(0, foo_url_3_id);
+  ASSERT_TRUE(SetKeywordSearchTermsForURL(foo_url_3_id, keyword_id, u"FOO"));
+
+  // First search for "bar".
+  URLRow bar_url_1(GURL("https://www.google.com/search?q=BAR&num=4"));
+  bar_url_1.set_visit_count(1);
+  bar_url_1.set_last_visit(local_midnight + base::Hours(4));
+  URLID bar_url_1_id = AddURL(bar_url_1);
+  ASSERT_NE(0, bar_url_1_id);
+  ASSERT_TRUE(SetKeywordSearchTermsForURL(bar_url_1_id, keyword_id, u"BAR"));
+
+  // Fourth search for "foo".
+  // This search will be ignored for being too close to previous search.
+  URLRow foo_url_4(GURL("https://www.google.com/search?q=foo&num=4"));
+  foo_url_4.set_visit_count(1);
+  foo_url_4.set_last_visit(local_midnight + base::Hours(3));
+  URLID foo_url_4_id = AddURL(foo_url_4);
+  ASSERT_NE(0, foo_url_4_id);
+  ASSERT_TRUE(SetKeywordSearchTermsForURL(foo_url_4_id, keyword_id, u"foo"));
+
+  // Make sure we get both "foo" and "bar" back. "bar" should come first since
+  // it was searched for most recently.
+  std::vector<std::unique_ptr<KeywordSearchTermVisit>> matches;
+  GetMostRecentKeywordSearchTerms(
+      keyword_id, history::AutocompleteAgeThreshold(), &matches);
+  ASSERT_EQ(2U, matches.size());
+  EXPECT_EQ(u"BAR", matches[0]->term);
+  EXPECT_EQ(u"bar", matches[0]->normalized_term);
+  EXPECT_EQ(1, matches[0]->visit_count);
+  EXPECT_EQ(local_midnight + base::Hours(4), matches[0]->last_visit_time);
+  EXPECT_EQ(u"Foo", matches[1]->term);
+  EXPECT_EQ(u"foo", matches[1]->normalized_term);
+  EXPECT_EQ(3, matches[1]->visit_count);
+  EXPECT_EQ(local_midnight + base::Hours(3), matches[1]->last_visit_time);
+
+  // Make sure we get both "foo" and "bar" back. "foo" should come first since
+  // it has more visits and thus a higher frecency score.
+  auto enumerator = CreateKeywordSearchTermVisitEnumerator(
+      keyword_id, history::AutocompleteAgeThreshold());
+  ASSERT_TRUE(enumerator);
+  std::vector<std::unique_ptr<KeywordSearchTermVisit>> matches_v2;
+  GetAutocompleteSearchTermsFromEnumerator(
+      *enumerator, /*ignore_duplicate_visits=*/true,
+      SearchTermRankingPolicy::kFrecency, &matches_v2);
+  ASSERT_EQ(2U, matches_v2.size());
+  EXPECT_EQ(u"FOO", matches_v2[0]->term);
+  EXPECT_EQ(u"foo", matches_v2[0]->normalized_term);
+  EXPECT_EQ(3, matches_v2[0]->visit_count);
+  EXPECT_EQ(local_midnight + base::Hours(3), matches_v2[0]->last_visit_time);
+  EXPECT_EQ(u"BAR", matches_v2[1]->term);
+  EXPECT_EQ(u"bar", matches_v2[1]->normalized_term);
+  EXPECT_EQ(1, matches_v2[1]->visit_count);
+  EXPECT_EQ(local_midnight + base::Hours(4), matches_v2[1]->last_visit_time);
+
+  KeywordSearchTermRow keyword_search_term_row;
+  ASSERT_TRUE(GetKeywordSearchTermRow(foo_url_3_id, &keyword_search_term_row));
+  EXPECT_EQ(keyword_id, keyword_search_term_row.keyword_id);
+  EXPECT_EQ(foo_url_3_id, keyword_search_term_row.url_id);
+  EXPECT_EQ(u"FOO", keyword_search_term_row.term);
+  ASSERT_TRUE(GetKeywordSearchTermRow(bar_url_1_id, &keyword_search_term_row));
+  EXPECT_EQ(keyword_id, keyword_search_term_row.keyword_id);
+  EXPECT_EQ(bar_url_1_id, keyword_search_term_row.url_id);
+  EXPECT_EQ(u"BAR", keyword_search_term_row.term);
+
+  // Delete all the search terms for the keyword.
+  DeleteAllSearchTermsForKeyword(keyword_id);
+
+  // Make sure we get nothing back.
+  matches.clear();
+  GetMostRecentKeywordSearchTerms(
+      keyword_id, history::AutocompleteAgeThreshold(), &matches);
+  ASSERT_EQ(0U, matches.size());
+
+  enumerator = CreateKeywordSearchTermVisitEnumerator(
+      keyword_id, history::AutocompleteAgeThreshold());
+  ASSERT_TRUE(enumerator);
+  matches_v2.clear();
+  GetAutocompleteSearchTermsFromEnumerator(
+      *enumerator, /*ignore_duplicate_visits=*/true,
+      SearchTermRankingPolicy::kFrecency, &matches_v2);
+  ASSERT_EQ(0U, matches_v2.size());
+
+  ASSERT_FALSE(GetKeywordSearchTermRow(foo_url_3_id, &keyword_search_term_row));
+}
+
+// Tests querying most repeated keyword search terms.
+TEST_F(URLDatabaseTest, KeywordSearchTerms_MostRepeated) {
   KeywordID keyword_id = 100;
   // Choose the local midnight of yesterday as the baseline for the time.
   base::Time local_midnight = Time::Now().LocalMidnight() - base::Days(1);
@@ -345,7 +535,7 @@
   ASSERT_TRUE(DeleteURLRow(url_id));
 
   // Make sure the keyword visit was deleted.
-  std::vector<KeywordSearchTermVisit> matches;
+  std::vector<std::unique_ptr<KeywordSearchTermVisit>> matches;
   GetMostRecentKeywordSearchTerms(1, u"visit", 10, &matches);
   ASSERT_EQ(0U, matches.size());
 }
diff --git a/components/metrics/metrics_pref_names.cc b/components/metrics/metrics_pref_names.cc
index 7af8c57..d7be87e 100644
--- a/components/metrics/metrics_pref_names.cc
+++ b/components/metrics/metrics_pref_names.cc
@@ -70,6 +70,21 @@
 // client id and low entropy source should be reset.
 const char kMetricsResetIds[] = "user_experience_metrics.reset_metrics_ids";
 
+#if BUILDFLAG(IS_ANDROID)
+// Boolean that determines whether to use the new sampling trial
+// "PostFREFixMetricsAndCrashSampling" and feature "PostFREFixMetricsReporting"
+// to control sampling on Android Chrome. This is set to true when disabling
+// metrics reporting, or on start up if metrics reporting is not consented to
+// (including new users going through their first run). As a result, all new UMA
+// users should have this pref set to true.
+// Note: This exists due to a bug in which the old sampling rate was not being
+// applied correctly. In order for the fix to not affect the overall sampling
+// rate, this pref controls what trial/feature to use to determine whether the
+// client is sampled. See crbug/1306481.
+const char kUsePostFREFixSamplingTrial[] =
+    "user_experience_metrics.use_post_fre_fix_sampling_trial";
+#endif  // BUILDFLAG(IS_ANDROID)
+
 // Boolean that specifies whether or not crash reporting and metrics reporting
 // are sent over the network for analysis.
 const char kMetricsReportingEnabled[] =
diff --git a/components/metrics/metrics_pref_names.h b/components/metrics/metrics_pref_names.h
index 48bb86a..20eb93c4 100644
--- a/components/metrics/metrics_pref_names.h
+++ b/components/metrics/metrics_pref_names.h
@@ -25,6 +25,9 @@
 extern const char kMetricsOngoingLogs[];
 extern const char kMetricsOngoingLogsMetadata[];
 extern const char kMetricsResetIds[];
+#if BUILDFLAG(IS_ANDROID)
+extern const char kUsePostFREFixSamplingTrial[];
+#endif  // BUILDFLAG(IS_ANDROID)
 
 // Preferences for cloned installs.
 extern const char kClonedResetCount[];
diff --git a/components/metrics/metrics_state_manager.cc b/components/metrics/metrics_state_manager.cc
index 742780c..b73db9f 100644
--- a/components/metrics/metrics_state_manager.cc
+++ b/components/metrics/metrics_state_manager.cc
@@ -269,6 +269,18 @@
         metrics::structured::NeutrinoDevicesLocation::kMetricsStateManager);
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
     ForceClientIdCreation();
+  } else {
+#if BUILDFLAG(IS_ANDROID)
+    // If on start up we determine that the client has not given their consent
+    // to report their metrics, the new sampling trial should be used to
+    // determine whether the client is sampled in or out (if the user ever
+    // enables metrics reporting). This covers users that are going through
+    // the first run, as well as users that have metrics reporting disabled.
+    //
+    // See crbug/1306481 and the comment above |kUsePostFREFixSamplingTrial| in
+    // components/metrics/metrics_pref_names.cc for more details.
+    local_state_->SetBoolean(metrics::prefs::kUsePostFREFixSamplingTrial, true);
+#endif  // BUILDFLAG(IS_ANDROID)
   }
 
 #if !BUILDFLAG(IS_WIN)
@@ -584,6 +596,9 @@
   registry->RegisterStringPref(prefs::kMetricsClientID, std::string());
   registry->RegisterInt64Pref(prefs::kMetricsReportingEnabledTimestamp, 0);
   registry->RegisterInt64Pref(prefs::kInstallDate, 0);
+#if BUILDFLAG(IS_ANDROID)
+  registry->RegisterBooleanPref(prefs::kUsePostFREFixSamplingTrial, false);
+#endif  // BUILDFLAG(IS_ANDROID)
 
   EntropyState::RegisterPrefs(registry);
   ClonedInstallDetector::RegisterPrefs(registry);
diff --git a/components/metrics_services_manager/metrics_services_manager.cc b/components/metrics_services_manager/metrics_services_manager.cc
index 2ba4a034..31d987c 100644
--- a/components/metrics_services_manager/metrics_services_manager.cc
+++ b/components/metrics_services_manager/metrics_services_manager.cc
@@ -100,6 +100,33 @@
     }
   }
 
+  // If metrics reporting goes from not consented to consented, create and
+  // persist a client ID (either generate a new one or promote the provisional
+  // client ID if this is the first run). This can occur in the following
+  // situations:
+  // 1. The user enables metrics reporting in the FRE
+  // 2. The user enables metrics reporting in settings, crash bubble, etc.
+  // 3. On startup, after fetching the enable status from the previous session
+  //    (if enabled)
+  //
+  // ForceClientIdCreation() may be called again later on via
+  // MetricsService::EnableRecording(), but in that case,
+  // ForceClientIdCreation() will be a no-op (will return early since a client
+  // ID will already exist).
+  //
+  // ForceClientIdCreation() must be called here, otherwise, in cases where the
+  // user is sampled out, the passed |current_may_record| will be false, which
+  // will result in not calling ForceClientIdCreation() in
+  // MetricsService::EnableRecording() later on. This is problematic because
+  // in the FRE, if the user consents to metrics reporting, this will cause the
+  // provisional client ID to not be promoted/stored as the client ID. In the
+  // next run, a different client ID will be generated and stored, which will
+  // result in different trial assignments—and the client may even be sampled
+  // in at that time.
+  if (!consent_given_ && current_consent_given) {
+    client_->GetMetricsStateManager()->ForceClientIdCreation();
+  }
+
   // Stash the current permissions so that we can update the services correctly
   // when preferences change.
   may_record_ = current_may_record;
diff --git a/components/omnibox/browser/local_history_zero_suggest_provider.cc b/components/omnibox/browser/local_history_zero_suggest_provider.cc
index 6aecd43..8e40d7f 100644
--- a/components/omnibox/browser/local_history_zero_suggest_provider.cc
+++ b/components/omnibox/browser/local_history_zero_suggest_provider.cc
@@ -23,6 +23,7 @@
 #include "components/history/core/browser/history_database.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/history/core/browser/keyword_search_term.h"
+#include "components/history/core/browser/keyword_search_term_util.h"
 #include "components/history/core/browser/url_database.h"
 #include "components/omnibox/browser/autocomplete_input.h"
 #include "components/omnibox/browser/autocomplete_match.h"
@@ -206,21 +207,31 @@
     return;
   }
 
-  std::vector<history::KeywordSearchTermVisit> results;
+  std::vector<std::unique_ptr<history::KeywordSearchTermVisit>> results;
   const base::TimeTicks db_query_time = base::TimeTicks::Now();
-  url_db->GetMostRecentKeywordSearchTerms(
-      template_url_service->GetDefaultSearchProvider()->id(),
-      OmniboxFieldTrial::GetLocalHistoryZeroSuggestAgeThreshold(), &results);
-
-  const base::Time now = base::Time::Now();
-  const int kRecencyDecayUnitSec = 60;
-  const double kFrequencyExponent = 1.15;
-  auto CompareByFrecency = [&](const auto& a, const auto& b) {
-    return a.GetFrecency(now, kRecencyDecayUnitSec, kFrequencyExponent) >
-           b.GetFrecency(now, kRecencyDecayUnitSec, kFrequencyExponent);
-  };
-  std::sort(results.begin(), results.end(), CompareByFrecency);
-
+  if (base::FeatureList::IsEnabled(omnibox::kLocalHistorySuggestRevamp)) {
+    auto enumerator = url_db->CreateKeywordSearchTermVisitEnumerator(
+        template_url_service->GetDefaultSearchProvider()->id(),
+        OmniboxFieldTrial::GetLocalHistoryZeroSuggestAgeThreshold());
+    if (enumerator) {
+      history::GetAutocompleteSearchTermsFromEnumerator(
+          *enumerator,
+          OmniboxFieldTrial::kZeroSuggestIgnoreDuplicateVisits.Get(),
+          history::SearchTermRankingPolicy::kFrecency, &results);
+    }
+  } else {
+    url_db->GetMostRecentKeywordSearchTerms(
+        template_url_service->GetDefaultSearchProvider()->id(),
+        OmniboxFieldTrial::GetLocalHistoryZeroSuggestAgeThreshold(), &results);
+    const base::Time now = base::Time::Now();
+    std::sort(results.begin(), results.end(),
+              [&](const auto& a, const auto& b) {
+                return history::GetFrecencyScore(a->visit_count,
+                                                 a->last_visit_time, now) >
+                       history::GetFrecencyScore(b->visit_count,
+                                                 b->last_visit_time, now);
+              });
+  }
   RecordDBMetrics(db_query_time, results.size());
 
   int relevance = client_->IsAuthenticated()
@@ -228,7 +239,7 @@
                       : kLocalHistoryZPSUnauthenticatedRelevance;
   for (const auto& result : results) {
     SearchSuggestionParser::SuggestResult suggestion(
-        /*suggestion=*/result.normalized_term,
+        /*suggestion=*/result->normalized_term,
         AutocompleteMatchType::SEARCH_HISTORY,
         /*subtypes=*/{}, /*from_keyword=*/false, relevance--,
         /*relevance_from_server=*/false,
diff --git a/components/omnibox/browser/local_history_zero_suggest_provider_unittest.cc b/components/omnibox/browser/local_history_zero_suggest_provider_unittest.cc
index aa7a59b..89a33aa 100644
--- a/components/omnibox/browser/local_history_zero_suggest_provider_unittest.cc
+++ b/components/omnibox/browser/local_history_zero_suggest_provider_unittest.cc
@@ -20,6 +20,7 @@
 #include "components/bookmarks/test/test_bookmark_client.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/history/core/browser/keyword_search_term.h"
+#include "components/history/core/browser/keyword_search_term_util.h"
 #include "components/history/core/browser/url_database.h"
 #include "components/history/core/test/history_service_test_util.h"
 #include "components/omnibox/browser/autocomplete_match.h"
@@ -482,12 +483,12 @@
   // produce the deleted match are deleted.
   history::URLDatabase* url_db =
       client_->GetHistoryService()->InMemoryDatabase();
-  std::vector<history::KeywordSearchTermVisit> visits;
+  std::vector<std::unique_ptr<history::KeywordSearchTermVisit>> visits;
   url_db->GetMostRecentKeywordSearchTerms(
       default_search_provider()->id(), GetLocalHistoryZeroSuggestAgeThreshold(),
       &visits);
   EXPECT_EQ(1U, visits.size());
-  EXPECT_EQ(u"not to be deleted", visits[0].normalized_term);
+  EXPECT_EQ(u"not to be deleted", visits[0]->normalized_term);
 
   // Make sure search terms from other search providers that would produce the
   // deleted match are not deleted.
@@ -496,5 +497,5 @@
       other_search_provider->id(), GetLocalHistoryZeroSuggestAgeThreshold(),
       &visits);
   EXPECT_EQ(1U, visits.size());
-  EXPECT_EQ(u"hello world", visits[0].normalized_term);
+  EXPECT_EQ(u"hello world", visits[0]->normalized_term);
 }
diff --git a/components/omnibox/browser/omnibox_field_trial.cc b/components/omnibox/browser/omnibox_field_trial.cc
index a8ecd9c..d2a1814 100644
--- a/components/omnibox/browser/omnibox_field_trial.cc
+++ b/components/omnibox/browser/omnibox_field_trial.cc
@@ -970,7 +970,7 @@
         "ShortBookmarkSuggestionsByTotalInputLengthThreshold",
         3);
 
-// Zero Suggest
+// Local history zero-prefix (aka zero-suggest) and prefix suggestions.
 const base::FeatureParam<bool> kZeroSuggestCacheCounterfactual(
     &omnibox::kZeroSuggestPrefetching,
     "ZeroSuggestCacheCounterfactual",
@@ -984,6 +984,15 @@
     "ZeroSuggestPrefetchBypassCache",
     false);
 
+const base::FeatureParam<bool> kZeroSuggestIgnoreDuplicateVisits(
+    &omnibox::kLocalHistorySuggestRevamp,
+    "ZeroSuggestIgnoreDuplicateVisits",
+    true);
+const base::FeatureParam<bool> kPrefixSuggestIgnoreDuplicateVisits(
+    &omnibox::kLocalHistorySuggestRevamp,
+    "PrefixSuggestIgnoreDuplicateVisits",
+    false);
+
 }  // namespace OmniboxFieldTrial
 
 std::string OmniboxFieldTrial::internal::GetValueForRuleInContext(
diff --git a/components/omnibox/browser/omnibox_field_trial.h b/components/omnibox/browser/omnibox_field_trial.h
index 96cb7e2..1ca5603d 100644
--- a/components/omnibox/browser/omnibox_field_trial.h
+++ b/components/omnibox/browser/omnibox_field_trial.h
@@ -546,7 +546,7 @@
 extern const base::FeatureParam<int>
     kShortBookmarkSuggestionsByTotalInputLengthThreshold;
 
-// Zero Suggest
+// Local history zero-prefix (aka zero-suggest) and prefix suggestions.
 // Indicates whether the user is in the counterfactual group in the experiment
 // for prefetching zero prefix suggestions on the NTP. Users in the
 // counterfactual group issue a follow-up non-cacheable request if the response
@@ -567,6 +567,14 @@
 // cache duration clock is reset and the subsequent non-prefetch zero suggest
 // requests, depending on the cache duration, are loaded from the HTTP cache.
 extern const base::FeatureParam<bool> kZeroSuggestPrefetchBypassCache;
+// Whether duplicative visits should be ignored for local history zero-suggest.
+// A duplicative visit is a visit to the same search term in an interval smaller
+// than kAutocompleteDuplicateVisitIntervalThreshold.
+extern const base::FeatureParam<bool> kZeroSuggestIgnoreDuplicateVisits;
+// Whether duplicative visits should be ignored for local history
+// prefix-suggest. A duplicative visit is a visit to the same search term in an
+// interval smaller than kAutocompleteDuplicateVisitIntervalThreshold.
+extern const base::FeatureParam<bool> kPrefixSuggestIgnoreDuplicateVisits;
 
 // New params should be inserted above this comment and formatted as:
 // - Short comment categorizing the relevant features & params.
diff --git a/components/omnibox/browser/search_provider.cc b/components/omnibox/browser/search_provider.cc
index baa27ddaa..e0c141f 100644
--- a/components/omnibox/browser/search_provider.cc
+++ b/components/omnibox/browser/search_provider.cc
@@ -28,6 +28,7 @@
 #include "base/trace_event/trace_event.h"
 #include "components/history/core/browser/in_memory_database.h"
 #include "components/history/core/browser/keyword_search_term.h"
+#include "components/history/core/browser/keyword_search_term_util.h"
 #include "components/omnibox/browser/autocomplete_provider_client.h"
 #include "components/omnibox/browser/autocomplete_provider_listener.h"
 #include "components/omnibox/browser/autocomplete_result.h"
@@ -682,24 +683,52 @@
   // now, this seems OK compared with the complexity of a real fix, which would
   // require multiple searches and tracking of "single- vs. multi-word" in the
   // database.
-  int num_matches = provider_max_matches_ * 5;
+  size_t num_matches = provider_max_matches_ * 5;
   const TemplateURL* default_url = providers_.GetDefaultProviderURL();
   if (default_url) {
     const base::TimeTicks db_query_time = base::TimeTicks::Now();
-    url_db->GetMostRecentKeywordSearchTerms(default_url->id(),
-                                            input_.text(),
-                                            num_matches,
-                                            &raw_default_history_results_);
+    if (base::FeatureList::IsEnabled(omnibox::kLocalHistorySuggestRevamp)) {
+      auto enumerator = url_db->CreateKeywordSearchTermVisitEnumerator(
+          default_url->id(), input_.text());
+      if (enumerator) {
+        history::GetAutocompleteSearchTermsFromEnumerator(
+            *enumerator,
+            OmniboxFieldTrial::kPrefixSuggestIgnoreDuplicateVisits.Get(),
+            history::SearchTermRankingPolicy::kRecency,
+            &raw_default_history_results_);
+      }
+    } else {
+      url_db->GetMostRecentKeywordSearchTerms(default_url->id(), input_.text(),
+                                              num_matches,
+                                              &raw_default_history_results_);
+    }
     RecordDBMetrics(db_query_time, raw_default_history_results_.size());
+    if (raw_default_history_results_.size() > num_matches) {
+      raw_default_history_results_.resize(num_matches);
+    }
   }
   const TemplateURL* keyword_url = providers_.GetKeywordProviderURL();
   if (keyword_url) {
     const base::TimeTicks db_query_time = base::TimeTicks::Now();
-    url_db->GetMostRecentKeywordSearchTerms(keyword_url->id(),
-                                            keyword_input_.text(),
-                                            num_matches,
-                                            &raw_keyword_history_results_);
+    if (base::FeatureList::IsEnabled(omnibox::kLocalHistorySuggestRevamp)) {
+      auto enumerator = url_db->CreateKeywordSearchTermVisitEnumerator(
+          keyword_url->id(), keyword_input_.text());
+      if (enumerator) {
+        history::GetAutocompleteSearchTermsFromEnumerator(
+            *enumerator,
+            OmniboxFieldTrial::kPrefixSuggestIgnoreDuplicateVisits.Get(),
+            history::SearchTermRankingPolicy::kRecency,
+            &raw_keyword_history_results_);
+      }
+    } else {
+      url_db->GetMostRecentKeywordSearchTerms(
+          keyword_url->id(), keyword_input_.text(), num_matches,
+          &raw_keyword_history_results_);
+    }
     RecordDBMetrics(db_query_time, raw_keyword_history_results_.size());
+    if (raw_keyword_history_results_.size() > num_matches) {
+      raw_keyword_history_results_.resize(num_matches);
+    }
   }
 }
 
@@ -1229,17 +1258,17 @@
       base::CollapseWhitespace(input_text, false);
   for (const auto& result : results) {
     const std::u16string& trimmed_suggestion =
-        base::CollapseWhitespace(result.term, false);
+        base::CollapseWhitespace(result->term, false);
 
     // Don't autocomplete multi-word queries that have only been seen once
     // unless the user has typed more than one word.
     bool prevent_inline_autocomplete =
         base_prevent_inline_autocomplete ||
-        (!input_multiple_words && (result.visit_count < 2) &&
+        (!input_multiple_words && (result->visit_count < 2) &&
          HasMultipleWords(trimmed_suggestion));
 
     int relevance = CalculateRelevanceForHistory(
-        result.last_visit_time, is_keyword, !prevent_inline_autocomplete,
+        result->last_visit_time, is_keyword, !prevent_inline_autocomplete,
         prevent_search_history_inlining);
     // Add the match to |scored_results| by putting the what-you-typed match
     // on the front and appending all other matches.  We want the what-you-
diff --git a/components/omnibox/browser/search_provider.h b/components/omnibox/browser/search_provider.h
index 642ef2a6..cbaf8db0 100644
--- a/components/omnibox/browser/search_provider.h
+++ b/components/omnibox/browser/search_provider.h
@@ -159,7 +159,8 @@
 
   class CompareScoredResults;
 
-  typedef std::vector<history::KeywordSearchTermVisit> HistoryResults;
+  typedef std::vector<std::unique_ptr<history::KeywordSearchTermVisit>>
+      HistoryResults;
 
   // A helper function for UpdateAllOldResults().
   static void UpdateOldResults(bool minimal_changes,
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index 3098204..a85a06e2 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -145,6 +145,11 @@
     "OmniboxOnFocusSuggestionsContextualWebOnContent",
     enabled_by_default_android_only};
 
+// Revamps how local search history is extracted and processed for generating
+// zero-prefix and prefix suggestions.
+extern const base::Feature kLocalHistorySuggestRevamp{
+    "LocalHistorySuggestRevamp", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Allows the LocalHistoryZeroSuggestProvider to use local search history.
 const base::Feature kLocalHistoryZeroSuggest{
     "LocalHistoryZeroSuggest", enabled_by_default_desktop_android};
diff --git a/components/omnibox/common/omnibox_features.h b/components/omnibox/common/omnibox_features.h
index d2ce023..f957e1bd 100644
--- a/components/omnibox/common/omnibox_features.h
+++ b/components/omnibox/common/omnibox_features.h
@@ -36,15 +36,16 @@
 extern const base::Feature kDynamicMaxAutocomplete;
 extern const base::Feature kRetainSuggestionsWithHeaders;
 
-// On-Focus Suggestions a.k.a. ZeroSuggest.
+// Local history zero-prefix (aka zero-suggest) and prefix suggestions.
 extern const base::Feature kClobberTriggersContextualWebZeroSuggest;
 extern const base::Feature kClobberTriggersSRPZeroSuggest;
+extern const base::Feature kLocalHistorySuggestRevamp;
+extern const base::Feature kLocalHistoryZeroSuggest;
 extern const base::Feature kOmniboxLocalZeroSuggestAgeThreshold;
 extern const base::Feature kOmniboxTrendingZeroPrefixSuggestionsOnNTP;
 extern const base::Feature kOnFocusSuggestionsContextualWeb;
 extern const base::Feature kOnFocusSuggestionsContextualWebAllowSRP;
 extern const base::Feature kOnFocusSuggestionsContextualWebOnContent;
-extern const base::Feature kLocalHistoryZeroSuggest;
 extern const base::Feature kZeroSuggestPrefetching;
 // Related, kMaxZeroSuggestMatches.
 
diff --git a/components/optimization_guide/content/browser/BUILD.gn b/components/optimization_guide/content/browser/BUILD.gn
index 26462cd..d9c42bfe 100644
--- a/components/optimization_guide/content/browser/BUILD.gn
+++ b/components/optimization_guide/content/browser/BUILD.gn
@@ -12,6 +12,8 @@
     "optimization_guide_decider.h",
     "page_content_annotations_service.cc",
     "page_content_annotations_service.h",
+    "page_content_annotations_validator.cc",
+    "page_content_annotations_validator.h",
     "page_content_annotations_web_contents_observer.cc",
     "page_content_annotations_web_contents_observer.h",
     "page_content_annotator.h",
@@ -76,6 +78,7 @@
   testonly = true
   sources = [
     "page_content_annotations_service_unittest.cc",
+    "page_content_annotations_validator_unittest.cc",
     "page_content_annotations_web_contents_observer_unittest.cc",
     "page_text_dump_result_unittest.cc",
     "page_text_observer_unittest.cc",
diff --git a/components/optimization_guide/content/browser/page_content_annotations_service.cc b/components/optimization_guide/content/browser/page_content_annotations_service.cc
index 517a488..8215bb50 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_service.cc
+++ b/components/optimization_guide/content/browser/page_content_annotations_service.cc
@@ -12,10 +12,9 @@
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
-#include "base/time/default_tick_clock.h"
-#include "base/timer/timer.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/leveldb_proto/public/proto_database_provider.h"
+#include "components/optimization_guide/content/browser/page_content_annotations_validator.h"
 #include "components/optimization_guide/core/local_page_entities_metadata_provider.h"
 #include "components/optimization_guide/core/noisy_metrics_recorder.h"
 #include "components/optimization_guide/core/optimization_guide_enums.h"
@@ -105,14 +104,6 @@
 }
 #endif /* BUILDFLAG(BUILD_WITH_TFLITE_LIB) */
 
-const char* kRandomWords[] = {
-    "interesting", "chunky",    "maniacal", "tickle",   "lettuce",
-    "obsequious",  "stir",      "bless",    "colossal", "squealing",
-    "elegant",     "ambitious", "eight",    "frighten", "descriptive",
-    "pretty",      "curly",     "regular",  "uneven",   "heap",
-};
-const size_t kCountRandomWords = 20;
-
 }  // namespace
 
 PageContentAnnotationsService::PageContentAnnotationsService(
@@ -154,22 +145,8 @@
         database_provider, database_dir, background_task_runner);
   }
 
-  if (features::BatchAnnotationsValidationEnabled()) {
-    // Normally the caller would do this, but we are our own caller.
-    RequestAndNotifyWhenModelAvailable(
-        features::BatchAnnotationsValidationUsePageTopics()
-            ? AnnotationType::kPageTopics
-            : AnnotationType::kContentVisibility,
-        base::DoNothing());
-
-    validation_timer_ = std::make_unique<base::OneShotTimer>(
-        base::DefaultTickClock::GetInstance());
-    validation_timer_->Start(
-        FROM_HERE, features::BatchAnnotationValidationStartupDelay(),
-        base::BindRepeating(
-            &PageContentAnnotationsService::RunBatchAnnotationValidation,
-            weak_ptr_factory_.GetWeakPtr()));
-  }
+  validator_ =
+      PageContentAnnotationsValidator::MaybeCreateAndStartTimer(annotator_);
 }
 
 PageContentAnnotationsService::~PageContentAnnotationsService() = default;
@@ -605,32 +582,6 @@
            PageContentAnnotationsType::kModelAnnotations);
 }
 
-void PageContentAnnotationsService::RunBatchAnnotationValidation() {
-  DCHECK(features::BatchAnnotationsValidationEnabled());
-  DCHECK(validation_timer_);
-  validation_timer_.reset();
-
-  std::vector<std::string> dummy_inputs;
-  dummy_inputs.reserve(features::BatchAnnotationsValidationBatchSize());
-  for (size_t i = 0; i < features::BatchAnnotationsValidationBatchSize(); i++) {
-    const char* word1 = kRandomWords[base::RandGenerator(kCountRandomWords)];
-    const char* word2 = kRandomWords[base::RandGenerator(kCountRandomWords)];
-    dummy_inputs.emplace_back(base::StringPrintf("%s-%s.com", word1, word2));
-  }
-
-  LOCAL_HISTOGRAM_COUNTS_100(
-      "OptimizationGuide.PageContentAnnotationsService.ValidationRun",
-      dummy_inputs.size());
-
-  if (!features::BatchAnnotationsValidationUsePageTopics()) {
-    BatchAnnotate(base::DoNothing(), dummy_inputs,
-                  AnnotationType::kContentVisibility);
-    return;
-  }
-
-  BatchAnnotatePageTopics(base::DoNothing(), dummy_inputs);
-}
-
 // static
 HistoryVisit PageContentAnnotationsService::CreateHistoryVisitFromWebContents(
     content::WebContents* web_contents,
diff --git a/components/optimization_guide/content/browser/page_content_annotations_service.h b/components/optimization_guide/content/browser/page_content_annotations_service.h
index ac34887..857abdf 100644
--- a/components/optimization_guide/content/browser/page_content_annotations_service.h
+++ b/components/optimization_guide/content/browser/page_content_annotations_service.h
@@ -33,10 +33,6 @@
 #include "third_party/abseil-cpp/absl/types/optional.h"
 #include "url/gurl.h"
 
-namespace base {
-class OneShotTimer;
-}  // namespace base
-
 namespace content {
 class WebContents;
 }  // namespace content
@@ -54,8 +50,9 @@
 class LocalPageEntitiesMetadataProvider;
 class OptimizationGuideModelProvider;
 class PageContentAnnotationsModelManager;
-class PageContentAnnotationsServiceTest;
 class PageContentAnnotationsServiceBrowserTest;
+class PageContentAnnotationsServiceTest;
+class PageContentAnnotationsValidator;
 class PageContentAnnotationsWebContentsObserver;
 
 // The information used by HistoryService to identify a visit to a URL.
@@ -255,10 +252,6 @@
                     PageContentAnnotationsType annotation_type,
                     history::QueryURLResult url_result);
 
-  // Runs a batch annotation validation, that is calls |BatchAnnotate| with
-  // dummy input and discards the output.
-  void RunBatchAnnotationValidation();
-
   // A metadata-only provider for page entities (as opposed to |model_manager_|
   // which does both entity model execution and metadata providing) that uses a
   // local database to provide the metadata for a given entity id. This is only
@@ -298,8 +291,9 @@
   // no visits are actively be annotated and a new batch can be started.
   std::vector<HistoryVisit> current_visit_annotation_batch_;
 
-  // Is only ever set when the feature is enabled.
-  std::unique_ptr<base::OneShotTimer> validation_timer_;
+  // Set during this' ctor if the corresponding command line or feature flags
+  // are set.
+  std::unique_ptr<PageContentAnnotationsValidator> validator_;
 
   base::WeakPtrFactory<PageContentAnnotationsService> weak_ptr_factory_{this};
 };
diff --git a/components/optimization_guide/content/browser/page_content_annotations_validator.cc b/components/optimization_guide/content/browser/page_content_annotations_validator.cc
new file mode 100644
index 0000000..771ec3d
--- /dev/null
+++ b/components/optimization_guide/content/browser/page_content_annotations_validator.cc
@@ -0,0 +1,120 @@
+// Copyright 2022 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/content/browser/page_content_annotations_validator.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/rand_util.h"
+#include "base/strings/strcat.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/default_tick_clock.h"
+#include "components/optimization_guide/content/browser/page_content_annotator.h"
+#include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/optimization_guide/core/optimization_guide_switches.h"
+#include "components/optimization_guide/core/page_content_annotations_common.h"
+
+namespace optimization_guide {
+
+namespace {
+
+const char* kRandomNouns[] = {
+    "Airplane", "Boat",       "Book",          "Dinosaur",   "Earth",
+    "Football", "Fork",       "Hummingbird",   "Magic Wand", "Mailbox",
+    "Molecule", "Pizza",      "Record Player", "Skeleton",   "Soda",
+    "Sphere",   "Strawberry", "Tiger",         "Turkey",     "Wolf",
+};
+const size_t kCountRandomNouns = 20;
+
+void LogAnnotationResultToConsole(
+    const std::vector<BatchAnnotationResult>& results) {
+  if (!switches::LogPageContentAnnotationsValidationToConsole()) {
+    return;
+  }
+
+  LOG(ERROR) << "PageContentAnnotations Validation Complete:";
+  for (size_t i = 0; i < results.size(); i++) {
+    LOG(ERROR) << "  " << i << ": " << results[i].ToJSON();
+  }
+}
+
+}  // namespace
+
+PageContentAnnotationsValidator::~PageContentAnnotationsValidator() = default;
+PageContentAnnotationsValidator::PageContentAnnotationsValidator(
+    PageContentAnnotator* annotator)
+    : annotator_(annotator) {
+  DCHECK(annotator);
+  for (AnnotationType type : {
+           AnnotationType::kPageTopics,
+           AnnotationType::kPageEntities,
+           AnnotationType::kContentVisibility,
+       }) {
+    if (features::PageContentAnnotationValidationEnabledForType(type)) {
+      enabled_annotation_types_.push_back(type);
+      annotator_->RequestAndNotifyWhenModelAvailable(type, base::DoNothing());
+    }
+  }
+
+  timer_.Start(FROM_HERE,
+               features::PageContentAnnotationValidationStartupDelay(),
+               base::BindOnce(&PageContentAnnotationsValidator::Run,
+                              weak_ptr_factory_.GetWeakPtr()));
+}
+
+// static
+std::unique_ptr<PageContentAnnotationsValidator>
+PageContentAnnotationsValidator::MaybeCreateAndStartTimer(
+    PageContentAnnotator* annotator) {
+  // This can happen with certain build/feature flags.
+  if (!annotator) {
+    return nullptr;
+  }
+
+  bool enabled_for_any_type = false;
+  for (AnnotationType type : {
+           AnnotationType::kPageTopics,
+           AnnotationType::kPageEntities,
+           AnnotationType::kContentVisibility,
+       }) {
+    enabled_for_any_type |=
+        features::PageContentAnnotationValidationEnabledForType(type);
+  }
+  if (!enabled_for_any_type) {
+    return nullptr;
+  }
+
+  // This is done because |PageContentAnnotationsValidator| has a private ctor.
+  return base::WrapUnique(new PageContentAnnotationsValidator(annotator));
+}
+
+void PageContentAnnotationsValidator::Run() {
+  for (AnnotationType type : enabled_annotation_types_) {
+    annotator_->Annotate(base::BindOnce(&LogAnnotationResultToConsole),
+                         BuildInputsForType(type), type);
+  }
+}
+
+// static
+std::vector<std::string> PageContentAnnotationsValidator::BuildInputsForType(
+    AnnotationType type) {
+  absl::optional<std::vector<std::string>> cmd_line_input =
+      switches::PageContentAnnotationsValidationInputForType(type);
+  if (cmd_line_input) {
+    return *cmd_line_input;
+  }
+
+  std::vector<std::string> inputs;
+  for (size_t i = 0; i < features::PageContentAnnotationsValidationBatchSize();
+       i++) {
+    const char* word1 = kRandomNouns[base::RandGenerator(kCountRandomNouns)];
+    const char* word2 = kRandomNouns[base::RandGenerator(kCountRandomNouns)];
+    inputs.emplace_back(base::StrCat({word1, " ", word2}));
+  }
+  return inputs;
+}
+
+}  // namespace optimization_guide
\ No newline at end of file
diff --git a/components/optimization_guide/content/browser/page_content_annotations_validator.h b/components/optimization_guide/content/browser/page_content_annotations_validator.h
new file mode 100644
index 0000000..8300e28
--- /dev/null
+++ b/components/optimization_guide/content/browser/page_content_annotations_validator.h
@@ -0,0 +1,57 @@
+// Copyright 2022 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_CONTENT_BROWSER_PAGE_CONTENT_ANNOTATIONS_VALIDATOR_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_PAGE_CONTENT_ANNOTATIONS_VALIDATOR_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/timer/timer.h"
+#include "components/optimization_guide/core/page_content_annotation_type.h"
+
+namespace optimization_guide {
+
+class PageContentAnnotator;
+
+// This class manages validation runs of the PageContentAnnotationsService,
+// running the ML model for a given AnnotationType on dummy data after some
+// delay from browser startup. This feature can be controlled by experimental
+// feature flags and command line.
+class PageContentAnnotationsValidator {
+ public:
+  ~PageContentAnnotationsValidator();
+
+  // If the appropriate feature flag or command line switch is given, an
+  // instance of |this| is created, else nullptr.
+  static std::unique_ptr<PageContentAnnotationsValidator>
+  MaybeCreateAndStartTimer(PageContentAnnotator* annotator);
+
+ private:
+  explicit PageContentAnnotationsValidator(PageContentAnnotator* annotator);
+
+  // Runs the validation for all enabled AnnotationTypes.
+  void Run();
+
+  // Creates a set of dummy input data to run for the given |type|, either
+  // randomly generated off of experiment parameters or given on the command
+  // line.
+  static std::vector<std::string> BuildInputsForType(AnnotationType type);
+
+  std::vector<AnnotationType> enabled_annotation_types_;
+
+  // Out lives |this|, not owned.
+  raw_ptr<PageContentAnnotator> annotator_;
+
+  // Starts in the ctor, roughly on browser start, and calls |Run|.
+  base::OneShotTimer timer_;
+
+  base::WeakPtrFactory<PageContentAnnotationsValidator> weak_ptr_factory_{this};
+};
+
+}  // namespace optimization_guide
+
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CONTENT_BROWSER_PAGE_CONTENT_ANNOTATIONS_VALIDATOR_H_
diff --git a/components/optimization_guide/content/browser/page_content_annotations_validator_unittest.cc b/components/optimization_guide/content/browser/page_content_annotations_validator_unittest.cc
new file mode 100644
index 0000000..7201771
--- /dev/null
+++ b/components/optimization_guide/content/browser/page_content_annotations_validator_unittest.cc
@@ -0,0 +1,325 @@
+// Copyright 2022 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/content/browser/page_content_annotations_validator.h"
+
+#include "base/command_line.h"
+#include "base/test/scoped_command_line.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/task_environment.h"
+#include "components/optimization_guide/content/browser/test_page_content_annotator.h"
+#include "components/optimization_guide/core/optimization_guide_features.h"
+#include "components/optimization_guide/core/optimization_guide_switches.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace optimization_guide {
+
+TEST(PageContentAnnotationsValidatorTest, DoesNothing) {
+  TestPageContentAnnotator annotator;
+  EXPECT_EQ(nullptr, PageContentAnnotationsValidator::MaybeCreateAndStartTimer(
+                         &annotator));
+}
+
+TEST(PageContentAnnotationsValidatorTest, NoAnnotator) {
+  TestPageContentAnnotator annotator;
+  EXPECT_EQ(nullptr,
+            PageContentAnnotationsValidator::MaybeCreateAndStartTimer(nullptr));
+}
+
+TEST(PageContentAnnotationsValidatorTest,
+     DoesNothing_FeatureEnabledButNoTypes) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      features::kPageContentAnnotationsValidation);
+
+  TestPageContentAnnotator annotator;
+  EXPECT_EQ(nullptr, PageContentAnnotationsValidator::MaybeCreateAndStartTimer(
+                         &annotator));
+}
+
+TEST(PageContentAnnotationsValidatorTest, AllEnabledByExperiment) {
+  base::test::TaskEnvironment task_env{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeatureWithParameters(
+      features::kPageContentAnnotationsValidation,
+      {
+          {"PageTopics", "true"},
+          {"PageEntities", "true"},
+          {"ContentVisibility", "true"},
+      });
+
+  TestPageContentAnnotator annotator;
+  auto validator =
+      PageContentAnnotationsValidator::MaybeCreateAndStartTimer(&annotator);
+  ASSERT_TRUE(validator);
+  task_env.FastForwardBy(base::Seconds(30));
+
+  const auto& annotation_requests = annotator.annotation_requests();
+  ASSERT_EQ(3U, annotation_requests.size());
+  EXPECT_EQ(annotation_requests[0].second, AnnotationType::kPageTopics);
+  EXPECT_EQ(annotation_requests[1].second, AnnotationType::kPageEntities);
+  EXPECT_EQ(annotation_requests[2].second, AnnotationType::kContentVisibility);
+}
+
+TEST(PageContentAnnotationsValidatorTest, AllEnabledByCommandLine) {
+  base::test::TaskEnvironment task_env{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
+
+  cmd->AppendSwitchASCII(switches::kPageContentAnnotationsValidationPageTopics,
+                         "page topics,pt input2, pt keeps whitespace  ");
+  cmd->AppendSwitchASCII(
+      switches::kPageContentAnnotationsValidationPageEntities,
+      "page entities,pe input2, pe keeps whitespace  ");
+  cmd->AppendSwitchASCII(
+      switches::kPageContentAnnotationsValidationContentVisibility,
+      "content viz,cv input2, cv keeps whitespace  ");
+
+  TestPageContentAnnotator annotator;
+  auto validator =
+      PageContentAnnotationsValidator::MaybeCreateAndStartTimer(&annotator);
+  ASSERT_TRUE(validator);
+  task_env.FastForwardBy(base::Seconds(30));
+
+  const auto& annotation_requests = annotator.annotation_requests();
+  ASSERT_EQ(3U, annotation_requests.size());
+
+  EXPECT_THAT(annotation_requests[0].first, testing::ElementsAreArray({
+                                                "page topics",
+                                                "pt input2",
+                                                " pt keeps whitespace  ",
+                                            }));
+  EXPECT_EQ(annotation_requests[0].second, AnnotationType::kPageTopics);
+
+  EXPECT_THAT(annotation_requests[1].first, testing::ElementsAreArray({
+                                                "page entities",
+                                                "pe input2",
+                                                " pe keeps whitespace  ",
+                                            }));
+  EXPECT_EQ(annotation_requests[1].second, AnnotationType::kPageEntities);
+
+  EXPECT_THAT(annotation_requests[2].first, testing::ElementsAreArray({
+                                                "content viz",
+                                                "cv input2",
+                                                " cv keeps whitespace  ",
+                                            }));
+  EXPECT_EQ(annotation_requests[2].second, AnnotationType::kContentVisibility);
+}
+
+TEST(PageContentAnnotationsValidatorTest, OnlyOneEnabled_Cmd) {
+  base::test::TaskEnvironment task_env{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+
+  for (AnnotationType type : {
+           AnnotationType::kPageTopics,
+           AnnotationType::kPageEntities,
+           AnnotationType::kContentVisibility,
+       }) {
+    SCOPED_TRACE(AnnotationTypeToString(type));
+    base::test::ScopedCommandLine scoped_cmd;
+    base::CommandLine* cmd = scoped_cmd.GetProcessCommandLine();
+
+    switch (type) {
+      case AnnotationType::kPageTopics:
+        cmd->AppendSwitch(
+            switches::kPageContentAnnotationsValidationPageTopics);
+        break;
+      case AnnotationType::kPageEntities:
+        cmd->AppendSwitch(
+            switches::kPageContentAnnotationsValidationPageEntities);
+        break;
+      case AnnotationType::kContentVisibility:
+        cmd->AppendSwitch(
+            switches::kPageContentAnnotationsValidationContentVisibility);
+        break;
+      default:
+        break;
+    }
+
+    TestPageContentAnnotator annotator;
+    auto validator =
+        PageContentAnnotationsValidator::MaybeCreateAndStartTimer(&annotator);
+    ASSERT_TRUE(validator);
+    task_env.FastForwardBy(base::Seconds(30));
+
+    const auto& annotation_requests = annotator.annotation_requests();
+    ASSERT_EQ(1U, annotation_requests.size());
+
+    EXPECT_EQ(annotation_requests[0].second, type);
+  }
+}
+
+TEST(PageContentAnnotationsValidatorTest, OnlyOneEnabled_Feature) {
+  base::test::TaskEnvironment task_env{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+
+  for (AnnotationType type : {
+           AnnotationType::kPageTopics,
+           AnnotationType::kPageEntities,
+           AnnotationType::kContentVisibility,
+       }) {
+    SCOPED_TRACE(AnnotationTypeToString(type));
+    base::test::ScopedFeatureList scoped_feature_list;
+
+    switch (type) {
+      case AnnotationType::kPageTopics:
+        scoped_feature_list.InitAndEnableFeatureWithParameters(
+            features::kPageContentAnnotationsValidation,
+            {{"PageTopics", "true"}});
+        break;
+      case AnnotationType::kPageEntities:
+        scoped_feature_list.InitAndEnableFeatureWithParameters(
+            features::kPageContentAnnotationsValidation,
+            {{"PageEntities", "true"}});
+        break;
+      case AnnotationType::kContentVisibility:
+        scoped_feature_list.InitAndEnableFeatureWithParameters(
+            features::kPageContentAnnotationsValidation,
+            {{"ContentVisibility", "true"}});
+        break;
+      default:
+        break;
+    }
+
+    TestPageContentAnnotator annotator;
+    auto validator =
+        PageContentAnnotationsValidator::MaybeCreateAndStartTimer(&annotator);
+    ASSERT_TRUE(validator);
+    task_env.FastForwardBy(base::Seconds(30));
+
+    const auto& annotation_requests = annotator.annotation_requests();
+    ASSERT_EQ(1U, annotation_requests.size());
+    EXPECT_EQ(annotation_requests[0].second, type);
+  }
+}
+
+TEST(PageContentAnnotationsValidatorTest, TimerDelayByCmd) {
+  base::test::TaskEnvironment task_env{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
+
+  cmd->AppendSwitch(switches::kPageContentAnnotationsValidationPageTopics);
+  cmd->AppendSwitchASCII(
+      switches::kPageContentAnnotationsValidationStartupDelaySeconds, "5");
+
+  TestPageContentAnnotator annotator;
+  auto validator =
+      PageContentAnnotationsValidator::MaybeCreateAndStartTimer(&annotator);
+  ASSERT_TRUE(validator);
+  EXPECT_TRUE(annotator.annotation_requests().empty());
+
+  task_env.FastForwardBy(base::Milliseconds(4999));
+  EXPECT_TRUE(annotator.annotation_requests().empty());
+
+  task_env.FastForwardBy(base::Milliseconds(1));
+  EXPECT_FALSE(annotator.annotation_requests().empty());
+}
+
+TEST(PageContentAnnotationsValidatorTest, TimerDelayByFeature) {
+  base::test::TaskEnvironment task_env{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeatureWithParameters(
+      features::kPageContentAnnotationsValidation, {
+                                                       {"PageTopics", "true"},
+                                                       {"startup_delay", "5"},
+                                                   });
+
+  TestPageContentAnnotator annotator;
+  auto validator =
+      PageContentAnnotationsValidator::MaybeCreateAndStartTimer(&annotator);
+  ASSERT_TRUE(validator);
+  EXPECT_TRUE(annotator.annotation_requests().empty());
+
+  task_env.FastForwardBy(base::Milliseconds(4999));
+  EXPECT_TRUE(annotator.annotation_requests().empty());
+
+  task_env.FastForwardBy(base::Milliseconds(1));
+  EXPECT_FALSE(annotator.annotation_requests().empty());
+}
+
+TEST(PageContentAnnotationsValidatorTest, BatchSizeByCmd) {
+  base::test::TaskEnvironment task_env{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
+
+  cmd->AppendSwitch(switches::kPageContentAnnotationsValidationPageTopics);
+  cmd->AppendSwitchASCII(
+      switches::kPageContentAnnotationsValidationBatchSizeOverride, "5");
+
+  TestPageContentAnnotator annotator;
+  auto validator =
+      PageContentAnnotationsValidator::MaybeCreateAndStartTimer(&annotator);
+  ASSERT_TRUE(validator);
+  task_env.FastForwardBy(base::Seconds(30));
+
+  const auto& annotation_requests = annotator.annotation_requests();
+  ASSERT_EQ(1U, annotation_requests.size());
+  EXPECT_EQ(annotation_requests[0].first.size(), 5U);
+}
+
+TEST(PageContentAnnotationsValidatorTest, BatchSizeByFeature) {
+  base::test::TaskEnvironment task_env{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeatureWithParameters(
+      features::kPageContentAnnotationsValidation, {
+                                                       {"PageTopics", "true"},
+                                                       {"batch_size", "5"},
+                                                   });
+
+  TestPageContentAnnotator annotator;
+  auto validator =
+      PageContentAnnotationsValidator::MaybeCreateAndStartTimer(&annotator);
+  ASSERT_TRUE(validator);
+  task_env.FastForwardBy(base::Seconds(30));
+
+  const auto& annotation_requests = annotator.annotation_requests();
+  ASSERT_EQ(1U, annotation_requests.size());
+  EXPECT_EQ(annotation_requests[0].first.size(), 5U);
+}
+
+TEST(PageContentAnnotationsValidatorTest, CommandOverridesFeature) {
+  base::test::TaskEnvironment task_env{
+      base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+  base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
+
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeatureWithParameters(
+      features::kPageContentAnnotationsValidation,
+      {
+          {"PageTopics", "true"},
+          {"PageEntities", "true"},
+          {"ContentVisibility", "true"},
+          {"batch_size", "3"},
+      });
+
+  cmd->AppendSwitchASCII(switches::kPageContentAnnotationsValidationPageTopics,
+                         "page topics");
+  cmd->AppendSwitchASCII(
+      switches::kPageContentAnnotationsValidationBatchSizeOverride, "5");
+
+  TestPageContentAnnotator annotator;
+  auto validator =
+      PageContentAnnotationsValidator::MaybeCreateAndStartTimer(&annotator);
+  ASSERT_TRUE(validator);
+  task_env.FastForwardBy(base::Seconds(30));
+
+  const auto& annotation_requests = annotator.annotation_requests();
+  ASSERT_EQ(3U, annotation_requests.size());
+
+  EXPECT_THAT(annotation_requests[0].first,
+              testing::ElementsAre("page topics"));
+  EXPECT_EQ(annotation_requests[0].second, AnnotationType::kPageTopics);
+
+  EXPECT_EQ(annotation_requests[1].first.size(), 5U);
+  EXPECT_EQ(annotation_requests[1].second, AnnotationType::kPageEntities);
+
+  EXPECT_EQ(annotation_requests[2].first.size(), 5U);
+  EXPECT_EQ(annotation_requests[2].second, AnnotationType::kContentVisibility);
+}
+
+}  // namespace optimization_guide
\ No newline at end of file
diff --git a/components/optimization_guide/content/browser/test_page_content_annotator.cc b/components/optimization_guide/content/browser/test_page_content_annotator.cc
index c036899..563cc9c 100644
--- a/components/optimization_guide/content/browser/test_page_content_annotator.cc
+++ b/components/optimization_guide/content/browser/test_page_content_annotator.cc
@@ -12,6 +12,8 @@
 void TestPageContentAnnotator::Annotate(BatchAnnotationCallback callback,
                                         const std::vector<std::string>& inputs,
                                         AnnotationType annotation_type) {
+  annotation_requests_.emplace_back(std::make_pair(inputs, annotation_type));
+
   std::vector<BatchAnnotationResult> results;
 
   if (annotation_type == AnnotationType::kPageTopics) {
@@ -90,9 +92,15 @@
   visibility_scores_for_input_ = visibility_scores_for_input;
 }
 
+bool TestPageContentAnnotator::ModelRequestedForType(
+    AnnotationType type) const {
+  return model_requests_.contains(type);
+}
+
 void TestPageContentAnnotator::RequestAndNotifyWhenModelAvailable(
     AnnotationType type,
     base::OnceCallback<void(bool)> callback) {
+  model_requests_.insert(type);
   std::move(callback).Run(true);
 }
 
diff --git a/components/optimization_guide/content/browser/test_page_content_annotator.h b/components/optimization_guide/content/browser/test_page_content_annotator.h
index 4004d47..713e945 100644
--- a/components/optimization_guide/content/browser/test_page_content_annotator.h
+++ b/components/optimization_guide/content/browser/test_page_content_annotator.h
@@ -40,6 +40,16 @@
       const absl::optional<ModelInfo>& model_info,
       const base::flat_map<std::string, double>& visibility_scores_for_input);
 
+  // Returns true iff |RequestAndNotifyWhenModelAvailable| was called for
+  // |type|.
+  bool ModelRequestedForType(AnnotationType type) const;
+
+  using AnnotateInputsAndType =
+      std::pair<std::vector<std::string>, AnnotationType>;
+  const std::vector<AnnotateInputsAndType>& annotation_requests() const {
+    return annotation_requests_;
+  }
+
   // PageContentAnnotator:
   void Annotate(BatchAnnotationCallback callback,
                 const std::vector<std::string>& inputs,
@@ -60,6 +70,10 @@
 
   absl::optional<ModelInfo> visibility_scores_model_info_;
   base::flat_map<std::string, double> visibility_scores_for_input_;
+
+  std::vector<AnnotateInputsAndType> annotation_requests_;
+
+  base::flat_set<AnnotationType> model_requests_;
 };
 
 }  // namespace optimization_guide
diff --git a/components/optimization_guide/core/BUILD.gn b/components/optimization_guide/core/BUILD.gn
index 34917144..1b844bf 100644
--- a/components/optimization_guide/core/BUILD.gn
+++ b/components/optimization_guide/core/BUILD.gn
@@ -105,6 +105,8 @@
     "optimization_guide_prefs.h",
     "optimization_guide_switches.cc",
     "optimization_guide_switches.h",
+    "page_content_annotation_type.cc",
+    "page_content_annotation_type.h",
   ]
   public_deps = [ "//base" ]
   deps = [
@@ -326,6 +328,8 @@
       "optimization_guide_switches.h",
       "optimization_guide_test_util.cc",
       "optimization_guide_test_util.h",
+      "page_content_annotation_type.cc",
+      "page_content_annotation_type.h",
     ]
     deps = [
       "//base",
diff --git a/components/optimization_guide/core/entity_annotator_native_library.cc b/components/optimization_guide/core/entity_annotator_native_library.cc
index 27759c3d..0bb123b 100644
--- a/components/optimization_guide/core/entity_annotator_native_library.cc
+++ b/components/optimization_guide/core/entity_annotator_native_library.cc
@@ -235,6 +235,16 @@
               native_library_,
               "OptimizationGuideEntityMetadataGetHumanReadableCategoryScoreAtIn"
               "dex"));
+  entity_metadata_get_human_readable_aliases_count_func_ =
+      reinterpret_cast<EntityMetadataGetHumanReadableAliasesCountFunc>(
+          base::GetFunctionPointerFromNativeLibrary(
+              native_library_,
+              "OptimizationGuideEntityMetadataGetHumanReadableAliasesCount"));
+  entity_metadata_get_human_readable_alias_at_index_func_ =
+      reinterpret_cast<EntityMetadataGetHumanReadableAliasAtIndexFunc>(
+          base::GetFunctionPointerFromNativeLibrary(
+              native_library_,
+              "OptimizationGuideEntityMetadataGetHumanReadableAliasAtIndex"));
 }
 
 DISABLE_CFI_ICALL
@@ -255,7 +265,9 @@
          entity_metadata_get_human_readable_name_func_ &&
          entity_metadata_get_human_readable_categories_count_func_ &&
          entity_metadata_get_human_readable_category_name_at_index_func_ &&
-         entity_metadata_get_human_readable_category_score_at_index_func_;
+         entity_metadata_get_human_readable_category_score_at_index_func_ &&
+         entity_metadata_get_human_readable_aliases_count_func_ &&
+         entity_metadata_get_human_readable_alias_at_index_func_;
 }
 
 DISABLE_CFI_ICALL
@@ -495,6 +507,14 @@
             og_entity_metadata, i);
     entity_metadata.human_readable_categories[category_name] = category_score;
   }
+  int32_t human_readable_aliases_count =
+      entity_metadata_get_human_readable_aliases_count_func_(
+          og_entity_metadata);
+  for (int32_t i = 0; i < human_readable_aliases_count; i++) {
+    entity_metadata.human_readable_aliases.push_back(
+        entity_metadata_get_human_readable_alias_at_index_func_(
+            og_entity_metadata, i));
+  }
   return entity_metadata;
 }
 
diff --git a/components/optimization_guide/core/entity_annotator_native_library.h b/components/optimization_guide/core/entity_annotator_native_library.h
index a397ed88..9b6b39b 100644
--- a/components/optimization_guide/core/entity_annotator_native_library.h
+++ b/components/optimization_guide/core/entity_annotator_native_library.h
@@ -179,6 +179,14 @@
   EntityMetadataGetHumanReadableCategoryScoreAtIndexFunc
       entity_metadata_get_human_readable_category_score_at_index_func_ =
           nullptr;
+  using EntityMetadataGetHumanReadableAliasesCountFunc =
+      int32_t (*)(const void*);
+  EntityMetadataGetHumanReadableAliasesCountFunc
+      entity_metadata_get_human_readable_aliases_count_func_ = nullptr;
+  using EntityMetadataGetHumanReadableAliasAtIndexFunc =
+      const char* (*)(const void*, int32_t);
+  EntityMetadataGetHumanReadableCategoryNameAtIndexFunc
+      entity_metadata_get_human_readable_alias_at_index_func_ = nullptr;
 };
 
 }  // namespace optimization_guide
diff --git a/components/optimization_guide/core/entity_metadata.cc b/components/optimization_guide/core/entity_metadata.cc
index 720cf322..9609edd 100644
--- a/components/optimization_guide/core/entity_metadata.cc
+++ b/components/optimization_guide/core/entity_metadata.cc
@@ -8,6 +8,7 @@
 #include <string>
 #include <vector>
 
+#include "base/json/json_writer.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 
@@ -17,22 +18,42 @@
 EntityMetadata::EntityMetadata(
     const std::string& entity_id,
     const std::string& human_readable_name,
-    const base::flat_map<std::string, float>& human_readable_categories)
+    const base::flat_map<std::string, float>& human_readable_categories,
+    const std::vector<std::string>& human_readable_aliases)
     : entity_id(entity_id),
       human_readable_name(human_readable_name),
-      human_readable_categories(human_readable_categories) {}
+      human_readable_categories(human_readable_categories),
+      human_readable_aliases(human_readable_aliases) {}
 EntityMetadata::EntityMetadata(const EntityMetadata&) = default;
 EntityMetadata::~EntityMetadata() = default;
 
+base::Value EntityMetadata::AsValue() const {
+  base::Value::List categories;
+  for (const auto& iter : human_readable_categories) {
+    base::Value::Dict category;
+    category.Set("category", iter.first);
+    category.Set("score", iter.second);
+    categories.Append(std::move(category));
+  }
+
+  base::Value::Dict metadata;
+  metadata.Set("entity_id", entity_id);
+  metadata.Set("human_readable_name", human_readable_name);
+  metadata.Set("categories", std::move(categories));
+
+  return base::Value(std::move(metadata));
+}
+
 std::string EntityMetadata::ToString() const {
   std::vector<std::string> categories;
   for (const auto& iter : human_readable_categories) {
     categories.push_back(
         base::StringPrintf("{%s,%f}", iter.first.c_str(), iter.second));
   }
-  return base::StringPrintf("EntityMetadata{%s, %s, {%s}}", entity_id.c_str(),
-                            human_readable_name.c_str(),
-                            base::JoinString(categories, ",").c_str());
+  return base::StringPrintf(
+      "EntityMetadata{%s, %s, {%s}, {%s}}", entity_id.c_str(),
+      human_readable_name.c_str(), base::JoinString(categories, ",").c_str(),
+      base::JoinString(human_readable_aliases, ",").c_str());
 }
 
 std::ostream& operator<<(std::ostream& out, const EntityMetadata& md) {
@@ -43,7 +64,8 @@
 bool operator==(const EntityMetadata& lhs, const EntityMetadata& rhs) {
   return lhs.entity_id == rhs.entity_id &&
          lhs.human_readable_name == rhs.human_readable_name &&
-         lhs.human_readable_categories == rhs.human_readable_categories;
+         lhs.human_readable_categories == rhs.human_readable_categories &&
+         lhs.human_readable_aliases == rhs.human_readable_aliases;
 }
 
 ScoredEntityMetadata::ScoredEntityMetadata() = default;
@@ -54,6 +76,13 @@
     default;
 ScoredEntityMetadata::~ScoredEntityMetadata() = default;
 
+base::Value ScoredEntityMetadata::AsValue() const {
+  base::Value::Dict scored_md;
+  scored_md.Set("metadata", metadata.AsValue());
+  scored_md.Set("score", score);
+  return base::Value(std::move(scored_md));
+}
+
 std::string ScoredEntityMetadata::ToString() const {
   return base::StringPrintf("ScoredEntityMetadata{%f, %s}", score,
                             metadata.ToString().c_str());
diff --git a/components/optimization_guide/core/entity_metadata.h b/components/optimization_guide/core/entity_metadata.h
index 73cdfa0..3c60f265 100644
--- a/components/optimization_guide/core/entity_metadata.h
+++ b/components/optimization_guide/core/entity_metadata.h
@@ -6,8 +6,10 @@
 #define COMPONENTS_OPTIMIZATION_GUIDE_CORE_ENTITY_METADATA_H_
 
 #include <string>
+#include <vector>
 
 #include "base/containers/flat_map.h"
+#include "base/values.h"
 
 namespace optimization_guide {
 
@@ -17,7 +19,8 @@
   EntityMetadata(
       const std::string& entity_id,
       const std::string& human_readable_name,
-      const base::flat_map<std::string, float>& human_readable_categories);
+      const base::flat_map<std::string, float>& human_readable_categories,
+      const std::vector<std::string>& human_readable_aliases = {});
   EntityMetadata(const EntityMetadata&);
   ~EntityMetadata();
 
@@ -32,8 +35,13 @@
   // contain the top 5 entries based on confidence score.
   base::flat_map<std::string, float> human_readable_categories;
 
+  // The ordered set of aliases for this entity in the user's locale.
+  std::vector<std::string> human_readable_aliases;
+
   std::string ToString() const;
 
+  base::Value AsValue() const;
+
   friend std::ostream& operator<<(std::ostream& out, const EntityMetadata& md);
   friend bool operator==(const EntityMetadata& lhs, const EntityMetadata& rhs);
 };
@@ -53,6 +61,8 @@
 
   std::string ToString() const;
 
+  base::Value AsValue() const;
+
   friend std::ostream& operator<<(std::ostream& out,
                                   const ScoredEntityMetadata& md);
   friend bool operator==(const ScoredEntityMetadata& lhs,
diff --git a/components/optimization_guide/core/model_handler.h b/components/optimization_guide/core/model_handler.h
index 910c556..6b4af98 100644
--- a/components/optimization_guide/core/model_handler.h
+++ b/components/optimization_guide/core/model_handler.h
@@ -56,11 +56,15 @@
         true);
 
     handler_created_time_ = base::TimeTicks::Now();
-    model_provider_->AddObserverForOptimizationTargetModel(
-        optimization_target_, model_metadata, this);
+
     model_executor_->InitializeAndMoveToExecutionThread(
         model_inference_timeout, optimization_target_,
         model_executor_task_runner_, base::SequencedTaskRunnerHandle::Get());
+
+    // Run this after the executor is initialized in case the model is already
+    // available.
+    model_provider_->AddObserverForOptimizationTargetModel(
+        optimization_target_, model_metadata, this);
   }
   ~ModelHandler() override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
diff --git a/components/optimization_guide/core/optimization_guide_features.cc b/components/optimization_guide/core/optimization_guide_features.cc
index 66aec263..afffb7d 100644
--- a/components/optimization_guide/core/optimization_guide_features.cc
+++ b/components/optimization_guide/core/optimization_guide_features.cc
@@ -154,8 +154,8 @@
 const base::Feature kUseLocalPageEntitiesMetadataProvider{
     "UseLocalPageEntitiesMetadataProvider", base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kBatchAnnotationsValidation{
-    "BatchAnnotationsValidation", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kPageContentAnnotationsValidation{
+    "PageContentAnnotationsValidation", base::FEATURE_DISABLED_BY_DEFAULT};
 
 const base::Feature kPreventLongRunningPredictionModels{
     "PreventLongRunningPredictionModels", base::FEATURE_ENABLED_BY_DEFAULT};
@@ -566,25 +566,45 @@
                                           "annotate_visit_batch_size", 1));
 }
 
-bool BatchAnnotationsValidationEnabled() {
-  return base::FeatureList::IsEnabled(kBatchAnnotationsValidation);
+bool PageContentAnnotationValidationEnabledForType(AnnotationType type) {
+  if (base::FeatureList::IsEnabled(kPageContentAnnotationsValidation)) {
+    if (GetFieldTrialParamByFeatureAsBool(kPageContentAnnotationsValidation,
+                                          AnnotationTypeToString(type),
+                                          false)) {
+      return true;
+    }
+  }
+
+  base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
+  switch (type) {
+    case AnnotationType::kPageTopics:
+      return cmd->HasSwitch(
+          switches::kPageContentAnnotationsValidationPageTopics);
+    case AnnotationType::kPageEntities:
+      return cmd->HasSwitch(
+          switches::kPageContentAnnotationsValidationPageEntities);
+    case AnnotationType::kContentVisibility:
+      return cmd->HasSwitch(
+          switches::kPageContentAnnotationsValidationContentVisibility);
+    default:
+      NOTREACHED();
+      break;
+  }
+
+  return false;
 }
 
-base::TimeDelta BatchAnnotationValidationStartupDelay() {
-  return base::Seconds(
-      std::max(1, GetFieldTrialParamByFeatureAsInt(kBatchAnnotationsValidation,
-                                                   "startup_delay", 30)));
+base::TimeDelta PageContentAnnotationValidationStartupDelay() {
+  return switches::PageContentAnnotationsValidationStartupDelay().value_or(
+      base::Seconds(std::max(
+          1, GetFieldTrialParamByFeatureAsInt(kPageContentAnnotationsValidation,
+                                              "startup_delay", 30))));
 }
 
-size_t BatchAnnotationsValidationBatchSize() {
-  int batch_size = GetFieldTrialParamByFeatureAsInt(kBatchAnnotationsValidation,
-                                                    "batch_size", 25);
-  return std::max(1, batch_size);
-}
-
-bool BatchAnnotationsValidationUsePageTopics() {
-  return GetFieldTrialParamByFeatureAsBool(kBatchAnnotationsValidation,
-                                           "use_page_topics", false);
+size_t PageContentAnnotationsValidationBatchSize() {
+  return switches::PageContentAnnotationsValidationBatchSize().value_or(
+      std::max(1, GetFieldTrialParamByFeatureAsInt(
+                      kPageContentAnnotationsValidation, "batch_size", 25)));
 }
 
 size_t MaxVisitAnnotationCacheSize() {
diff --git a/components/optimization_guide/core/optimization_guide_features.h b/components/optimization_guide/core/optimization_guide_features.h
index 72fc89e..9c2e3663 100644
--- a/components/optimization_guide/core/optimization_guide_features.h
+++ b/components/optimization_guide/core/optimization_guide_features.h
@@ -12,6 +12,7 @@
 #include "base/feature_list.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/time/time.h"
+#include "components/optimization_guide/core/page_content_annotation_type.h"
 #include "components/optimization_guide/proto/hints.pb.h"
 #include "components/optimization_guide/proto/models.pb.h"
 #include "net/nqe/effective_connection_type.h"
@@ -40,7 +41,7 @@
 extern const base::Feature kPageEntitiesModelResetOnShutdown;
 extern const base::Feature kPageEntitiesModelBypassFilters;
 extern const base::Feature kUseLocalPageEntitiesMetadataProvider;
-extern const base::Feature kBatchAnnotationsValidation;
+extern const base::Feature kPageContentAnnotationsValidation;
 extern const base::Feature kPreventLongRunningPredictionModels;
 extern const base::Feature kOverrideNumThreadsForModelExecution;
 extern const base::Feature kOptGuideEnableXNNPACKDelegateWithTFLite;
@@ -280,19 +281,16 @@
 // immediately after requested.
 size_t AnnotateVisitBatchSize();
 
-// Whether the batch annotation validation feature is enabled.
-bool BatchAnnotationsValidationEnabled();
+// Whether the page content annotation validation feature or command line flag
+// is enabled for the given annotation type.
+bool PageContentAnnotationValidationEnabledForType(AnnotationType type);
 
-// The time period between browser start and running a running batch annotation
-// validation.
-base::TimeDelta BatchAnnotationValidationStartupDelay();
+// The time period between browser start and running a running page content
+// annotation validation.
+base::TimeDelta PageContentAnnotationValidationStartupDelay();
 
-// The size of batches to run for validation.
-size_t BatchAnnotationsValidationBatchSize();
-
-// True if the batch annotations feature should use the PageTopics annotation
-// type instead of ContentVisibility.
-bool BatchAnnotationsValidationUsePageTopics();
+// The size of batches to run for page content validation.
+size_t PageContentAnnotationsValidationBatchSize();
 
 // The maximum size of the visit annotation cache.
 size_t MaxVisitAnnotationCacheSize();
diff --git a/components/optimization_guide/core/optimization_guide_switches.cc b/components/optimization_guide/core/optimization_guide_switches.cc
index f8f4c4a..b0cb348 100644
--- a/components/optimization_guide/core/optimization_guide_switches.cc
+++ b/components/optimization_guide/core/optimization_guide_switches.cc
@@ -7,6 +7,7 @@
 #include "base/base64.h"
 #include "base/command_line.h"
 #include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
 #include "build/build_config.h"
 #include "components/optimization_guide/proto/hints.pb.h"
@@ -84,6 +85,22 @@
 const char kPageContentAnnotationsLoggingEnabled[] =
     "enable-page-content-annotations-logging";
 
+const char kPageContentAnnotationsValidationStartupDelaySeconds[] =
+    "page-content-annotations-validation-startup-delay-seconds";
+
+const char kPageContentAnnotationsValidationBatchSizeOverride[] =
+    "page-content-annotations-validation-batch-size";
+
+// Enables the specific annotation type to run validation at startup after a
+// delay. A comma separated list of inputs can be given as a value which will be
+// used as input for the validation job.
+const char kPageContentAnnotationsValidationPageTopics[] =
+    "page-content-annotations-validation-page-topics";
+const char kPageContentAnnotationsValidationPageEntities[] =
+    "page-content-annotations-validation-page-entities";
+const char kPageContentAnnotationsValidationContentVisibility[] =
+    "page-content-annotations-validation-content-visibility";
+
 bool IsHintComponentProcessingDisabled() {
   return base::CommandLine::ForCurrentProcess()->HasSwitch(kHintsProtoOverride);
 }
@@ -194,5 +211,77 @@
       kPageContentAnnotationsLoggingEnabled);
 }
 
+absl::optional<base::TimeDelta> PageContentAnnotationsValidationStartupDelay() {
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  if (!command_line->HasSwitch(
+          kPageContentAnnotationsValidationStartupDelaySeconds)) {
+    return absl::nullopt;
+  }
+
+  std::string value = command_line->GetSwitchValueASCII(
+      kPageContentAnnotationsValidationStartupDelaySeconds);
+
+  size_t seconds = 0;
+  if (base::StringToSizeT(value, &seconds)) {
+    return base::Seconds(seconds);
+  }
+  return absl::nullopt;
+}
+
+absl::optional<size_t> PageContentAnnotationsValidationBatchSize() {
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  if (!command_line->HasSwitch(
+          kPageContentAnnotationsValidationBatchSizeOverride)) {
+    return absl::nullopt;
+  }
+
+  std::string value = command_line->GetSwitchValueASCII(
+      kPageContentAnnotationsValidationBatchSizeOverride);
+
+  size_t size = 0;
+  if (base::StringToSizeT(value, &size)) {
+    return size;
+  }
+  return absl::nullopt;
+}
+
+bool LogPageContentAnnotationsValidationToConsole() {
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  return command_line->HasSwitch(kPageContentAnnotationsValidationPageTopics) ||
+         command_line->HasSwitch(
+             kPageContentAnnotationsValidationPageEntities) ||
+         command_line->HasSwitch(
+             kPageContentAnnotationsValidationContentVisibility);
+}
+
+absl::optional<std::vector<std::string>>
+PageContentAnnotationsValidationInputForType(AnnotationType type) {
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+
+  std::string value;
+  switch (type) {
+    case AnnotationType::kPageTopics:
+      value = command_line->GetSwitchValueASCII(
+          kPageContentAnnotationsValidationPageTopics);
+      break;
+    case AnnotationType::kPageEntities:
+      value = command_line->GetSwitchValueASCII(
+          kPageContentAnnotationsValidationPageEntities);
+      break;
+    case AnnotationType::kContentVisibility:
+      value = command_line->GetSwitchValueASCII(
+          kPageContentAnnotationsValidationContentVisibility);
+      break;
+    default:
+      break;
+  }
+  if (value.empty()) {
+    return absl::nullopt;
+  }
+
+  return base::SplitString(value, ",", base::KEEP_WHITESPACE,
+                           base::SPLIT_WANT_ALL);
+}
+
 }  // namespace switches
 }  // namespace optimization_guide
diff --git a/components/optimization_guide/core/optimization_guide_switches.h b/components/optimization_guide/core/optimization_guide_switches.h
index ea0f552..2d1ce34 100644
--- a/components/optimization_guide/core/optimization_guide_switches.h
+++ b/components/optimization_guide/core/optimization_guide_switches.h
@@ -9,6 +9,8 @@
 #include <string>
 #include <vector>
 
+#include "base/time/time.h"
+#include "components/optimization_guide/core/page_content_annotation_type.h"
 #include "components/optimization_guide/proto/models.pb.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
@@ -34,6 +36,11 @@
 extern const char kDebugLoggingEnabled[];
 extern const char kModelValidate[];
 extern const char kPageContentAnnotationsLoggingEnabled[];
+extern const char kPageContentAnnotationsValidationStartupDelaySeconds[];
+extern const char kPageContentAnnotationsValidationBatchSizeOverride[];
+extern const char kPageContentAnnotationsValidationPageTopics[];
+extern const char kPageContentAnnotationsValidationPageEntities[];
+extern const char kPageContentAnnotationsValidationContentVisibility[];
 
 // Returns whether the hint component should be processed.
 // Available hint components are only processed if a proto override isn't being
@@ -91,6 +98,24 @@
 // Returns true if page content annotations input should be logged.
 bool ShouldLogPageContentAnnotationsInput();
 
+// Returns the delay to use for page content annotations validation, if given
+// and valid on the command line.
+absl::optional<base::TimeDelta> PageContentAnnotationsValidationStartupDelay();
+
+// Returns the size of the batch to use for page content annotations validation,
+// if given and valid on the command line.
+absl::optional<size_t> PageContentAnnotationsValidationBatchSize();
+
+// Whether the result of page content annotations validation should be sent to
+// the console. True when any one of the corresponding command line flags is
+// enabled.
+bool LogPageContentAnnotationsValidationToConsole();
+
+// Returns a set on inputs to run the validation on for the given |type|,
+// using comma separated input from the command line.
+absl::optional<std::vector<std::string>>
+PageContentAnnotationsValidationInputForType(AnnotationType type);
+
 }  // namespace switches
 }  // namespace optimization_guide
 
diff --git a/components/optimization_guide/core/page_content_annotation_type.cc b/components/optimization_guide/core/page_content_annotation_type.cc
new file mode 100644
index 0000000..d0fd790a
--- /dev/null
+++ b/components/optimization_guide/core/page_content_annotation_type.cc
@@ -0,0 +1,25 @@
+// Copyright 2022 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_content_annotation_type.h"
+
+namespace optimization_guide {
+
+// Each of these string values is used in UMA histograms so please update the
+// variants there when any changes are made.
+// //tools/metrics/histograms/metadata/optimization/histograms.xml
+std::string AnnotationTypeToString(AnnotationType type) {
+  switch (type) {
+    case AnnotationType::kUnknown:
+      return "Unknown";
+    case AnnotationType::kPageTopics:
+      return "PageTopics";
+    case AnnotationType::kContentVisibility:
+      return "ContentVisibility";
+    case AnnotationType::kPageEntities:
+      return "PageEntities";
+  }
+}
+
+}  // namespace optimization_guide
\ No newline at end of file
diff --git a/components/optimization_guide/core/page_content_annotation_type.h b/components/optimization_guide/core/page_content_annotation_type.h
new file mode 100644
index 0000000..716a95f
--- /dev/null
+++ b/components/optimization_guide/core/page_content_annotation_type.h
@@ -0,0 +1,37 @@
+// Copyright 2022 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_CONTENT_ANNOTATION_TYPE_H_
+#define COMPONENTS_OPTIMIZATION_GUIDE_CORE_PAGE_CONTENT_ANNOTATION_TYPE_H_
+
+#include <string>
+
+namespace optimization_guide {
+
+// The type of annotation that is being done on the given input.
+//
+// Each of these is used in UMA histograms so please update the variants there
+// when any changes are made.
+// //tools/metrics/histograms/metadata/optimization/histograms.xml
+enum class AnnotationType {
+  kUnknown,
+
+  // The input will be annotated with the topics on the page. These topics are
+  // fairly high-level like "sports" or "news".
+  kPageTopics,
+
+  // The input will be annotated for the visibility of the content.
+  kContentVisibility,
+
+  // The input will be annotated with the entities on the page. If the entities
+  // will be persisted, make sure that only the entity IDs are persisted. To map
+  // the IDs back to human-readable strings, use `EntityMetadataProvider`.
+  kPageEntities,
+};
+
+std::string AnnotationTypeToString(AnnotationType type);
+
+}  // namespace optimization_guide
+
+#endif  // COMPONENTS_OPTIMIZATION_GUIDE_CORE_PAGE_CONTENT_ANNOTATION_TYPE_H_
\ No newline at end of file
diff --git a/components/optimization_guide/core/page_content_annotations_common.cc b/components/optimization_guide/core/page_content_annotations_common.cc
index be995c4d..2c7eda1 100644
--- a/components/optimization_guide/core/page_content_annotations_common.cc
+++ b/components/optimization_guide/core/page_content_annotations_common.cc
@@ -8,28 +8,14 @@
 #include <ostream>
 
 #include "base/check_op.h"
+#include "base/json/json_writer.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 
 namespace optimization_guide {
 
-// Each of these string values is used in UMA histograms so please update the
-// variants there when any changes are made.
-// //tools/metrics/histograms/metadata/optimization/histograms.xml
-std::string AnnotationTypeToString(AnnotationType type) {
-  switch (type) {
-    case AnnotationType::kUnknown:
-      return "Unknown";
-    case AnnotationType::kPageTopics:
-      return "PageTopics";
-    case AnnotationType::kContentVisibility:
-      return "ContentVisibility";
-    case AnnotationType::kPageEntities:
-      return "PageEntities";
-  }
-}
-
 WeightedIdentifier::WeightedIdentifier(int32_t value, double weight)
     : value_(value), weight_(weight) {
   DCHECK_GE(weight_, 0.0);
@@ -48,6 +34,13 @@
   return base::StringPrintf("WeightedIdentifier{%d,%f}", value(), weight());
 }
 
+base::Value WeightedIdentifier::AsValue() const {
+  base::Value::Dict wi;
+  wi.Set("value", value());
+  wi.Set("weight", weight());
+  return base::Value(std::move(wi));
+}
+
 std::ostream& operator<<(std::ostream& stream, const WeightedIdentifier& ws) {
   stream << ws.ToString();
   return stream;
@@ -71,6 +64,42 @@
   }
 }
 
+base::Value BatchAnnotationResult::AsValue() const {
+  base::Value::Dict result;
+  result.Set("input", input());
+  result.Set("type", AnnotationTypeToString(type()));
+
+  if (topics()) {
+    base::Value::List list;
+    for (const auto& wi : *topics()) {
+      list.Append(wi.AsValue());
+    }
+    result.Set("topics", std::move(list));
+  }
+
+  if (entities()) {
+    base::Value::List list;
+    for (const auto& md : *entities()) {
+      list.Append(md.AsValue());
+    }
+    result.Set("entities", std::move(list));
+  }
+
+  if (visibility_score()) {
+    result.Set("visibility_score", *visibility_score());
+  }
+
+  return base::Value(std::move(result));
+}
+
+std::string BatchAnnotationResult::ToJSON() const {
+  std::string json;
+  if (base::JSONWriter::Write(AsValue(), &json)) {
+    return json;
+  }
+  return std::string();
+}
+
 std::string BatchAnnotationResult::ToString() const {
   std::string output = "nullopt";
   if (topics_) {
@@ -90,10 +119,10 @@
   }
   return base::StringPrintf(
       "BatchAnnotationResult{"
-      "\"<input with length %zu>\", "
+      "\"%s\", "
       "type: %s, "
       "output: %s}",
-      input_.size(), AnnotationTypeToString(type_).c_str(), output.c_str());
+      input_.c_str(), AnnotationTypeToString(type_).c_str(), output.c_str());
 }
 
 std::ostream& operator<<(std::ostream& stream,
diff --git a/components/optimization_guide/core/page_content_annotations_common.h b/components/optimization_guide/core/page_content_annotations_common.h
index 937992e..d53dc3c9c 100644
--- a/components/optimization_guide/core/page_content_annotations_common.h
+++ b/components/optimization_guide/core/page_content_annotations_common.h
@@ -9,34 +9,13 @@
 #include <vector>
 
 #include "base/callback.h"
+#include "base/values.h"
 #include "components/optimization_guide/core/entity_metadata.h"
+#include "components/optimization_guide/core/page_content_annotation_type.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
 
 namespace optimization_guide {
 
-// The type of annotation that is being done on the given input.
-//
-// Each of these is used in UMA histograms so please update the variants there
-// when any changes are made.
-// //tools/metrics/histograms/metadata/optimization/histograms.xml
-enum class AnnotationType {
-  kUnknown,
-
-  // The input will be annotated with the topics on the page. These topics are
-  // fairly high-level like "sports" or "news".
-  kPageTopics,
-
-  // The input will be annotated for the visibility of the content.
-  kContentVisibility,
-
-  // The input will be annotated with the entities on the page. If the entities
-  // will be persisted, make sure that only the entity IDs are persisted. To map
-  // the IDs back to human-readable strings, use `EntityMetadataProvider`.
-  kPageEntities,
-};
-
-std::string AnnotationTypeToString(AnnotationType type);
-
 // A weighted ID value.
 class WeightedIdentifier {
  public:
@@ -49,6 +28,8 @@
 
   std::string ToString() const;
 
+  base::Value AsValue() const;
+
   bool operator==(const WeightedIdentifier& other) const;
 
   friend std::ostream& operator<<(std::ostream& stream,
@@ -100,6 +81,9 @@
   absl::optional<double> visibility_score() const { return visibility_score_; }
 
   std::string ToString() const;
+  std::string ToJSON() const;
+
+  base::Value AsValue() const;
 
   bool operator==(const BatchAnnotationResult& other) const;
 
diff --git a/components/optimization_guide/core/tflite_model_executor.h b/components/optimization_guide/core/tflite_model_executor.h
index e6a3939..e321196 100644
--- a/components/optimization_guide/core/tflite_model_executor.h
+++ b/components/optimization_guide/core/tflite_model_executor.h
@@ -118,7 +118,8 @@
   // Called when a model file is available to load. Depending on feature flags,
   // the model may or may not be immediately loaded.
   void UpdateModelFile(const base::FilePath& file_path) override {
-    DCHECK(execution_task_runner_->RunsTasksInCurrentSequence());
+    DCHECK(execution_task_runner_ &&
+           execution_task_runner_->RunsTasksInCurrentSequence());
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
     UnloadModel();
diff --git a/components/page_load_metrics/browser/page_load_metrics_observer_interface.h b/components/page_load_metrics/browser/page_load_metrics_observer_interface.h
index 0c92886..cb20439 100644
--- a/components/page_load_metrics/browser/page_load_metrics_observer_interface.h
+++ b/components/page_load_metrics/browser/page_load_metrics_observer_interface.h
@@ -64,7 +64,8 @@
   // The origin of the final URL for the request (final = after redirects).
   //
   // The full URL is not available, because in some cases the path and query
-  // be sanitized away - see https://crbug.com/973885.
+  // may be sanitized away - see https://crbug.com/973885.
+  // TODO(crbug.com/973885): use url::SchemeHostPort if applicable.
   const url::Origin origin_of_final_url;
 
   // The host (IP address) and port for the request.
@@ -131,6 +132,12 @@
   // receives forward metrics via FORWARD_OBSERVING, and returns STOP_OBSERVING,
   // It just stop observing forward metrics, and still see other callbacks for
   // the orinally bound page.
+  // Most events requiring preprocesses, such as lifecycle events, are forwarded
+  // to the outer page at the PageLoadTracker layer, and only events that are
+  // directly delivered to the observers need FORWARD_OBSERVING. See
+  // PageLoadMetricsForwardObserver to know which events need the observer layer
+  // forwarding. Eventually, we may treat all forwarding at the PageLoadTracker
+  // layer to deprecate the FORWARD_OBSERVING for simplicity.
   enum ObservePolicy {
     CONTINUE_OBSERVING,
     STOP_OBSERVING,
diff --git a/components/page_load_metrics/renderer/metrics_render_frame_observer.cc b/components/page_load_metrics/renderer/metrics_render_frame_observer.cc
index a0d3a1b..cba719ad 100644
--- a/components/page_load_metrics/renderer/metrics_render_frame_observer.cc
+++ b/components/page_load_metrics/renderer/metrics_render_frame_observer.cc
@@ -157,7 +157,7 @@
 }
 
 void MetricsRenderFrameObserver::DidStartResponse(
-    const GURL& response_url,
+    const url::SchemeHostPort& final_response_url,
     int request_id,
     const network::mojom::URLResponseHead& response_head,
     network::mojom::RequestDestination request_destination) {
@@ -168,10 +168,10 @@
     // case. There should be a guarantee that DidStartProvisionalLoad be called
     // before DidStartResponse for the frame request.
     provisional_frame_resource_data_use_->DidStartResponse(
-        response_url, request_id, response_head, request_destination);
+        final_response_url, request_id, response_head, request_destination);
   } else if (page_timing_metrics_sender_) {
     page_timing_metrics_sender_->DidStartResponse(
-        response_url, request_id, response_head, request_destination);
+        final_response_url, request_id, response_head, request_destination);
     UpdateResourceMetadata(request_id);
   }
 }
diff --git a/components/page_load_metrics/renderer/metrics_render_frame_observer.h b/components/page_load_metrics/renderer/metrics_render_frame_observer.h
index 396876f..ba1ba57 100644
--- a/components/page_load_metrics/renderer/metrics_render_frame_observer.h
+++ b/components/page_load_metrics/renderer/metrics_render_frame_observer.h
@@ -63,7 +63,7 @@
                           uint32_t all_call_count,
                           uint32_t ng_call_count) override;
   void DidStartResponse(
-      const GURL& response_url,
+      const url::SchemeHostPort& final_response_url,
       int request_id,
       const network::mojom::URLResponseHead& response_head,
       network::mojom::RequestDestination request_destination) override;
diff --git a/components/page_load_metrics/renderer/page_resource_data_use.cc b/components/page_load_metrics/renderer/page_resource_data_use.cc
index 5dc14f6..79e728b 100644
--- a/components/page_load_metrics/renderer/page_resource_data_use.cc
+++ b/components/page_load_metrics/renderer/page_resource_data_use.cc
@@ -8,6 +8,7 @@
 #include "services/network/public/mojom/url_response_head.mojom.h"
 #include "third_party/blink/public/common/loader/resource_type_util.h"
 #include "url/gurl.h"
+#include "url/scheme_host_port.h"
 
 namespace page_load_metrics {
 
@@ -30,7 +31,7 @@
 PageResourceDataUse::~PageResourceDataUse() = default;
 
 void PageResourceDataUse::DidStartResponse(
-    const GURL& response_url,
+    const url::SchemeHostPort& final_response_url,
     int resource_id,
     const network::mojom::URLResponseHead& response_head,
     network::mojom::RequestDestination request_destination) {
@@ -40,7 +41,7 @@
   mime_type_ = response_head.mime_type;
   if (response_head.was_fetched_via_cache)
     cache_type_ = mojom::CacheType::kHttp;
-  is_secure_scheme_ = response_url.SchemeIsCryptographic();
+  is_secure_scheme_ = GURL::SchemeIsCryptographic(final_response_url.scheme());
   is_primary_frame_resource_ =
       blink::IsRequestDestinationFrame(request_destination);
 }
diff --git a/components/page_load_metrics/renderer/page_resource_data_use.h b/components/page_load_metrics/renderer/page_resource_data_use.h
index 604b042..9c29051 100644
--- a/components/page_load_metrics/renderer/page_resource_data_use.h
+++ b/components/page_load_metrics/renderer/page_resource_data_use.h
@@ -15,6 +15,10 @@
 struct URLLoaderCompletionStatus;
 }  // namespace network
 
+namespace url {
+class SchemeHostPort;
+}  // namespace url
+
 namespace page_load_metrics {
 
 // PageResourceDataUse contains the data use information of one resource. Data
@@ -28,7 +32,7 @@
 
   ~PageResourceDataUse();
 
-  void DidStartResponse(const GURL& response_url,
+  void DidStartResponse(const url::SchemeHostPort& final_response_url,
                         int resource_id,
                         const network::mojom::URLResponseHead& response_head,
                         network::mojom::RequestDestination request_destination);
diff --git a/components/page_load_metrics/renderer/page_timing_metrics_sender.cc b/components/page_load_metrics/renderer/page_timing_metrics_sender.cc
index de2285c..ff29e47 100644
--- a/components/page_load_metrics/renderer/page_timing_metrics_sender.cc
+++ b/components/page_load_metrics/renderer/page_timing_metrics_sender.cc
@@ -125,7 +125,7 @@
 }
 
 void PageTimingMetricsSender::DidStartResponse(
-    const GURL& response_url,
+    const url::SchemeHostPort& final_response_url,
     int resource_id,
     const network::mojom::URLResponseHead& response_head,
     network::mojom::RequestDestination request_destination) {
@@ -135,7 +135,7 @@
       std::piecewise_construct, std::forward_as_tuple(resource_id),
       std::forward_as_tuple(std::make_unique<PageResourceDataUse>()));
   resource_it.first->second->DidStartResponse(
-      response_url, resource_id, response_head, request_destination);
+      final_response_url, resource_id, response_head, request_destination);
 }
 
 void PageTimingMetricsSender::DidReceiveTransferSizeUpdate(
diff --git a/components/page_load_metrics/renderer/page_timing_metrics_sender.h b/components/page_load_metrics/renderer/page_timing_metrics_sender.h
index 2535ea2..5bd2b62 100644
--- a/components/page_load_metrics/renderer/page_timing_metrics_sender.h
+++ b/components/page_load_metrics/renderer/page_timing_metrics_sender.h
@@ -58,7 +58,7 @@
                           uint32_t ng_call_count);
   void DidObserveMobileFriendlinessChanged(const blink::MobileFriendliness&);
 
-  void DidStartResponse(const GURL& response_url,
+  void DidStartResponse(const url::SchemeHostPort& final_response_url,
                         int resource_id,
                         const network::mojom::URLResponseHead& response_head,
                         network::mojom::RequestDestination request_destination);
diff --git a/components/policy/core/common/cloud/cloud_policy_client.cc b/components/policy/core/common/cloud/cloud_policy_client.cc
index c04d7375..68922c94 100644
--- a/components/policy/core/common/cloud/cloud_policy_client.cc
+++ b/components/policy/core/common/cloud/cloud_policy_client.cc
@@ -722,12 +722,11 @@
     return;
   }
 
-  std::unique_ptr<EncryptedReportingJobConfiguration> config =
-      std::make_unique<EncryptedReportingJobConfiguration>(
-          this, service()->configuration()->GetEncryptedReportingServerUrl(),
-          std::move(merging_payload),
-          base::BindOnce(&CloudPolicyClient::OnEncryptedReportUploadCompleted,
-                         weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+  auto config = std::make_unique<EncryptedReportingJobConfiguration>(
+      this, service()->configuration()->GetEncryptedReportingServerUrl(),
+      std::move(merging_payload),
+      base::BindOnce(&CloudPolicyClient::OnEncryptedReportUploadCompleted,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
   if (context.has_value()) {
     config->UpdateContext(std::move(context.value()));
   }
@@ -1433,7 +1432,7 @@
     StatusCallback callback,
     DeviceManagementService::Job* job,
     DeviceManagementStatus status,
-    int net_error,
+    int reponse_code,
     absl::optional<base::Value::Dict> response) {
   status_ = status;
   if (status != DM_STATUS_SUCCESS)
@@ -1450,8 +1449,9 @@
     ResponseCallback callback,
     DeviceManagementService::Job* job,
     DeviceManagementStatus status,
-    int net_error,
+    int reponse_code,
     absl::optional<base::Value::Dict> response) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (job == nullptr) {
     std::move(callback).Run(absl::nullopt);
     return;
diff --git a/components/policy/core/common/cloud/device_management_service_unittest.cc b/components/policy/core/common/cloud/device_management_service_unittest.cc
index eb78d87..02344b32 100644
--- a/components/policy/core/common/cloud/device_management_service_unittest.cc
+++ b/components/policy/core/common/cloud/device_management_service_unittest.cc
@@ -296,7 +296,7 @@
   MOCK_METHOD4(OnJobDone,
                void(DeviceManagementService::Job*,
                     DeviceManagementStatus,
-                    int,
+                    int /*net_error*/,
                     const std::string&));
 
   MOCK_METHOD2(OnJobRetry,
diff --git a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc
index 57481fe3..d761f81 100644
--- a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc
+++ b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.cc
@@ -46,7 +46,7 @@
     // failure to the callback.
     std::move(callback_).Run(/*job=*/nullptr,
                              DeviceManagementStatus::DM_STATUS_REQUEST_FAILED,
-                             /*net_error=*/418,
+                             /*response_code=*/418,
                              /*response_body=*/absl::nullopt);
   }
 }
diff --git a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h
index 73c9b99..06ec69b 100644
--- a/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h
+++ b/components/policy/core/common/cloud/encrypted_reporting_job_configuration.h
@@ -104,8 +104,6 @@
   std::string GetUmaString() const override;
 
  private:
-  friend class EncryptedReportingJobConfigurationTest;
-
   std::set<std::string> GetTopLevelKeyAllowList();
 };
 
diff --git a/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc b/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc
index ff0f372..4fc47b5 100644
--- a/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc
+++ b/components/policy/core/common/cloud/encrypted_reporting_job_configuration_unittest.cc
@@ -38,7 +38,7 @@
 namespace policy {
 
 namespace {
-constexpr uint64_t kGenerationId = 4321;
+constexpr int64_t kGenerationId = 4321;
 constexpr ::reporting::Priority kPriority = ::reporting::Priority::IMMEDIATE;
 
 // Default values for EncryptionInfo
@@ -55,9 +55,9 @@
 // Encryption settings request key
 constexpr char kAttachEncryptionSettingsKey[] = "attachEncryptionSettings";
 
-// Keys for EncrypedRecord
+// Keys for EncryptedRecord
 constexpr char kEncryptedWrappedRecordKey[] = "encryptedWrappedRecord";
-constexpr char kSequenceInformationKey[] = "sequencingInformation";
+constexpr char kSequenceInformationKey[] = "sequenceInformation";
 constexpr char kEncryptionInfoKey[] = "encryptionInfo";
 
 // Keys for internal encryption information dictionaries.
@@ -240,7 +240,7 @@
  protected:
   using MockCompleteCb = MockFunction<void(DeviceManagementService::Job* job,
                                            DeviceManagementStatus code,
-                                           int net_error,
+                                           int response_code,
                                            absl::optional<base::Value::Dict>)>;
   static base::Value::Dict GenerateContext(base::StringPiece key,
                                            base::StringPiece value) {
@@ -499,7 +499,8 @@
       *record_value.FindDictKey(kSequenceInformationKey), absl::nullopt);
 
   EXPECT_CALL(complete_cb_,
-              Call(&job_, DM_STATUS_SUCCESS, net::OK, Eq(ByRef(response))))
+              Call(&job_, DM_STATUS_SUCCESS, DeviceManagementService::kSuccess,
+                   Eq(ByRef(response))))
       .Times(1);
   EncryptedReportingJobConfiguration configuration(
       &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
@@ -518,15 +519,15 @@
 
 // Ensures that upload failure is handled correctly.
 TEST_F(EncryptedReportingJobConfigurationTest, OnURLLoadComplete_NetError) {
-  int net_error = net::ERR_CONNECTION_RESET;
-  EXPECT_CALL(complete_cb_, Call(&job_, DM_STATUS_REQUEST_FAILED, net_error,
+  EXPECT_CALL(complete_cb_, Call(&job_, DM_STATUS_REQUEST_FAILED, _,
                                  testing::Eq(absl::nullopt)))
       .Times(1);
   EncryptedReportingJobConfiguration configuration(
       &client_, service_.configuration()->GetEncryptedReportingServerUrl(),
       RequestPayloadBuilder().Build(),
       base::BindOnce(&MockCompleteCb::Call, base::Unretained(&complete_cb_)));
-  configuration.OnURLLoadComplete(&job_, net_error, 0, "");
+  configuration.OnURLLoadComplete(&job_, net::ERR_CONNECTION_RESET,
+                                  0 /* ignored */, "");
 }
 
 }  // namespace policy
diff --git a/components/policy/core/common/cloud/mock_device_management_service.cc b/components/policy/core/common/cloud/mock_device_management_service.cc
index a1fe17b..c2479584 100644
--- a/components/policy/core/common/cloud/mock_device_management_service.cc
+++ b/components/policy/core/common/cloud/mock_device_management_service.cc
@@ -301,9 +301,9 @@
                                              int net_error,
                                              int response_code,
                                              const std::string& response_body) {
-  DeviceManagementStatus code =
+  DeviceManagementStatus status =
       MapNetErrorAndResponseCodeToDMStatus(net_error, response_code);
-  std::move(callback_).Run(job, code, net_error, response_body);
+  std::move(callback_).Run(job, status, net_error, response_body);
 }
 
 }  // namespace policy
diff --git a/components/policy/core/common/cloud/realtime_reporting_job_configuration_unittest.cc b/components/policy/core/common/cloud/realtime_reporting_job_configuration_unittest.cc
index a2ec262..91aacdf 100644
--- a/components/policy/core/common/cloud/realtime_reporting_job_configuration_unittest.cc
+++ b/components/policy/core/common/cloud/realtime_reporting_job_configuration_unittest.cc
@@ -53,7 +53,7 @@
   MOCK_METHOD4(OnURLLoadComplete,
                void(DeviceManagementService::Job* job,
                     DeviceManagementStatus code,
-                    int net_error,
+                    int response_code,
                     absl::optional<base::Value::Dict>));
 };
 
@@ -220,7 +220,8 @@
 TEST_F(RealtimeReportingJobConfigurationTest, OnURLLoadComplete_Success) {
   base::Value::Dict response = CreateResponse({ids[0], ids[1], ids[2]}, {}, {});
   EXPECT_CALL(callback_observer_,
-              OnURLLoadComplete(&job_, DM_STATUS_SUCCESS, net::OK,
+              OnURLLoadComplete(&job_, DM_STATUS_SUCCESS,
+                                DeviceManagementService::kSuccess,
                                 testing::Eq(testing::ByRef(response))));
   configuration_->OnURLLoadComplete(&job_, net::OK,
                                     DeviceManagementService::kSuccess,
@@ -228,17 +229,18 @@
 }
 
 TEST_F(RealtimeReportingJobConfigurationTest, OnURLLoadComplete_NetError) {
-  int net_error = net::ERR_CONNECTION_RESET;
   EXPECT_CALL(callback_observer_,
-              OnURLLoadComplete(&job_, DM_STATUS_REQUEST_FAILED, net_error,
+              OnURLLoadComplete(&job_, DM_STATUS_REQUEST_FAILED, _,
                                 testing::Eq(absl::nullopt)));
-  configuration_->OnURLLoadComplete(&job_, net_error, 0, "");
+  configuration_->OnURLLoadComplete(&job_, net::ERR_CONNECTION_RESET,
+                                    0 /* ignored */, "");
 }
 
 TEST_F(RealtimeReportingJobConfigurationTest,
        OnURLLoadComplete_InvalidRequest) {
   EXPECT_CALL(callback_observer_,
-              OnURLLoadComplete(&job_, DM_STATUS_REQUEST_INVALID, net::OK,
+              OnURLLoadComplete(&job_, DM_STATUS_REQUEST_INVALID,
+                                DeviceManagementService::kInvalidArgument,
                                 testing::Eq(absl::nullopt)));
   configuration_->OnURLLoadComplete(
       &job_, net::OK, DeviceManagementService::kInvalidArgument, "");
@@ -249,7 +251,8 @@
   EXPECT_CALL(
       callback_observer_,
       OnURLLoadComplete(&job_, DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID,
-                        net::OK, testing::Eq(absl::nullopt)));
+                        DeviceManagementService::kInvalidAuthCookieOrDMToken,
+                        testing::Eq(absl::nullopt)));
   configuration_->OnURLLoadComplete(
       &job_, net::OK, DeviceManagementService::kInvalidAuthCookieOrDMToken, "");
 }
@@ -258,14 +261,16 @@
   EXPECT_CALL(
       callback_observer_,
       OnURLLoadComplete(&job_, DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED,
-                        net::OK, testing::Eq(absl::nullopt)));
+                        DeviceManagementService::kDeviceManagementNotAllowed,
+                        testing::Eq(absl::nullopt)));
   configuration_->OnURLLoadComplete(
       &job_, net::OK, DeviceManagementService::kDeviceManagementNotAllowed, "");
 }
 
 TEST_F(RealtimeReportingJobConfigurationTest, OnURLLoadComplete_TempError) {
   EXPECT_CALL(callback_observer_,
-              OnURLLoadComplete(&job_, DM_STATUS_TEMPORARY_UNAVAILABLE, net::OK,
+              OnURLLoadComplete(&job_, DM_STATUS_TEMPORARY_UNAVAILABLE,
+                                DeviceManagementService::kServiceUnavailable,
                                 testing::Eq(absl::nullopt)));
   configuration_->OnURLLoadComplete(
       &job_, net::OK, DeviceManagementService::kServiceUnavailable, "");
@@ -273,7 +278,8 @@
 
 TEST_F(RealtimeReportingJobConfigurationTest, OnURLLoadComplete_UnknownError) {
   EXPECT_CALL(callback_observer_,
-              OnURLLoadComplete(&job_, DM_STATUS_HTTP_STATUS_ERROR, net::OK,
+              OnURLLoadComplete(&job_, DM_STATUS_HTTP_STATUS_ERROR,
+                                DeviceManagementService::kInvalidURL,
                                 testing::Eq(absl::nullopt)));
   configuration_->OnURLLoadComplete(&job_, net::OK,
                                     DeviceManagementService::kInvalidURL, "");
diff --git a/components/policy/core/common/cloud/reporting_job_configuration_base.cc b/components/policy/core/common/cloud/reporting_job_configuration_base.cc
index 022064e9..fb034eee9 100644
--- a/components/policy/core/common/cloud/reporting_job_configuration_base.cc
+++ b/components/policy/core/common/cloud/reporting_job_configuration_base.cc
@@ -215,30 +215,30 @@
   // Parse the response even if |response_code| is not a success since the
   // response data may contain an error message.
   // Map the net_error/response_code to a DeviceManagementStatus.
-  DeviceManagementStatus code;
+  DeviceManagementStatus status;
   if (net_error != net::OK) {
-    code = DM_STATUS_REQUEST_FAILED;
+    status = DM_STATUS_REQUEST_FAILED;
   } else {
     switch (response_code) {
       case DeviceManagementService::kSuccess:
-        code = DM_STATUS_SUCCESS;
+        status = DM_STATUS_SUCCESS;
         break;
       case DeviceManagementService::kInvalidArgument:
-        code = DM_STATUS_REQUEST_INVALID;
+        status = DM_STATUS_REQUEST_INVALID;
         break;
       case DeviceManagementService::kInvalidAuthCookieOrDMToken:
-        code = DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID;
+        status = DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID;
         break;
       case DeviceManagementService::kDeviceManagementNotAllowed:
-        code = DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED;
+        status = DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED;
         break;
       default:
         // Handle all unknown 5xx HTTP error codes as temporary and any other
         // unknown error as one that needs more time to recover.
         if (response_code >= 500 && response_code <= 599)
-          code = DM_STATUS_TEMPORARY_UNAVAILABLE;
+          status = DM_STATUS_TEMPORARY_UNAVAILABLE;
         else
-          code = DM_STATUS_HTTP_STATUS_ERROR;
+          status = DM_STATUS_HTTP_STATUS_ERROR;
         break;
     }
   }
@@ -246,7 +246,8 @@
   auto response_dict = response && response->is_dict()
                            ? absl::make_optional(std::move(response->GetDict()))
                            : absl::nullopt;
-  std::move(callback_).Run(job, code, net_error, std::move(response_dict));
+  std::move(callback_).Run(job, status, response_code,
+                           std::move(response_dict));
 }
 
 DeviceManagementService::Job::RetryMethod
diff --git a/components/policy/core/common/cloud/reporting_job_configuration_base.h b/components/policy/core/common/cloud/reporting_job_configuration_base.h
index 46c568a..09ab650b 100644
--- a/components/policy/core/common/cloud/reporting_job_configuration_base.h
+++ b/components/policy/core/common/cloud/reporting_job_configuration_base.h
@@ -42,8 +42,8 @@
   // Callback used once the job is complete.
   using UploadCompleteCallback =
       base::OnceCallback<void(DeviceManagementService::Job* job,
-                              DeviceManagementStatus code,
-                              int net_error,
+                              DeviceManagementStatus status,
+                              int response_code,
                               absl::optional<base::Value::Dict>)>;
 
   // Builds a Device dictionary for uploading information about the device to
diff --git a/components/translate/content/android/java/src/org/chromium/components/translate/TranslateMessage.java b/components/translate/content/android/java/src/org/chromium/components/translate/TranslateMessage.java
index dbe944cb..66dc6cce 100644
--- a/components/translate/content/android/java/src/org/chromium/components/translate/TranslateMessage.java
+++ b/components/translate/content/android/java/src/org/chromium/components/translate/TranslateMessage.java
@@ -6,7 +6,9 @@
 
 import android.app.Activity;
 import android.content.Context;
+import android.database.DataSetObserver;
 import android.text.TextUtils;
+import android.view.View;
 
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
@@ -25,6 +27,7 @@
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modelutil.PropertyModel;
+import org.chromium.ui.widget.RectProvider;
 import org.chromium.ui.widget.Toast;
 
 import java.lang.ref.WeakReference;
@@ -103,6 +106,8 @@
                                     MessageIdentifier.TRANSLATE)
                             .with(MessageBannerProperties.ICON_RESOURCE_ID,
                                     R.drawable.infobar_translate_compact)
+                            .with(MessageBannerProperties.ICON_TINT_COLOR,
+                                    MessageBannerProperties.TINT_NONE)
                             .with(MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID,
                                     R.drawable.settings_cog)
                             .with(MessageBannerProperties.SECONDARY_MENU_BUTTON_DELEGATE,
@@ -186,14 +191,42 @@
                 menuItem.overflowMenuItemId, menuItem.languageCode, menuItem.hasCheckmark);
     }
 
-    private final class SecondaryMenuButtonDelegate implements ListMenuButtonDelegate {
+    private final class SecondaryMenuButtonDelegate
+            extends DataSetObserver implements ListMenuButtonDelegate {
+        /**
+         * Keeps track of the RectProvider supplied to anchor the AnchoredPopupWindow to the
+         * ListMenuButton. It's kept as a WeakReference so that this doesn't inadvertently extend
+         * the lifetime of the RectProvider and all of its references past the time when the popup
+         * window is dismissed.
+         */
+        private WeakReference<RectProvider> mRectProvider;
+
+        // ListMenuButtonDelegate implementation:
+        @Override
+        public RectProvider getRectProvider(View listMenuButton) {
+            RectProvider provider = ListMenuButtonDelegate.super.getRectProvider(listMenuButton);
+            mRectProvider = new WeakReference<RectProvider>(provider);
+            return provider;
+        }
+
         @Override
         public ListMenu getListMenu() {
-            return new TranslateMessageSecondaryMenu(mContext, TranslateMessage.this,
+            return new TranslateMessageSecondaryMenu(mContext, /*handler=*/TranslateMessage.this,
+                    /*dataSetObserver=*/this,
                     mNativeTranslateMessage == 0
                             ? null
                             : TranslateMessageJni.get().buildOverflowMenu(mNativeTranslateMessage));
         }
+
+        // DataSetObserver implementation:
+        @Override
+        public void onChanged() {
+            // If the mRectProvider is set, then call setRect() with the existing Rect in order to
+            // force it to notify its observer, which will cause the AnchoredPopupWindow to update
+            // its onscreen dimensions to fit the new menu items.
+            RectProvider provider = mRectProvider.get();
+            if (provider != null) provider.setRect(provider.getRect());
+        }
     }
 
     @NativeMethods
diff --git a/components/translate/content/android/java/src/org/chromium/components/translate/TranslateMessageSecondaryMenu.java b/components/translate/content/android/java/src/org/chromium/components/translate/TranslateMessageSecondaryMenu.java
index 033c40e..6b3bff1 100644
--- a/components/translate/content/android/java/src/org/chromium/components/translate/TranslateMessageSecondaryMenu.java
+++ b/components/translate/content/android/java/src/org/chromium/components/translate/TranslateMessageSecondaryMenu.java
@@ -5,6 +5,7 @@
 package org.chromium.components.translate;
 
 import android.content.Context;
+import android.database.DataSetObserver;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.AdapterView;
@@ -30,9 +31,15 @@
     private final ListView mListView;
     private final List<Runnable> mClickRunnables;
 
-    public TranslateMessageSecondaryMenu(Context context, Handler handler, MenuItem[] menuItems) {
+    public TranslateMessageSecondaryMenu(Context context, Handler handler,
+            DataSetObserver dataSetObserver, MenuItem[] menuItems) {
         mHandler = handler;
         mAdapter = new TranslateMessageSecondaryMenuAdapter(context, menuItems);
+        // The dataSetObserver *must* be registered on mAdapter before the call to
+        // mListView.setAdapter() below, so that the dimensions of the AnchoredPopupWindow are
+        // updated before the ListView's DataSetObserver is called, which will update the ListView's
+        // appearance.
+        mAdapter.registerDataSetObserver(dataSetObserver);
 
         mContentView = LayoutInflater.from(context).inflate(R.layout.app_menu_layout, null);
         mListView = mContentView.findViewById(R.id.app_menu_list);
diff --git a/components/translate/content/android/translate_message.h b/components/translate/content/android/translate_message.h
index 1283628..f627c33 100644
--- a/components/translate/content/android/translate_message.h
+++ b/components/translate/content/android/translate_message.h
@@ -149,7 +149,6 @@
 
   // Constructed the first time ShowTranslateStep is called.
   std::unique_ptr<TranslateUIDelegate> ui_delegate_;
-  base::android::ScopedJavaGlobalRef<jobject> java_translate_message_;
   TranslateStep translate_step_ = TRANSLATE_STEP_TRANSLATE_ERROR;
 };
 
diff --git a/components/viz/service/display/renderer_pixeltest.cc b/components/viz/service/display/renderer_pixeltest.cc
index f2e493e..814376b 100644
--- a/components/viz/service/display/renderer_pixeltest.cc
+++ b/components/viz/service/display/renderer_pixeltest.cc
@@ -4773,8 +4773,7 @@
     }
 
     gfx::ColorTransform::Options options;
-    options.sdr_max_luminance_nits =
-        display_color_spaces_.GetSDRMaxLuminanceNits();
+    options.sdr_max_luminance_nits = gfx::ColorSpace::kDefaultSDRWhiteLevelV2;
     std::unique_ptr<gfx::ColorTransform> transform =
         gfx::ColorTransform::NewColorTransform(this->src_color_space_,
                                                this->dst_color_space_, options);
@@ -4887,8 +4886,7 @@
     gfx::ColorSpace(PrimaryID::BT709, TransferID::SMPTEST428_1),
     gfx::ColorSpace(PrimaryID::BT709, TransferID::SRGB_HDR),
     gfx::ColorSpace(PrimaryID::BT709, TransferID::LINEAR_HDR),
-    gfx::ColorSpace::CreateHDR10(50.f),
-    gfx::ColorSpace::CreateHDR10(250.f),
+    gfx::ColorSpace::CreateHDR10(),
 };
 
 gfx::ColorSpace dst_color_spaces[] = {
diff --git a/content/BUILD.gn b/content/BUILD.gn
index 4f6391ff..e0b59ac 100644
--- a/content/BUILD.gn
+++ b/content/BUILD.gn
@@ -115,6 +115,7 @@
     "//content/browser/attribution_reporting:mojo_bindings_webui_js",
     "//content/browser/prerender:mojo_bindings_webui_js",
     "//content/browser/process_internals:mojo_bindings_webui_js",
+    "//content/browser/resources/attribution_reporting:build_ts",
     "//storage/browser/quota:mojo_bindings_webui_js",
   ]
 }
diff --git a/content/OWNERS b/content/OWNERS
index 5b11dc3..08eae22f 100644
--- a/content/OWNERS
+++ b/content/OWNERS
@@ -36,6 +36,8 @@
 # structural changes, please get a review from a reviewer in this file.
 per-file BUILD.gn=*
 
+per-file dev_ui_content_resources.grd=file://ui/webui/PLATFORM_OWNERS
+
 # The remaining rules are for platform-specific implementation changes.
 # Do NOT add other per-file changes as they would also apply to public/.
 
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index d27eadd..f35c16b 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -686,6 +686,8 @@
     "code_cache/generated_code_cache.h",
     "code_cache/generated_code_cache_context.cc",
     "code_cache/generated_code_cache_context.h",
+    "code_cache/simple_lru_cache_index.cc",
+    "code_cache/simple_lru_cache_index.h",
     "compositor/surface_utils.cc",
     "compositor/surface_utils.h",
     "compute_pressure/compute_pressure_host.cc",
diff --git a/content/browser/accessibility/dump_accessibility_scripts_browsertest.cc b/content/browser/accessibility/dump_accessibility_scripts_browsertest.cc
index 6743b4b..a1b88dd 100644
--- a/content/browser/accessibility/dump_accessibility_scripts_browsertest.cc
+++ b/content/browser/accessibility/dump_accessibility_scripts_browsertest.cc
@@ -9,10 +9,13 @@
 #include "content/browser/accessibility/dump_accessibility_browsertest_base.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
 #include "content/public/test/content_browser_test_utils.h"
 #include "content/shell/browser/shell.h"
 #include "ui/accessibility/platform/inspect/ax_api_type.h"
 #include "ui/accessibility/platform/inspect/ax_script_instruction.h"
+#include "ui/events/keycodes/dom/keycode_converter.h"
+#include "ui/events/keycodes/keyboard_code_conversion.h"
 
 namespace content {
 
@@ -51,14 +54,22 @@
     // from scenario directives.
     test_helper_.OverrideExpectationType("content");
   }
+  ~DumpAccessibilityScriptTest() = default;
+  DumpAccessibilityScriptTest(const DumpAccessibilityScriptTest&) = delete;
+  DumpAccessibilityScriptTest& operator=(const DumpAccessibilityScriptTest&) =
+      delete;
 
+ protected:
   void SetUpCommandLine(base::CommandLine* command_line) override {
     // Enable MathMLCore for some MathML tests.
     base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
         switches::kEnableBlinkFeatures, "MathMLCore");
   }
 
-  std::vector<ui::AXPropertyFilter> DefaultFilters() const override;
+  std::vector<ui::AXPropertyFilter> DefaultFilters() const override {
+    return {};
+  }
+
   void AddPropertyFilter(
       std::vector<AXPropertyFilter>* property_filters,
       const std::string& filter,
@@ -66,16 +77,6 @@
     property_filters->push_back(AXPropertyFilter(filter, type));
   }
 
-  base::Value EvaluateScript(
-      AXTreeFormatter* formatter,
-      BrowserAccessibility* root,
-      const std::vector<AXScriptInstruction>& instructions,
-      size_t start_index,
-      size_t end_index) {
-    return base::Value(
-        formatter->EvaluateScript(root, instructions, start_index, end_index));
-  }
-
   std::vector<std::string> Dump() override {
     std::vector<std::string> dump;
     std::unique_ptr<AXTreeFormatter> formatter(CreateFormatter());
@@ -85,14 +86,21 @@
     size_t length = scenario_.script_instructions.size();
     while (start_index < length) {
       std::string wait_for;
+      std::string dom_key_string;
       bool printTree = false;
       size_t index = start_index;
       for (; index < length; index++) {
-        if (scenario_.script_instructions[index].IsEvent()) {
-          wait_for = scenario_.script_instructions[index].AsEvent();
+        const AXScriptInstruction& instruction =
+            scenario_.script_instructions[index];
+        if (instruction.IsEvent()) {
+          wait_for = instruction.AsEvent();
           break;
         }
-        if (scenario_.script_instructions[index].IsPrintTree()) {
+        if (instruction.IsKeyEvent()) {
+          dom_key_string = instruction.AsDomKeyString();
+          break;
+        }
+        if (instruction.IsPrintTree()) {
           printTree = true;
           break;
         }
@@ -115,6 +123,20 @@
         }
       }
 
+      if (!dom_key_string.empty()) {
+        ui::DomKey dom_key =
+            ui::KeycodeConverter::KeyStringToDomKey(dom_key_string);
+        if (dom_key != ui::DomKey::NONE) {
+          ui::DomCode dom_code =
+              ui::KeycodeConverter::CodeStringToDomCode(dom_key_string);
+          SimulateKeyPress(GetWebContents(), dom_key, dom_code,
+                           ui::DomCodeToUsLayoutKeyboardCode(dom_code),
+                           /* control */ false, /* shift */ false,
+                           /* alt */ false, /* command */ false);
+        }
+        actual_contents += "press " + dom_key_string + '\n';
+        RunUntilInputProcessed(GetWidgetHost());
+      }
       if (printTree) {
         actual_contents += DumpTreeAsString() + '\n';
       }
@@ -128,12 +150,21 @@
     }
     return dump;
   }
-};
 
-std::vector<ui::AXPropertyFilter> DumpAccessibilityScriptTest::DefaultFilters()
-    const {
-  return {};
-}
+  base::Value EvaluateScript(
+      AXTreeFormatter* formatter,
+      BrowserAccessibility* root,
+      const std::vector<AXScriptInstruction>& instructions,
+      size_t start_index,
+      size_t end_index) {
+    return base::Value(
+        formatter->EvaluateScript(root, instructions, start_index, end_index));
+  }
+
+  RenderWidgetHost* GetWidgetHost() {
+    return GetWebContents()->GetMainFrame()->GetRenderViewHost()->GetWidget();
+  }
+};
 
 // Parameterize the tests so that each test-pass is run independently.
 struct TestPassToString {
diff --git a/content/browser/attribution_reporting/attribution_internals_browsertest.cc b/content/browser/attribution_reporting/attribution_internals_browsertest.cc
index e6cb08a9..2590dbc 100644
--- a/content/browser/attribution_reporting/attribution_internals_browsertest.cc
+++ b/content/browser/attribution_reporting/attribution_internals_browsertest.cc
@@ -127,7 +127,8 @@
   // the report table is empty.
   void SetTitleOnReportsTableEmpty(const std::u16string& title) {
     static constexpr char kObserveEmptyReportsTableScript[] = R"(
-    let table = document.querySelector("#report-table-wrapper tbody");
+    let table = document.querySelector('#reportTable')
+        .shadowRoot.querySelector('tbody');
     let obs = new MutationObserver(() => {
       if (table.children.length === 1 &&
           table.children[0].children[0].innerText === "No sent or pending reports.") {
@@ -222,7 +223,8 @@
   OverrideWebUIAttributionManager();
 
   static constexpr char wait_script[] = R"(
-    let table = document.querySelector("#source-table-wrapper tbody");
+    let table = document.querySelector('#sourceTable')
+        .shadowRoot.querySelector('tbody');
     let obs = new MutationObserver(() => {
       if (table.children.length === 1 &&
           table.children[0].children[0].innerText ===
@@ -294,7 +296,8 @@
       StorableSource::Result::kExcessiveReportingOrigins);
 
   static constexpr char wait_script[] = R"(
-    let table = document.querySelector("#source-table-wrapper tbody");
+    let table = document.querySelector('#sourceTable')
+        .shadowRoot.querySelector('tbody');
     let obs = new MutationObserver(() => {
       if (table.children.length === 8 &&
           table.children[0].children[0].innerText === $1 &&
@@ -461,7 +464,8 @@
 
   {
     static constexpr char wait_script[] = R"(
-      let table = document.querySelector("#report-table-wrapper tbody");
+      let table = document.querySelector('#reportTable')
+          .shadowRoot.querySelector('tbody');
       let obs = new MutationObserver(() => {
         if (table.children.length === 6 &&
             table.children[0].children[3].innerText ===
@@ -493,7 +497,8 @@
 
   {
     static constexpr char wait_script[] = R"(
-      let table = document.querySelector("#report-table-wrapper tbody");
+      let table = document.querySelector('#reportTable')
+          .shadowRoot.querySelector('tbody');
       let obs = new MutationObserver(() => {
         if (table.children.length === 6 &&
             table.children[5].children[3].innerText ===
@@ -520,14 +525,16 @@
 
     TitleWatcher title_watcher(shell()->web_contents(), kCompleteTitle2);
     // Sort by priority ascending.
-    EXPECT_TRUE(ExecJsInWebUI(
-        "document.querySelectorAll('#report-table-wrapper th')[6].click();"));
+    EXPECT_TRUE(
+        ExecJsInWebUI("document.querySelector('#reportTable')"
+                      ".shadowRoot.querySelectorAll('th')[6].click();"));
     EXPECT_EQ(kCompleteTitle2, title_watcher.WaitAndGetTitle());
   }
 
   {
     static constexpr char wait_script[] = R"(
-      let table = document.querySelector("#report-table-wrapper tbody");
+      let table = document.querySelector('#reportTable')
+          .shadowRoot.querySelector('tbody');
       let obs = new MutationObserver(() => {
         if (table.children.length === 6 &&
             table.children[0].children[3].innerText ===
@@ -554,8 +561,9 @@
 
     TitleWatcher title_watcher(shell()->web_contents(), kCompleteTitle3);
     // Sort by priority descending.
-    EXPECT_TRUE(ExecJsInWebUI(
-        "document.querySelectorAll('#report-table-wrapper th')[6].click();"));
+    EXPECT_TRUE(
+        ExecJsInWebUI("document.querySelector('#reportTable')"
+                      ".shadowRoot.querySelectorAll('th')[6].click();"));
 
     EXPECT_EQ(kCompleteTitle3, title_watcher.WaitAndGetTitle());
   }
@@ -591,7 +599,8 @@
 
   // Verify both rows get rendered.
   static constexpr char wait_script[] = R"(
-    let table = document.querySelector("#report-table-wrapper tbody");
+    let table = document.querySelector('#reportTable')
+        .shadowRoot.querySelector('tbody');
     let obs = new MutationObserver(() => {
       if (table.children.length === 2 &&
           table.children[0].children[6].innerText === "7" &&
@@ -639,7 +648,8 @@
 
   // Verify both rows get rendered.
   static constexpr char wait_script[] = R"(
-    let table = document.querySelector("#source-table-wrapper tbody");
+    let table = document.querySelector('#sourceTable')
+        .shadowRoot.querySelector('tbody');
     let obs = new MutationObserver(() => {
       if (table.children.length === 2 &&
           table.children[0].children[0].innerText === "5" &&
@@ -659,7 +669,8 @@
   const std::u16string kDeleteTitle = u"Delete";
   TitleWatcher delete_title_watcher(shell()->web_contents(), kDeleteTitle);
   static constexpr char kObserveEmptySourcesTableScript[] = R"(
-    let table = document.querySelector("#source-table-wrapper tbody");
+    let table = document.querySelector('#sourceTable')
+        .shadowRoot.querySelector('tbody');
     let obs = new MutationObserver(() => {
       if (table.children.length === 1 &&
           table.children[0].children[0].innerText === "No sources.") {
@@ -701,7 +712,8 @@
   OverrideWebUIAttributionManager();
 
   static constexpr char wait_script[] = R"(
-    let table = document.querySelector("#report-table-wrapper tbody");
+    let table = document.querySelector('#reportTable')
+        .shadowRoot.querySelector('tbody');
     let obs = new MutationObserver(() => {
       if (table.children.length === 1 &&
           table.children[0].children[6].innerText === "7") {
@@ -722,7 +734,8 @@
   SetTitleOnReportsTableEmpty(kSentTitle);
 
   EXPECT_TRUE(ExecJsInWebUI(
-      R"(document.querySelector('#report-table-wrapper input[type="checkbox"]').click();)"));
+      R"(document.querySelector('#reportTable')
+         .shadowRoot.querySelector('input[type="checkbox"]').click();)"));
   EXPECT_TRUE(
       ExecJsInWebUI("document.getElementById('send-reports').click();"));
 
@@ -821,7 +834,8 @@
 
   {
     static constexpr char wait_script[] = R"(
-      let table = document.querySelector("#aggregatable-report-table-wrapper tbody");
+      let table = document.querySelector('#aggregatableReportTable')
+          .shadowRoot.querySelector('tbody');
       let obs = new MutationObserver(() => {
         if (table.children.length === 6 &&
             table.children[0].children[3].innerText ===
@@ -882,7 +896,8 @@
       R"json([ {  "data": "2",  "priority": "3",  "filters": {   "c": [    "d"   ]  } }, {  "data": "4",  "priority": "5",  "deduplication_key": "6",  "not_filters": {   "e": [    "f"   ]  } }])json";
 
   static constexpr char wait_script[] = R"(
-      let table = document.querySelector("#trigger-table-wrapper tbody");
+      let table = document.querySelector('#triggerTable')
+          .shadowRoot.querySelector('tbody');
       let obs = new MutationObserver(() => {
         if (table.children.length === 1 &&
             table.children[0].children[1].innerText === "Success: Report stored" &&
@@ -952,7 +967,8 @@
   OverrideWebUIAttributionManager();
 
   static constexpr char wait_script[] = R"(
-    let table = document.querySelector("#aggregatable-report-table-wrapper tbody");
+    let table = document.querySelector('#aggregatableReportTable')
+        .shadowRoot.querySelector('tbody');
     let obs = new MutationObserver(() => {
       if (table.children.length === 1) {
         document.title = $1;
@@ -971,7 +987,8 @@
   TitleWatcher sent_title_watcher(shell()->web_contents(), kSentTitle);
 
   static constexpr char kObserveEmptyReportsTableScript[] = R"(
-    let table = document.querySelector("#aggregatable-report-table-wrapper tbody");
+    let table = document.querySelector('#aggregatableReportTable')
+        .shadowRoot.querySelector('tbody');
     let obs = new MutationObserver(() => {
       if (table.children.length === 1 &&
           table.children[0].children[0].innerText === "No sent or pending reports.") {
@@ -983,7 +1000,8 @@
       ExecJsInWebUI(JsReplace(kObserveEmptyReportsTableScript, kSentTitle)));
 
   EXPECT_TRUE(ExecJsInWebUI(
-      R"(document.querySelectorAll('#aggregatable-report-table-wrapper input[type="checkbox"]')[1].click();)"));
+      R"(document.querySelector('#aggregatableReportTable')
+         .shadowRoot.querySelectorAll('input[type="checkbox"]')[1].click();)"));
   EXPECT_TRUE(ExecJsInWebUI(
       "document.getElementById('send-aggregatable-reports').click();"));
 
@@ -1024,7 +1042,8 @@
   // By default, debug reports are shown.
   {
     static constexpr char wait_script[] = R"(
-      let table = document.querySelector("#report-table-wrapper tbody");
+      let table = document.querySelector('#reportTable')
+          .shadowRoot.querySelector('tbody');
       let label = document.querySelector('#show-debug-event-reports span');
       let obs = new MutationObserver(() => {
         if (table.children.length === 2 &&
@@ -1061,7 +1080,8 @@
   // the label should indicate the number.
   {
     static constexpr char wait_script[] = R"(
-      let table = document.querySelector("#report-table-wrapper tbody");
+      let table = document.querySelector('#reportTable')
+          .shadowRoot.querySelector('tbody');
       let label = document.querySelector('#show-debug-event-reports span');
       let obs = new MutationObserver(() => {
         if (table.children.length === 1 &&
@@ -1087,7 +1107,8 @@
   // cleared.
   {
     static constexpr char wait_script[] = R"(
-      let table = document.querySelector("#report-table-wrapper tbody");
+      let table = document.querySelector('#reportTable').shadowRoot
+          .querySelector('tbody');
       let label = document.querySelector('#show-debug-event-reports span');
       let obs = new MutationObserver(() => {
         if (table.children.length === 3 &&
diff --git a/content/browser/attribution_reporting/attribution_internals_ui.cc b/content/browser/attribution_reporting/attribution_internals_ui.cc
index e855262..e99edc22c 100644
--- a/content/browser/attribution_reporting/attribution_internals_ui.cc
+++ b/content/browser/attribution_reporting/attribution_internals_ui.cc
@@ -30,6 +30,10 @@
                           IDR_ATTRIBUTION_INTERNALS_MOJOM_JS);
   source->AddResourcePath("attribution_internals.js",
                           IDR_ATTRIBUTION_INTERNALS_JS);
+  source->AddResourcePath("attribution_internals_table.js",
+                          IDR_ATTRIBUTION_INTERNALS_TABLE_JS);
+  source->AddResourcePath("table_model.js",
+                          IDR_ATTRIBUTION_INTERNALS_TABLE_MODEL_JS);
   source->AddResourcePath("attribution_internals.css",
                           IDR_ATTRIBUTION_INTERNALS_CSS);
   source->SetDefaultResource(IDR_ATTRIBUTION_INTERNALS_HTML);
diff --git a/content/browser/code_cache/generated_code_cache.cc b/content/browser/code_cache/generated_code_cache.cc
index a8f45fb3..c256ba1 100644
--- a/content/browser/code_cache/generated_code_cache.cc
+++ b/content/browser/code_cache/generated_code_cache.cc
@@ -10,6 +10,7 @@
 #include "base/callback_helpers.h"
 #include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/time/time.h"
@@ -247,12 +248,14 @@
   PendingOperation(Operation op,
                    const std::string& key,
                    const base::Time& response_time,
+                   const base::TimeTicks start_time,
                    scoped_refptr<net::IOBufferWithSize> small_buffer,
                    scoped_refptr<BigIOBuffer> large_buffer,
                    ReadDataCallback read_callback)
       : op_(op),
         key_(key),
         response_time_(response_time),
+        start_time_(start_time),
         small_buffer_(small_buffer),
         large_buffer_(large_buffer),
         read_callback_(std::move(read_callback)) {
@@ -275,6 +278,28 @@
   scoped_refptr<net::IOBufferWithSize> small_buffer() { return small_buffer_; }
   scoped_refptr<BigIOBuffer> large_buffer() { return large_buffer_; }
   ReadDataCallback TakeReadCallback() { return std::move(read_callback_); }
+  void RunReadCallback(GeneratedCodeCache* code_cache,
+                       base::Time response_time,
+                       mojo_base::BigBuffer data) {
+    if (code_cache->cache_type_ == CodeCacheType::kJavaScript) {
+      const bool code_cache_hit = data.size() > 0;
+      const bool hypothetical_in_memory_code_cache_hit =
+          code_cache->lru_cache_index_.Get(key_);
+      if (code_cache_hit && !hypothetical_in_memory_code_cache_hit) {
+        code_cache->lru_cache_index_.Put(key_, data.size());
+      }
+      if (code_cache_hit && hypothetical_in_memory_code_cache_hit) {
+        base::UmaHistogramTimes(
+            "SiteIsolatedCodeCache.JS.MemoryBackedCodeCachePotentialImpact",
+            base::TimeTicks::Now() - start_time_);
+      }
+      base::UmaHistogramBoolean("SiteIsolatedCodeCache.JS.Hit", code_cache_hit);
+      base::UmaHistogramBoolean(
+          "SiteIsolatedCodeCache.JS.PotentialMemoryBackedCodeCacheHit",
+          hypothetical_in_memory_code_cache_hit);
+    }
+    std::move(read_callback_).Run(response_time, std::move(data));
+  }
   GetBackendCallback TakeBackendCallback() {
     return std::move(backend_callback_);
   }
@@ -296,6 +321,8 @@
     return response_time_;
   }
 
+  base::TimeTicks start_time() const { return start_time_; }
+
   // These are called by write and fetch operations to track buffer completions
   // and signal when the operation has finished, and whether it was successful.
   bool succeeded() const { return succeeded_; }
@@ -314,6 +341,7 @@
   const Operation op_;
   const std::string key_;
   const base::Time response_time_;
+  const base::TimeTicks start_time_ = base::TimeTicks::Now();
   scoped_refptr<net::IOBufferWithSize> small_buffer_;
   scoped_refptr<BigIOBuffer> large_buffer_;
   ReadDataCallback read_callback_;
@@ -432,6 +460,7 @@
   auto op = std::make_unique<PendingOperation>(Operation::kWrite, key,
                                                small_buffer, large_buffer);
   EnqueueOperation(std::move(op));
+  lru_cache_index_.Put(key, data_size);
 }
 
 void GeneratedCodeCache::FetchEntry(const GURL& url,
@@ -463,6 +492,8 @@
   std::string key = GetCacheKey(url, origin_lock, nik, cache_type_);
   auto op = std::make_unique<PendingOperation>(Operation::kDelete, key);
   EnqueueOperation(std::move(op));
+
+  lru_cache_index_.Delete(key);
 }
 
 void GeneratedCodeCache::CreateBackend() {
@@ -660,7 +691,7 @@
   DCHECK(Operation::kFetch == op->operation() ||
          Operation::kFetchWithSHAKey == op->operation());
   if (backend_state_ != kInitialized) {
-    op->TakeReadCallback().Run(base::Time(), mojo_base::BigBuffer());
+    op->RunReadCallback(this, base::Time(), mojo_base::BigBuffer());
     CloseOperationAndIssueNext(op);
     return;
   }
@@ -682,7 +713,7 @@
          Operation::kFetchWithSHAKey == op->operation());
   if (entry_result.net_error() != net::OK) {
     CollectStatistics(CacheEntryStatus::kMiss);
-    op->TakeReadCallback().Run(base::Time(), mojo_base::BigBuffer());
+    op->RunReadCallback(this, base::Time(), mojo_base::BigBuffer());
     CloseOperationAndIssueNext(op);
     return;
   }
@@ -759,7 +790,7 @@
   DCHECK(Operation::kFetch == op->operation() ||
          Operation::kFetchWithSHAKey == op->operation());
   if (!op->succeeded()) {
-    op->TakeReadCallback().Run(base::Time(), mojo_base::BigBuffer());
+    op->RunReadCallback(this, base::Time(), mojo_base::BigBuffer());
     // Doom this entry since it is inaccessible.
     DoomEntry(op);
   } else {
@@ -773,12 +804,12 @@
         mojo_base::BigBuffer data(data_size);
         memcpy(data.data(), op->small_buffer()->data() + kHeaderSizeInBytes,
                data_size);
-        op->TakeReadCallback().Run(response_time, std::move(data));
+        op->RunReadCallback(this, response_time, std::move(data));
       } else if (!ShouldDeduplicateEntry(data_size)) {
         // Large data below the merging threshold, or deduplication is disabled.
         // Return the large buffer.
-        op->TakeReadCallback().Run(response_time,
-                                   op->large_buffer()->TakeBuffer());
+        op->RunReadCallback(this, response_time,
+                            op->large_buffer()->TakeBuffer());
       } else {
         // Very large data. Create the second fetch using the checksum as key.
         DCHECK_EQ(static_cast<int>(kHeaderSizeInBytes + kSHAKeySizeInBytes),
@@ -790,13 +821,14 @@
         auto large_buffer = base::MakeRefCounted<BigIOBuffer>(data_size);
         auto op2 = std::make_unique<PendingOperation>(
             Operation::kFetchWithSHAKey, checksum_key, response_time,
-            small_buffer, large_buffer, op->TakeReadCallback());
+            op->start_time(), small_buffer, large_buffer,
+            op->TakeReadCallback());
         EnqueueOperation(std::move(op2));
       }
     } else {
       // Large merged code data with no header. |op| holds the response time.
-      op->TakeReadCallback().Run(op->response_time(),
-                                 op->large_buffer()->TakeBuffer());
+      op->RunReadCallback(this, op->response_time(),
+                          op->large_buffer()->TakeBuffer());
     }
   }
   CloseOperationAndIssueNext(op);
diff --git a/content/browser/code_cache/generated_code_cache.h b/content/browser/code_cache/generated_code_cache.h
index 5402b87..6c8de65 100644
--- a/content/browser/code_cache/generated_code_cache.h
+++ b/content/browser/code_cache/generated_code_cache.h
@@ -11,6 +11,7 @@
 #include "base/containers/queue.h"
 #include "base/files/file_path.h"
 #include "base/memory/weak_ptr.h"
+#include "content/browser/code_cache/simple_lru_cache_index.h"
 #include "content/common/content_export.h"
 #include "mojo/public/cpp/base/big_buffer.h"
 #include "net/base/io_buffer.h"
@@ -233,6 +234,9 @@
   int max_size_bytes_;
   CodeCacheType cache_type_;
 
+  // A hypothetical memory-backed code cache. Used to collect UMAs.
+  SimpleLruCacheIndex lru_cache_index_{/*capacity=*/200 * 1024 * 1024};
+
   base::WeakPtrFactory<GeneratedCodeCache> weak_ptr_factory_{this};
 };
 
diff --git a/content/browser/code_cache/simple_lru_cache_index.cc b/content/browser/code_cache/simple_lru_cache_index.cc
new file mode 100644
index 0000000..c11c2a88
--- /dev/null
+++ b/content/browser/code_cache/simple_lru_cache_index.cc
@@ -0,0 +1,70 @@
+// Copyright 2022 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 "content/browser/code_cache/simple_lru_cache_index.h"
+
+#include <limits>
+
+#include "net/base/url_util.h"
+
+namespace content {
+
+SimpleLruCacheIndex::SimpleLruCacheIndex(uint64_t capacity)
+    : capacity_(capacity) {}
+SimpleLruCacheIndex::~SimpleLruCacheIndex() = default;
+
+bool SimpleLruCacheIndex::Get(const std::string& key) {
+  const auto it = entries_.find(key);
+  if (it == entries_.end()) {
+    return false;
+  }
+  const Age age = GetNextAge();
+  access_list_.erase(it->second.age);
+  it->second.age = age;
+  access_list_.emplace(age, it->first);
+  return true;
+}
+
+void SimpleLruCacheIndex::Put(const std::string& key, uint32_t payload_size) {
+  Delete(key);
+
+  const Age age = GetNextAge();
+  const uint32_t size =
+      std::min(payload_size,
+               std::numeric_limits<uint32_t>::max() - kEmptyEntrySize) +
+      kEmptyEntrySize;
+
+  entries_.emplace(key, Value(age, size));
+  access_list_.emplace(age, std::move(key));
+  size_ += size;
+  Evict();
+}
+
+void SimpleLruCacheIndex::Delete(const std::string& key) {
+  const auto it = entries_.find(key);
+  if (it == entries_.end()) {
+    return;
+  }
+
+  DCHECK_GE(size_, it->second.size);
+  size_ -= it->second.size;
+  access_list_.erase(it->second.age);
+  entries_.erase(it);
+}
+
+uint64_t SimpleLruCacheIndex::GetSize() const {
+  return size_;
+}
+
+void SimpleLruCacheIndex::Evict() {
+  while (capacity_ < size_) {
+    auto it = access_list_.begin();
+    DCHECK(it != access_list_.end());
+    DCHECK(entries_.find(it->second) != entries_.end());
+
+    Delete(it->second);
+  }
+}
+
+}  // namespace content
diff --git a/content/browser/code_cache/simple_lru_cache_index.h b/content/browser/code_cache/simple_lru_cache_index.h
new file mode 100644
index 0000000..85fdc54
--- /dev/null
+++ b/content/browser/code_cache/simple_lru_cache_index.h
@@ -0,0 +1,62 @@
+// Copyright 2022 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 CONTENT_BROWSER_CODE_CACHE_SIMPLE_LRU_CACHE_INDEX_H_
+#define CONTENT_BROWSER_CODE_CACHE_SIMPLE_LRU_CACHE_INDEX_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <string>
+
+#include "base/types/strong_alias.h"
+#include "content/common/content_export.h"
+
+namespace content {
+
+// A simple LRU cache index, to measure the potential performance impact of
+// memory-backed code cache.
+class CONTENT_EXPORT SimpleLruCacheIndex {
+ public:
+  explicit SimpleLruCacheIndex(uint64_t capacity);
+  ~SimpleLruCacheIndex();
+
+  SimpleLruCacheIndex(const SimpleLruCacheIndex&) = delete;
+  SimpleLruCacheIndex& operator=(const SimpleLruCacheIndex&) = delete;
+
+  // Returns whether an entry for `key` exists in the cache.
+  bool Get(const std::string& key);
+  // Puts an entry whose payload size is `size` to the cache.
+  void Put(const std::string& key, uint32_t size);
+  // Deletes an entry for `key` in the cache. If there is no such an entry, this
+  // does nothing.
+  void Delete(const std::string& key);
+  // Returns the total size of the cache.
+  uint64_t GetSize() const;
+
+  static constexpr uint32_t kEmptyEntrySize = 2048;
+
+ private:
+  using Age = base::StrongAlias<class AgeTag, uint32_t>;
+  using Key = std::string;
+  struct Value {
+    Value(Age age, uint32_t size) : age(age), size(size) {}
+
+    Age age;
+    uint32_t size;
+  };
+
+  Age GetNextAge() { return Age(age_source_++); }
+  void Evict();
+
+  const uint64_t capacity_;
+  std::map<Key, Value> entries_;
+  std::map<Age, Key> access_list_;
+  uint32_t age_source_ = 0;
+  uint64_t size_ = 0;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_CODE_CACHE_SIMPLE_LRU_CACHE_INDEX_H_
diff --git a/content/browser/code_cache/simple_lru_cache_index_unittest.cc b/content/browser/code_cache/simple_lru_cache_index_unittest.cc
new file mode 100644
index 0000000..bb493013
--- /dev/null
+++ b/content/browser/code_cache/simple_lru_cache_index_unittest.cc
@@ -0,0 +1,176 @@
+// Copyright 2022 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 "content/browser/code_cache/simple_lru_cache_index.h"
+#include "net/base/schemeful_site.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace content {
+namespace {
+
+constexpr auto kEmptyEntrySize = SimpleLruCacheIndex::kEmptyEntrySize;
+
+TEST(SimpleLruCacheIndexTest, Empty) {
+  const std::string kKey = "hello";
+  SimpleLruCacheIndex cache(/*capacity=*/100 * 1024);
+
+  EXPECT_EQ(cache.GetSize(), 0u);
+  EXPECT_FALSE(cache.Get(kKey));
+}
+
+TEST(SimpleLruCacheIndexTest, PutAndGet) {
+  const std::string kKey1("key1");
+  const std::string kKey2("key2");
+  const std::string kKey3("key3");
+  const std::string kKey4("key4");
+
+  SimpleLruCacheIndex cache(/*capacity=*/100 * 1024);
+
+  EXPECT_EQ(cache.GetSize(), 0u);
+  EXPECT_FALSE(cache.Get(kKey1));
+  EXPECT_FALSE(cache.Get(kKey2));
+  EXPECT_FALSE(cache.Get(kKey3));
+  EXPECT_FALSE(cache.Get(kKey4));
+
+  cache.Put(kKey1, 1);
+  EXPECT_EQ(cache.GetSize(), 1 + kEmptyEntrySize);
+  EXPECT_TRUE(cache.Get(kKey1));
+  EXPECT_FALSE(cache.Get(kKey2));
+  EXPECT_FALSE(cache.Get(kKey3));
+  EXPECT_FALSE(cache.Get(kKey4));
+
+  cache.Put(kKey1, 2);
+  EXPECT_EQ(cache.GetSize(), 2 + kEmptyEntrySize);
+  EXPECT_TRUE(cache.Get(kKey1));
+  EXPECT_FALSE(cache.Get(kKey2));
+  EXPECT_FALSE(cache.Get(kKey3));
+  EXPECT_FALSE(cache.Get(kKey4));
+
+  cache.Put(kKey2, 3);
+  EXPECT_EQ(cache.GetSize(), 5 + 2 * kEmptyEntrySize);
+  EXPECT_TRUE(cache.Get(kKey1));
+  EXPECT_TRUE(cache.Get(kKey2));
+  EXPECT_FALSE(cache.Get(kKey3));
+  EXPECT_FALSE(cache.Get(kKey4));
+
+  cache.Put(kKey4, 4);
+  EXPECT_EQ(cache.GetSize(), 9 + 3 * kEmptyEntrySize);
+  EXPECT_TRUE(cache.Get(kKey1));
+  EXPECT_TRUE(cache.Get(kKey2));
+  EXPECT_FALSE(cache.Get(kKey3));
+  EXPECT_TRUE(cache.Get(kKey4));
+}
+
+TEST(SimpleLruCacheIndexTest, PutAndEvict) {
+  const std::string kKey("key1");
+
+  SimpleLruCacheIndex cache(/*capacity=*/kEmptyEntrySize + 1);
+
+  EXPECT_EQ(cache.GetSize(), 0u);
+  EXPECT_FALSE(cache.Get(kKey));
+
+  // This entry is immediately evicted because the size excceeds the capacity.
+  cache.Put(kKey, 2);
+  EXPECT_EQ(cache.GetSize(), 0u);
+  EXPECT_FALSE(cache.Get(kKey));
+
+  // This entry stays.
+  cache.Put(kKey, 1);
+  EXPECT_EQ(cache.GetSize(), 1 + kEmptyEntrySize);
+  EXPECT_TRUE(cache.Get(kKey));
+
+  // An updated entry can also be evicted.
+  cache.Put(kKey, 2);
+  EXPECT_EQ(cache.GetSize(), 0u);
+  EXPECT_FALSE(cache.Get(kKey));
+}
+
+TEST(SimpleLruCacheIndexTest, LRU) {
+  const std::string kKey1("key1");
+  const std::string kKey2("key2");
+  const std::string kKey3("key3");
+  const std::string kKey4("key4");
+
+  SimpleLruCacheIndex cache(kEmptyEntrySize * 2);
+
+  cache.Put(kKey1, 0);
+  cache.Put(kKey2, 0);
+  cache.Put(kKey3, 0);
+  cache.Put(kKey4, 0);
+
+  // The last two entries are kept.
+  EXPECT_EQ(cache.GetSize(), 2 * kEmptyEntrySize);
+  EXPECT_FALSE(cache.Get(kKey1));
+  EXPECT_FALSE(cache.Get(kKey2));
+  EXPECT_TRUE(cache.Get(kKey3));
+  EXPECT_TRUE(cache.Get(kKey4));
+}
+
+TEST(SimpleLruCacheIndexTest, LRUAndGet) {
+  const std::string kKey1("key1");
+  const std::string kKey2("key2");
+  const std::string kKey3("key3");
+  const std::string kKey4("key4");
+
+  SimpleLruCacheIndex cache(kEmptyEntrySize * 2);
+
+  cache.Put(kKey1, 0);
+  cache.Put(kKey2, 0);
+  // This call updates the access time.
+  EXPECT_TRUE(cache.Get(kKey1));
+  cache.Put(kKey3, 0);
+
+  EXPECT_EQ(cache.GetSize(), 2 * kEmptyEntrySize);
+  EXPECT_TRUE(cache.Get(kKey1));
+  EXPECT_FALSE(cache.Get(kKey2));
+  EXPECT_TRUE(cache.Get(kKey3));
+  EXPECT_FALSE(cache.Get(kKey4));
+}
+
+TEST(SimpleLruCacheIndexTest, Delete) {
+  const std::string kKey1("key1");
+  const std::string kKey2("key2");
+  const std::string kKey3("key3");
+  const std::string kKey4("key4");
+
+  SimpleLruCacheIndex cache(/*capacity=*/1024 * 1024);
+
+  cache.Put(kKey1, 1);
+  cache.Put(kKey2, 2);
+  cache.Put(kKey3, 3);
+  cache.Put(kKey4, 4);
+
+  EXPECT_EQ(cache.GetSize(), 4 * kEmptyEntrySize + 10);
+  EXPECT_TRUE(cache.Get(kKey1));
+  EXPECT_TRUE(cache.Get(kKey2));
+  EXPECT_TRUE(cache.Get(kKey3));
+  EXPECT_TRUE(cache.Get(kKey4));
+
+  cache.Delete(kKey2);
+  EXPECT_EQ(cache.GetSize(), 3 * kEmptyEntrySize + 8);
+  EXPECT_TRUE(cache.Get(kKey1));
+  EXPECT_FALSE(cache.Get(kKey2));
+  EXPECT_TRUE(cache.Get(kKey3));
+  EXPECT_TRUE(cache.Get(kKey4));
+
+  cache.Delete(kKey2);
+  EXPECT_EQ(cache.GetSize(), 3 * kEmptyEntrySize + 8);
+  EXPECT_TRUE(cache.Get(kKey1));
+  EXPECT_FALSE(cache.Get(kKey2));
+  EXPECT_TRUE(cache.Get(kKey3));
+  EXPECT_TRUE(cache.Get(kKey4));
+
+  cache.Delete(kKey1);
+  cache.Delete(kKey2);
+  cache.Delete(kKey3);
+  cache.Delete(kKey4);
+  EXPECT_EQ(cache.GetSize(), 0u);
+  EXPECT_FALSE(cache.Get(kKey1));
+  EXPECT_FALSE(cache.Get(kKey2));
+  EXPECT_FALSE(cache.Get(kKey3));
+  EXPECT_FALSE(cache.Get(kKey4));
+}
+
+}  // namespace
+}  // namespace content
diff --git a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
index b8ed639b..8670fc2 100644
--- a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
+++ b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
@@ -2735,6 +2735,18 @@
   WaitForNotification("Tracing.tracingComplete", true);
 }
 
+IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, NavigateToAboutBlankLoaderId) {
+  NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
+  Attach();
+
+  base::Value::Dict params;
+  params.Set("url", "about:blank");
+  const base::Value::Dict* result =
+      SendCommand("Page.navigate", std::move(params));
+  EXPECT_THAT(result->FindString("loaderId"),
+              testing::Pointee(testing::Not("")));
+}
+
 class SystemTracingDevToolsProtocolTest : public DevToolsProtocolTest {
  protected:
   const base::Value::Dict* StartSystemTrace() {
diff --git a/content/browser/devtools/protocol/page_handler.cc b/content/browser/devtools/protocol/page_handler.cc
index 8f93e90..966ed1b 100644
--- a/content/browser/devtools/protocol/page_handler.cc
+++ b/content/browser/devtools/protocol/page_handler.cc
@@ -457,6 +457,31 @@
   return network::mojom::ReferrerPolicy::kDefault;
 }
 
+namespace {
+
+void DispatchNavigateCallback(
+    NavigationRequest* request,
+    std::unique_ptr<PageHandler::NavigateCallback> callback) {
+  std::string frame_id =
+      request->frame_tree_node()->devtools_frame_token().ToString();
+  // A new NavigationRequest may have been created before |request|
+  // started, in which case it is not marked as aborted. We report this as an
+  // abort to DevTools anyway.
+  if (!request->IsNavigationStarted()) {
+    callback->sendSuccess(frame_id, Maybe<std::string>(),
+                          net::ErrorToString(net::ERR_ABORTED));
+    return;
+  }
+  Maybe<std::string> opt_error;
+  if (request->GetNetErrorCode() != net::OK)
+    opt_error = net::ErrorToString(request->GetNetErrorCode());
+  callback->sendSuccess(frame_id,
+                        request->devtools_navigation_token().ToString(),
+                        std::move(opt_error));
+}
+
+}  // namespace
+
 void PageHandler::Navigate(const std::string& url,
                            Maybe<std::string> referrer,
                            Maybe<std::string> maybe_transition_type,
@@ -534,44 +559,40 @@
   // Handler may be destroyed while navigating if the session
   // gets disconnected as a result of access checks.
   base::WeakPtr<PageHandler> weak_self = weak_factory_.GetWeakPtr();
-  frame_tree_node->navigator().controller().LoadURLWithParams(params);
+  base::WeakPtr<NavigationHandle> navigation_handle =
+      frame_tree_node->navigator().controller().LoadURLWithParams(params);
+  // TODO(caseq): should we still dispatch callback here?
   if (!weak_self)
     return;
-  if (frame_tree_node->navigation_request()) {
-    navigate_callbacks_[frame_tree_node->navigation_request()
-                            ->devtools_navigation_token()] =
-        std::move(callback);
-  } else {
+  if (!navigation_handle) {
     callback->sendSuccess(out_frame_id, Maybe<std::string>(),
-                          Maybe<std::string>());
+                          net::ErrorToString(net::ERR_ABORTED));
+    return;
   }
+  auto* navigation_request =
+      static_cast<NavigationRequest*>(navigation_handle.get());
+  if (frame_tree_node->navigation_request() != navigation_request) {
+    // The ownership of the navigation request should have been transferred to
+    // RFH at this point, so we won't get `NavigationReset` for it any more --
+    // fire the callback now!
+    DispatchNavigateCallback(navigation_request, std::move(callback));
+    return;
+  }
+  // At this point, we expect the callback to get dispatched upon
+  // `NavigationReset()` is called when `NavigationRequest` is taken from
+  // `FrameTreeNode`.
+  const base::UnguessableToken& navigation_token =
+      navigation_request->devtools_navigation_token();
+  navigate_callbacks_[navigation_token] = std::move(callback);
 }
 
 void PageHandler::NavigationReset(NavigationRequest* navigation_request) {
-  auto navigate_callback =
+  auto it =
       navigate_callbacks_.find(navigation_request->devtools_navigation_token());
-  if (navigate_callback == navigate_callbacks_.end())
+  if (it == navigate_callbacks_.end())
     return;
-  std::string frame_id =
-      navigation_request->frame_tree_node()->devtools_frame_token().ToString();
-  // A new NavigationRequest may have been created before |navigation_request|
-  // started, in which case it is not marked as aborted. We report this as an
-  // abort to DevTools anyway.
-  if (!navigation_request->IsNavigationStarted()) {
-    navigate_callback->second->sendSuccess(
-        frame_id, Maybe<std::string>(),
-        Maybe<std::string>(net::ErrorToString(net::ERR_ABORTED)));
-  } else {
-    bool success = navigation_request->GetNetErrorCode() == net::OK;
-    std::string error_string =
-        net::ErrorToString(navigation_request->GetNetErrorCode());
-    navigate_callback->second->sendSuccess(
-        frame_id,
-        Maybe<std::string>(
-            navigation_request->devtools_navigation_token().ToString()),
-        success ? Maybe<std::string>() : Maybe<std::string>(error_string));
-  }
-  navigate_callbacks_.erase(navigate_callback);
+  DispatchNavigateCallback(navigation_request, std::move(it->second));
+  navigate_callbacks_.erase(it);
 }
 
 void PageHandler::DownloadWillBegin(FrameTreeNode* ftn,
diff --git a/content/browser/file_system_access/file_system_chooser.cc b/content/browser/file_system_access/file_system_chooser.cc
index 4adbbe5..1608613 100644
--- a/content/browser/file_system_access/file_system_chooser.cc
+++ b/content/browser/file_system_access/file_system_chooser.cc
@@ -275,13 +275,15 @@
   base::FilePath::StringType extension_lower =
       base::ToLowerASCII(GetLastExtension(extension));
 
-  // .lnk and .scf files may be used to execute arbitrary code (see
+  // '.lnk' and '.scf' files may be used to execute arbitrary code (see
   // https://nvd.nist.gov/vuln/detail/CVE-2010-2568 and
-  // https://crbug.com/1227995, respectively). .local files are used by Windows
-  // to determine which DLLs to load for an application.
+  // https://crbug.com/1227995, respectively). '.local' files are used by
+  // Windows to determine which DLLs to load for an application. '.url' files
+  // can be used to read arbirtary files (see https://crbug.com/1307930).
   if ((extension_lower == FILE_PATH_LITERAL("lnk")) ||
       (extension_lower == FILE_PATH_LITERAL("local")) ||
-      (extension_lower == FILE_PATH_LITERAL("scf"))) {
+      (extension_lower == FILE_PATH_LITERAL("scf")) ||
+      (extension_lower == FILE_PATH_LITERAL("url"))) {
     return true;
   }
 
diff --git a/content/browser/file_system_access/file_system_chooser_browsertest.cc b/content/browser/file_system_access/file_system_chooser_browsertest.cc
index 9ea4db7..79dda31 100644
--- a/content/browser/file_system_access/file_system_chooser_browsertest.cc
+++ b/content/browser/file_system_access/file_system_chooser_browsertest.cc
@@ -1556,13 +1556,21 @@
   name_infos.push_back({"not_matching.jpg", ListValueOf(".txt"), false,
                         "not_matching.jpg", false});
 
-  // ".lnk", ".local", and ".scf" extensions should be sanitized.
-  name_infos.push_back({"dangerous_extension.local", ListValueOf(".local"),
-                        true, "dangerous_extension.download", false});
+  // ".lnk", ".local", ".scf", and ".url" extensions should be sanitized.
   name_infos.push_back({"dangerous_extension.lnk", ListValueOf(".lnk"), true,
                         "dangerous_extension.download", false});
+  name_infos.push_back({"dangerous_extension.lnk", ListValueOf(".LNK"), true,
+                        "dangerous_extension.download", false});
+  name_infos.push_back({"dangerous_extension.LNK", ListValueOf(".lnk"), true,
+                        "dangerous_extension.download", false});
+  name_infos.push_back({"dangerous_extension.LNK", ListValueOf(".LNK"), true,
+                        "dangerous_extension.download", false});
+  name_infos.push_back({"dangerous_extension.local", ListValueOf(".local"),
+                        true, "dangerous_extension.download", false});
   name_infos.push_back({"dangerous_extension.scf", ListValueOf(".scf"), true,
                         "dangerous_extension.download", false});
+  name_infos.push_back({"dangerous_extension.url", ListValueOf(".url"), true,
+                        "dangerous_extension.download", false});
   // Compound extensions ending in a dangerous extension should be sanitized.
   name_infos.push_back({"dangerous_extension.png.local", ListValueOf(".local"),
                         true, "dangerous_extension.png.download", false});
@@ -1570,6 +1578,8 @@
                         true, "dangerous_extension.png.download", false});
   name_infos.push_back({"dangerous_extension.png.scf", ListValueOf(".scf"),
                         true, "dangerous_extension.png.download", false});
+  name_infos.push_back({"dangerous_extension.png.url", ListValueOf(".url"),
+                        true, "dangerous_extension.png.download", false});
   // Compound extensions not ending in a dangerous extension should not be
   // sanitized.
   name_infos.push_back({"dangerous_extension.local.png", ListValueOf(".png"),
@@ -1578,6 +1588,8 @@
                         true, "dangerous_extension.lnk.png", true});
   name_infos.push_back({"dangerous_extension.scf.png", ListValueOf(".png"),
                         true, "dangerous_extension.scf.png", true});
+  name_infos.push_back({"dangerous_extension.url.png", ListValueOf(".png"),
+                        true, "dangerous_extension.url.png", true});
   // Invalid characters should be sanitized.
   name_infos.push_back({R"(inv*l:d\\ch%rבאמת!a<ters🤓.txt)",
                         ListValueOf(".txt"), true,
diff --git a/content/browser/file_system_access/file_system_chooser_unittest.cc b/content/browser/file_system_access/file_system_chooser_unittest.cc
index 9b27d63..3082c08 100644
--- a/content/browser/file_system_access/file_system_chooser_unittest.cc
+++ b/content/browser/file_system_access/file_system_chooser_unittest.cc
@@ -189,7 +189,7 @@
   accepts.emplace_back(blink::mojom::ChooseFileSystemEntryAcceptsOption::New(
       u"", std::vector<std::string>({}),
       std::vector<std::string>(
-          {"lnk", "foo.lnk", "foo.bar.local", "text", "local", "scf"})));
+          {"lnk", "foo.lnk", "foo.bar.local", "text", "local", "scf", "url"})));
   SyncShowDialog(std::move(accepts), /*include_accepts_all=*/false);
 
   ASSERT_TRUE(dialog_params.file_types);
diff --git a/content/browser/indexed_db/indexed_db_context_impl.cc b/content/browser/indexed_db/indexed_db_context_impl.cc
index aac5a9eb..3d0b3e4 100644
--- a/content/browser/indexed_db/indexed_db_context_impl.cc
+++ b/content/browser/indexed_db/indexed_db_context_impl.cc
@@ -605,37 +605,23 @@
 
 void IndexedDBContextImpl::SetForceKeepSessionState() {
   idb_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          [](IndexedDBContextImpl* context) {
-            context->force_keep_session_state_ = true;
-          },
-          // `this` is destroyed on idb_task_runner_ so it's safe to post raw.
-          base::Unretained(this)));
+      FROM_HERE, base::BindOnce(
+                     [](base::WeakPtr<IndexedDBContextImpl> context) {
+                       if (context)
+                         context->force_keep_session_state_ = true;
+                     },
+                     weak_factory_.GetWeakPtr()));
 }
 
 void IndexedDBContextImpl::ApplyPolicyUpdates(
     std::vector<storage::mojom::StoragePolicyUpdatePtr> policy_updates) {
+  DCHECK(IDBTaskRunner()->RunsTasksInCurrentSequence());
   for (const auto& update : policy_updates) {
-    // TODO(https://crbug.com/1199077): Use the real StorageKey when available.
-    GetOrCreateDefaultBucket(
-        blink::StorageKey(update->origin),
-        base::BindOnce(&IndexedDBContextImpl::ApplyPolicyUpdateImpl,
-                       weak_factory_.GetWeakPtr(), *update));
-  }
-}
-
-void IndexedDBContextImpl::ApplyPolicyUpdateImpl(
-    const storage::mojom::StoragePolicyUpdate& policy_update,
-    const absl::optional<storage::BucketLocator>& bucket_locator) {
-  if (!bucket_locator)
-    return;
-  DCHECK_EQ(blink::StorageKey(policy_update.origin),
-            bucket_locator->storage_key);
-  if (!policy_update.purge_on_shutdown) {
-    buckets_to_purge_on_shutdown_.erase(*bucket_locator);
-  } else {
-    buckets_to_purge_on_shutdown_.insert(*bucket_locator);
+    if (!update->purge_on_shutdown) {
+      sites_to_purge_on_shutdown_.erase(net::SchemefulSite(update->origin));
+    } else {
+      sites_to_purge_on_shutdown_.insert(net::SchemefulSite(update->origin));
+    }
   }
 }
 
@@ -650,12 +636,12 @@
   IDBTaskRunner()->PostTask(
       FROM_HERE,
       base::BindOnce(
-          [](IndexedDBContextImpl* context,
+          [](base::WeakPtr<IndexedDBContextImpl> context,
              mojo::PendingRemote<storage::mojom::IndexedDBObserver> observer) {
-            context->observers_.Add(std::move(observer));
+            if (context)
+              context->observers_.Add(std::move(observer));
           },
-          // `this` is destroyed on idb_task_runner_ so it's safe to post raw.
-          base::Unretained(this), std::move(observer)));
+          weak_factory_.GetWeakPtr(), std::move(observer)));
 }
 
 void IndexedDBContextImpl::GetBaseDataPathForTesting(
@@ -1044,7 +1030,7 @@
     return;
 
   // Clear session-only databases.
-  if (buckets_to_purge_on_shutdown_.empty())
+  if (sites_to_purge_on_shutdown_.empty())
     return;
 
   IndexedDBFactoryImpl* factory = GetIDBFactory();
@@ -1052,7 +1038,16 @@
       DefaultBucketFilePerFirstPartyStorageKey(GetFirstPartyDataPath());
   const auto& bucket_id_to_file_path =
       DefaultBucketFilePerThirdPartyBucketId(GetThirdPartyDataPath());
-  for (const auto& bucket_locator : buckets_to_purge_on_shutdown_) {
+  for (const auto& bucket_locator : bucket_set_) {
+    const auto& origin_it = sites_to_purge_on_shutdown_.find(
+        net::SchemefulSite(bucket_locator.storage_key.origin()));
+    const auto& top_site_it = sites_to_purge_on_shutdown_.find(
+        bucket_locator.storage_key.top_level_site());
+    if (origin_it == sites_to_purge_on_shutdown_.end() &&
+        top_site_it == sites_to_purge_on_shutdown_.end()) {
+      // No match for a site we want to clear in this bucket locator.
+      continue;
+    }
     base::FilePath path;
     const auto& first_party_it =
         storage_key_to_file_path.find(bucket_locator.storage_key);
@@ -1082,8 +1077,12 @@
     return;
 
   idb_task_runner_->PostTask(
-      FROM_HERE, base::BindOnce(&IndexedDBContextImpl::ShutdownOnIDBSequence,
-                                base::WrapRefCounted(this)));
+      FROM_HERE,
+      base::BindOnce(
+          &IndexedDBContextImpl::InitializeFromFilesIfNeeded,
+          weak_factory_.GetWeakPtr(),
+          base::BindOnce(&IndexedDBContextImpl::ShutdownOnIDBSequence,
+                         base::WrapRefCounted(this))));
 }
 
 base::FilePath IndexedDBContextImpl::GetBlobStorePath(
@@ -1161,31 +1160,29 @@
       GetOrCreateDefaultBucket(
           iter->first,
           base::BindOnce(
-              [](IndexedDBContextImpl* context,
+              [](base::WeakPtr<IndexedDBContextImpl> context,
                  base::OnceClosure inner_callback,
                  const absl::optional<storage::BucketLocator>& bucket_locator) {
-                if (bucket_locator)
-                  context->bucket_set_.insert(*bucket_locator);
+                if (context) {
+                  if (bucket_locator)
+                    context->bucket_set_.insert(*bucket_locator);
+                  context->did_initialize_from_files_ = true;
+                }
                 std::move(inner_callback).Run();
-                context->did_initialize_from_files_ = true;
               },
-              // `this` is destroyed on idb_task_runner_ so it's safe to post
-              // raw.
-              base::Unretained(this), std::move(callback)));
+              weak_factory_.GetWeakPtr(), std::move(callback)));
       // This path is only invoked once but the analyzer doesn't know that.
       callback = base::DoNothing();
     } else {
       GetOrCreateDefaultBucket(
           iter->first,
           base::BindOnce(
-              [](IndexedDBContextImpl* context,
+              [](base::WeakPtr<IndexedDBContextImpl> context,
                  const absl::optional<storage::BucketLocator>& bucket_locator) {
-                if (bucket_locator)
+                if (bucket_locator && context)
                   context->bucket_set_.insert(*bucket_locator);
               },
-              // `this` is destroyed on idb_task_runner_ so it's safe to post
-              // raw.
-              base::Unretained(this)));
+              weak_factory_.GetWeakPtr()));
     }
   }
   for (auto iter = bucket_id_to_file_path.begin();
@@ -1196,31 +1193,29 @@
       GetBucketById(
           iter->first,
           base::BindOnce(
-              [](IndexedDBContextImpl* context,
+              [](base::WeakPtr<IndexedDBContextImpl> context,
                  base::OnceClosure inner_callback,
                  const absl::optional<storage::BucketLocator>& bucket_locator) {
-                if (bucket_locator)
-                  context->bucket_set_.insert(*bucket_locator);
+                if (context) {
+                  if (bucket_locator)
+                    context->bucket_set_.insert(*bucket_locator);
+                  context->did_initialize_from_files_ = true;
+                }
                 std::move(inner_callback).Run();
-                context->did_initialize_from_files_ = true;
               },
-              // `this` is destroyed on idb_task_runner_ so it's safe to post
-              // raw.
-              base::Unretained(this), std::move(callback)));
+              weak_factory_.GetWeakPtr(), std::move(callback)));
       // This path is only invoked once but the analyzer doesn't know that.
       callback = base::DoNothing();
     } else {
       GetBucketById(
           iter->first,
           base::BindOnce(
-              [](IndexedDBContextImpl* context,
+              [](base::WeakPtr<IndexedDBContextImpl> context,
                  const absl::optional<storage::BucketLocator>& bucket_locator) {
-                if (bucket_locator)
+                if (bucket_locator && context)
                   context->bucket_set_.insert(*bucket_locator);
               },
-              // `this` is destroyed on idb_task_runner_ so it's safe to post
-              // raw.
-              base::Unretained(this)));
+              weak_factory_.GetWeakPtr()));
     }
   }
 }
@@ -1246,23 +1241,24 @@
     quota_manager_proxy_->GetOrCreateBucket(
         storage::BucketInitParams(storage_key), idb_task_runner_,
         base::BindOnce(
-            [](IndexedDBContextImpl* context,
+            [](base::WeakPtr<IndexedDBContextImpl> context,
                DidGetBucketLocatorCallback inner_callback,
                storage::QuotaErrorOr<storage::BucketInfo> result) {
               if (result.ok()) {
                 const auto& bucket_locator = result->ToBucketLocator();
-                context->storage_key_to_bucket_locator_[bucket_locator
-                                                            .storage_key] =
-                    bucket_locator;
-                context->bucket_id_to_bucket_locator_[bucket_locator.id] =
-                    bucket_locator;
+                if (context) {
+                  context->storage_key_to_bucket_locator_[bucket_locator
+                                                              .storage_key] =
+                      bucket_locator;
+                  context->bucket_id_to_bucket_locator_[bucket_locator.id] =
+                      bucket_locator;
+                }
                 std::move(inner_callback).Run(bucket_locator);
               } else {
                 std::move(inner_callback).Run(absl::nullopt);
               }
             },
-            // `this` is destroyed on idb_task_runner_ so it's safe to post raw.
-            base::Unretained(this), std::move(callback)));
+            weak_factory_.GetWeakPtr(), std::move(callback)));
   } else {
     std::move(callback).Run(bucket_locator->second);
   }
@@ -1276,23 +1272,24 @@
     quota_manager_proxy_->GetBucketById(
         bucket_id, idb_task_runner_,
         base::BindOnce(
-            [](IndexedDBContextImpl* context,
+            [](base::WeakPtr<IndexedDBContextImpl> context,
                DidGetBucketLocatorCallback inner_callback,
                storage::QuotaErrorOr<storage::BucketInfo> result) {
               if (result.ok()) {
                 const auto& bucket_locator = result->ToBucketLocator();
-                context->storage_key_to_bucket_locator_[bucket_locator
-                                                            .storage_key] =
-                    bucket_locator;
-                context->bucket_id_to_bucket_locator_[bucket_locator.id] =
-                    bucket_locator;
+                if (context) {
+                  context->storage_key_to_bucket_locator_[bucket_locator
+                                                              .storage_key] =
+                      bucket_locator;
+                  context->bucket_id_to_bucket_locator_[bucket_locator.id] =
+                      bucket_locator;
+                }
                 std::move(inner_callback).Run(bucket_locator);
               } else {
                 std::move(inner_callback).Run(absl::nullopt);
               }
             },
-            // `this` is destroyed on idb_task_runner_ so it's safe to post raw.
-            base::Unretained(this), std::move(callback)));
+            weak_factory_.GetWeakPtr(), std::move(callback)));
   } else {
     std::move(callback).Run(bucket_locator->second);
   }
diff --git a/content/browser/indexed_db/indexed_db_context_impl.h b/content/browser/indexed_db/indexed_db_context_impl.h
index 67326788..731532d7 100644
--- a/content/browser/indexed_db/indexed_db_context_impl.h
+++ b/content/browser/indexed_db/indexed_db_context_impl.h
@@ -33,6 +33,7 @@
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/bindings/remote_set.h"
+#include "net/base/schemeful_site.h"
 #include "storage/browser/quota/quota_manager_proxy.h"
 #include "third_party/blink/public/common/storage_key/storage_key.h"
 
@@ -246,9 +247,6 @@
   void DownloadBucketDataImpl(
       DownloadBucketDataCallback callback,
       const absl::optional<storage::BucketLocator>& bucket_locator);
-  void ApplyPolicyUpdateImpl(
-      const storage::mojom::StoragePolicyUpdate& policy_update,
-      const absl::optional<storage::BucketLocator>& bucket_locator);
 
   void ShutdownOnIDBSequence();
 
@@ -317,8 +315,9 @@
   const scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy_;
   std::set<storage::BucketLocator> bucket_set_;
   std::map<storage::BucketLocator, int64_t> bucket_size_map_;
-  // The set of buckets whose storage should be cleared on shutdown.
-  std::set<storage::BucketLocator> buckets_to_purge_on_shutdown_;
+  // The set of sites whose storage should be cleared on shutdown. These are
+  // matched against the origin and top level site in each bucket's StorageKey.
+  std::set<net::SchemefulSite> sites_to_purge_on_shutdown_;
   const raw_ptr<base::Clock> clock_;
 
   const std::unique_ptr<IndexedDBQuotaClient> quota_client_;
diff --git a/content/browser/indexed_db/indexed_db_unittest.cc b/content/browser/indexed_db/indexed_db_unittest.cc
index 7c22062..920de2a 100644
--- a/content/browser/indexed_db/indexed_db_unittest.cc
+++ b/content/browser/indexed_db/indexed_db_unittest.cc
@@ -91,6 +91,10 @@
   storage::BucketLocator kNormalThirdPartyBucketLocator;
   blink::StorageKey kSessionOnlyThirdPartyStorageKey;
   storage::BucketLocator kSessionOnlyThirdPartyBucketLocator;
+  blink::StorageKey kInvertedNormalThirdPartyStorageKey;
+  storage::BucketLocator kInvertedNormalThirdPartyBucketLocator;
+  blink::StorageKey kInvertedSessionOnlyThirdPartyStorageKey;
+  storage::BucketLocator kInvertedSessionOnlyThirdPartyBucketLocator;
 
   IndexedDBTest()
       : quota_manager_proxy_(
@@ -142,13 +146,30 @@
         kSessionOnlyThirdPartyStorageKey;
     context()->RegisterBucketLocatorToSkipQuotaLookupForTesting(
         kSessionOnlyThirdPartyBucketLocator);
+    kInvertedNormalThirdPartyStorageKey =
+        blink::StorageKey(url::Origin::Create(GURL("http://rando/")),
+                          url::Origin::Create(GURL("http://normal/")));
+    kInvertedNormalThirdPartyBucketLocator = storage::BucketLocator();
+    kInvertedNormalThirdPartyBucketLocator.id =
+        storage::BucketId::FromUnsafeValue(3);
+    kInvertedNormalThirdPartyBucketLocator.storage_key =
+        kInvertedNormalThirdPartyStorageKey;
+    context()->RegisterBucketLocatorToSkipQuotaLookupForTesting(
+        kInvertedNormalThirdPartyBucketLocator);
+    kInvertedSessionOnlyThirdPartyStorageKey =
+        blink::StorageKey(url::Origin::Create(GURL("http://rando/")),
+                          url::Origin::Create(GURL("http://session-only/")));
+    kInvertedSessionOnlyThirdPartyBucketLocator = storage::BucketLocator();
+    kInvertedSessionOnlyThirdPartyBucketLocator.id =
+        storage::BucketId::FromUnsafeValue(4);
+    kInvertedSessionOnlyThirdPartyBucketLocator.storage_key =
+        kInvertedSessionOnlyThirdPartyStorageKey;
+    context()->RegisterBucketLocatorToSkipQuotaLookupForTesting(
+        kInvertedSessionOnlyThirdPartyBucketLocator);
     std::vector<storage::mojom::StoragePolicyUpdatePtr> policy_updates;
     policy_updates.emplace_back(storage::mojom::StoragePolicyUpdate::New(
         kSessionOnlyFirstPartyStorageKey.origin(),
         /*should_purge_on_shutdown=*/true));
-    policy_updates.emplace_back(storage::mojom::StoragePolicyUpdate::New(
-        kSessionOnlyThirdPartyStorageKey.origin(),
-        /*should_purge_on_shutdown=*/true));
     context_->ApplyPolicyUpdates(std::move(policy_updates));
   }
 
@@ -226,6 +247,8 @@
   base::FilePath session_only_path_first_party;
   base::FilePath normal_path_third_party;
   base::FilePath session_only_path_third_party;
+  base::FilePath inverted_normal_path_third_party;
+  base::FilePath inverted_session_only_path_third_party;
 
   normal_path_first_party =
       GetFilePathForTesting(kNormalFirstPartyBucketLocator);
@@ -235,32 +258,44 @@
       GetFilePathForTesting(kNormalThirdPartyBucketLocator);
   session_only_path_third_party =
       GetFilePathForTesting(kSessionOnlyThirdPartyBucketLocator);
+  inverted_normal_path_third_party =
+      GetFilePathForTesting(kInvertedNormalThirdPartyBucketLocator);
+  inverted_session_only_path_third_party =
+      GetFilePathForTesting(kInvertedSessionOnlyThirdPartyBucketLocator);
   if (IsThirdPartyStoragePartitioningEnabled()) {
     EXPECT_NE(normal_path_first_party, normal_path_third_party);
     EXPECT_NE(session_only_path_first_party, session_only_path_third_party);
+    EXPECT_NE(inverted_normal_path_third_party,
+              inverted_session_only_path_third_party);
   } else {
     EXPECT_EQ(normal_path_first_party, normal_path_third_party);
     EXPECT_EQ(session_only_path_first_party, session_only_path_third_party);
+    EXPECT_EQ(inverted_normal_path_third_party,
+              inverted_session_only_path_third_party);
   }
 
   ASSERT_TRUE(base::CreateDirectory(normal_path_first_party));
   ASSERT_TRUE(base::CreateDirectory(session_only_path_first_party));
   ASSERT_TRUE(base::CreateDirectory(normal_path_third_party));
   ASSERT_TRUE(base::CreateDirectory(session_only_path_third_party));
+  ASSERT_TRUE(base::CreateDirectory(inverted_normal_path_third_party));
+  ASSERT_TRUE(base::CreateDirectory(inverted_session_only_path_third_party));
+
+  context()->ForceInitializeFromFilesForTesting(base::DoNothing());
   base::RunLoop().RunUntilIdle();
 
   context()->Shutdown();
-
   base::RunLoop().RunUntilIdle();
 
   EXPECT_TRUE(base::DirectoryExists(normal_path_first_party));
   EXPECT_FALSE(base::DirectoryExists(session_only_path_first_party));
   EXPECT_TRUE(base::DirectoryExists(normal_path_third_party));
+  EXPECT_FALSE(base::DirectoryExists(session_only_path_third_party));
+  EXPECT_TRUE(base::DirectoryExists(inverted_normal_path_third_party));
   if (IsThirdPartyStoragePartitioningEnabled()) {
-    // TODO(https://crbug.com/1199077): Policy updates are per-origin.
-    EXPECT_TRUE(base::DirectoryExists(session_only_path_third_party));
+    EXPECT_FALSE(base::DirectoryExists(inverted_session_only_path_third_party));
   } else {
-    EXPECT_FALSE(base::DirectoryExists(session_only_path_third_party));
+    EXPECT_TRUE(base::DirectoryExists(inverted_session_only_path_third_party));
   }
 }
 
@@ -272,6 +307,7 @@
 
   // Save session state. This should bypass the destruction-time deletion.
   context()->SetForceKeepSessionState();
+  base::RunLoop().RunUntilIdle();
 
   normal_path_first_party =
       GetFilePathForTesting(kNormalFirstPartyBucketLocator);
@@ -293,10 +329,11 @@
   ASSERT_TRUE(base::CreateDirectory(session_only_path_first_party));
   ASSERT_TRUE(base::CreateDirectory(normal_path_third_party));
   ASSERT_TRUE(base::CreateDirectory(session_only_path_third_party));
+
+  context()->ForceInitializeFromFilesForTesting(base::DoNothing());
   base::RunLoop().RunUntilIdle();
 
   context()->Shutdown();
-
   base::RunLoop().RunUntilIdle();
 
   // No data was cleared because of SetForceKeepSessionState.
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index 4d81170..a1da50db 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -784,6 +784,12 @@
   LogQueueTimeHistogram("Navigation.QueueTime.OnReceiveResponse",
                         resource_request_->is_outermost_main_frame);
   head_ = std::move(head);
+
+  // Early Hints preloads should not be committed for PDF.
+  // See https://github.com/whatwg/html/issues/7823
+  if (head_->mime_type == "application/pdf" || head_->mime_type == "text/pdf")
+    early_hints_manager_.reset();
+
   if (response_body)
     OnStartLoadingResponseBody(std::move(response_body));
 }
diff --git a/content/browser/renderer_host/frame_tree_browsertest.cc b/content/browser/renderer_host/frame_tree_browsertest.cc
index dfcb619f..8d11abd 100644
--- a/content/browser/renderer_host/frame_tree_browsertest.cc
+++ b/content/browser/renderer_host/frame_tree_browsertest.cc
@@ -2607,9 +2607,14 @@
   EXPECT_TRUE(fenced_frame->IsInFencedFrameTree());
 
   // Since the fenced frame is not yet navigated, it's specific controller
-  // should have an entry count of 0.
+  // should have no entries, or should be on the initial NavigationEntry.
   if (GetParam() == blink::features::FencedFramesImplementationType::kMPArch) {
-    EXPECT_EQ(0, fenced_frame->navigator().controller().GetEntryCount());
+    EXPECT_TRUE(
+        !fenced_frame->navigator().controller().GetLastCommittedEntry() ||
+        fenced_frame->navigator()
+            .controller()
+            .GetLastCommittedEntry()
+            ->IsInitialEntry());
   }
 
   TestFrameNavigationObserver observer(fenced_frame);
@@ -2673,7 +2678,12 @@
               fenced_frame->current_frame_host()->GetLastCommittedURL());
   } else {
     EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
-    EXPECT_EQ(0, fenced_frame->navigator().controller().GetEntryCount());
+    EXPECT_TRUE(
+        !fenced_frame->navigator().controller().GetLastCommittedEntry() ||
+        fenced_frame->navigator()
+            .controller()
+            .GetLastCommittedEntry()
+            ->IsInitialEntry());
   }
 }
 
@@ -2776,8 +2786,13 @@
     // restored. Therefore we will only have the initial fenced frame without
     // any navigation.
     ASSERT_EQ(0U, new_entry->root_node()->children.size());
-    EXPECT_EQ(0,
-              restored_fenced_frame->navigator().controller().GetEntryCount());
+    EXPECT_TRUE(!restored_fenced_frame->navigator()
+                     .controller()
+                     .GetLastCommittedEntry() ||
+                restored_fenced_frame->navigator()
+                    .controller()
+                    .GetLastCommittedEntry()
+                    ->IsInitialEntry());
   }
 }
 
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index a092ccb..983de4a 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -7426,12 +7426,13 @@
 }
 
 void RenderFrameHostImpl::SubresourceResponseStarted(
-    const GURL& url,
+    const url::SchemeHostPort& final_response_url,
     net::CertStatus cert_status) {
-  OPTIONAL_TRACE_EVENT1(
-      "content", "RenderFrameHostImpl::SubresourceResponseStarted", "url", url);
+  OPTIONAL_TRACE_EVENT1("content",
+                        "RenderFrameHostImpl::SubresourceResponseStarted",
+                        "url", final_response_url.GetURL());
   frame_tree_->controller().ssl_manager()->DidStartResourceResponse(
-      url, cert_status);
+      final_response_url, cert_status);
 }
 
 void RenderFrameHostImpl::ResourceLoadComplete(
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
index 3de87142..94f9913 100644
--- a/content/browser/renderer_host/render_frame_host_impl.h
+++ b/content/browser/renderer_host/render_frame_host_impl.h
@@ -2738,7 +2738,7 @@
       mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client,
       mojo::PendingRemote<blink::mojom::PolicyContainerHostKeepAliveHandle>
           initiator_policy_container_host_keep_alive_handle) override;
-  void SubresourceResponseStarted(const GURL& url,
+  void SubresourceResponseStarted(const url::SchemeHostPort& final_response_url,
                                   net::CertStatus cert_status) override;
   void ResourceLoadComplete(
       blink::mojom::ResourceLoadInfoPtr resource_load_info) override;
diff --git a/content/browser/renderer_host/render_view_host_impl.cc b/content/browser/renderer_host/render_view_host_impl.cc
index f19a15f..dcd6248 100644
--- a/content/browser/renderer_host/render_view_host_impl.cc
+++ b/content/browser/renderer_host/render_view_host_impl.cc
@@ -707,6 +707,7 @@
 
 void RenderViewHostImpl::ClosePage() {
   is_waiting_for_page_close_completion_ = true;
+  DCHECK(GetMainRenderFrameHost()->IsOutermostMainFrame());
 
   if (IsRenderViewLive() && !SuddenTerminationAllowed()) {
     close_timeout_->Start(kUnloadTimeout);
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index a2efd55..b22ccbf 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -79,6 +79,10 @@
 #include "ui/android/screen_android.h"
 #endif
 
+#if BUILDFLAG(IS_MAC)
+#include "content/browser/renderer_host/test_render_widget_host_view_mac_factory.h"
+#endif
+
 #if defined(USE_AURA) || BUILDFLAG(IS_MAC)
 #include "content/browser/compositor/test/test_image_transport_factory.h"
 #endif
@@ -1319,17 +1323,22 @@
   host_->DidProcessFrame(1, base::TimeTicks::Now());
 }
 
-// Unable to include render_widget_host_view_mac.h and compile.
-#if !BUILDFLAG(IS_MAC)
 // Tests setting background transparency.
 TEST_F(RenderWidgetHostTest, Background) {
   RenderWidgetHostViewBase* view;
 #if defined(USE_AURA)
   view = new RenderWidgetHostViewAura(host_.get());
-  // TODO(derat): Call this on all platforms: http://crbug.com/102450.
-  view->InitAsChild(nullptr);
 #elif BUILDFLAG(IS_ANDROID)
   view = new RenderWidgetHostViewAndroid(host_.get(), nullptr);
+#elif BUILDFLAG(IS_MAC)
+  view = CreateRenderWidgetHostViewMacForTesting(host_.get());
+#else
+#error "This test isn't implemented for this platform."
+#endif
+
+#if !BUILDFLAG(IS_ANDROID)
+  // TODO(derat): Call this on all platforms: http://crbug.com/102450.
+  view->InitAsChild(nullptr);
 #endif
   host_->SetView(view);
 
@@ -1352,9 +1361,15 @@
     // The owner delegate will be called to pass it over IPC to the RenderView.
     EXPECT_CALL(mock_owner_delegate_, SetBackgroundOpaque(false));
     view->SetBackgroundColor(SK_ColorTRANSPARENT);
+#if BUILDFLAG(IS_MAC)
+    // Mac replaces transparent background colors with white. See the comment in
+    // RenderWidgetHostViewMac::GetBackgroundColor. (https://crbug.com/735407)
+    EXPECT_EQ(unsigned{SK_ColorWHITE}, *view->GetBackgroundColor());
+#else
     // The browser side will represent the background color as transparent
     // immediately.
     EXPECT_EQ(unsigned{SK_ColorTRANSPARENT}, *view->GetBackgroundColor());
+#endif
   }
   {
     // Setting back an opaque color informs the view.
@@ -1366,7 +1381,6 @@
   host_->SetView(nullptr);
   view->Destroy();
 }
-#endif
 
 // Test that the RenderWidgetHost tells the renderer when it is hidden and
 // shown, and can accept a racey update from the renderer after hiding.
diff --git a/content/browser/renderer_host/test_render_widget_host_view_mac_factory.h b/content/browser/renderer_host/test_render_widget_host_view_mac_factory.h
new file mode 100644
index 0000000..e2645962
--- /dev/null
+++ b/content/browser/renderer_host/test_render_widget_host_view_mac_factory.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2022 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 CONTENT_BROWSER_RENDERER_HOST_TEST_RENDER_WIDGET_HOST_VIEW_MAC_FACTORY_H_
+#define CONTENT_BROWSER_RENDERER_HOST_TEST_RENDER_WIDGET_HOST_VIEW_MAC_FACTORY_H_
+
+#include "content/browser/renderer_host/render_widget_host_view_base.h"
+
+namespace content {
+
+class RenderWidgetHost;
+
+// Returns a new RenderWidgetHostViewMac that can be used in a C++ unit test or
+// browser test. These tests can't include render_widget_host_view_mac.h
+// directly because it contains Objective-C code. The returned object must be
+// deleted by calling its `Destroy` method (see the comments in
+// render_widget_host_view_mac.h.)
+RenderWidgetHostViewBase* CreateRenderWidgetHostViewMacForTesting(
+    RenderWidgetHost* widget);
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_RENDERER_HOST_TEST_RENDER_WIDGET_HOST_VIEW_MAC_FACTORY_H_
diff --git a/content/browser/renderer_host/test_render_widget_host_view_mac_factory.mm b/content/browser/renderer_host/test_render_widget_host_view_mac_factory.mm
new file mode 100644
index 0000000..fff2272
--- /dev/null
+++ b/content/browser/renderer_host/test_render_widget_host_view_mac_factory.mm
@@ -0,0 +1,17 @@
+// Copyright (c) 2022 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 "content/browser/renderer_host/test_render_widget_host_view_mac_factory.h"
+
+#include "content/browser/renderer_host/render_widget_host_view_mac.h"
+#include "content/public/browser/render_widget_host.h"
+
+namespace content {
+
+RenderWidgetHostViewBase* CreateRenderWidgetHostViewMacForTesting(
+    RenderWidgetHost* widget) {
+  return new RenderWidgetHostViewMac(widget);
+}
+
+}  // namespace content
diff --git a/content/browser/resources/BUILD.gn b/content/browser/resources/BUILD.gn
index 2b6281e..be349189 100644
--- a/content/browser/resources/BUILD.gn
+++ b/content/browser/resources/BUILD.gn
@@ -11,7 +11,6 @@
 if (enable_js_type_check) {
   group("closure_compile") {
     deps = [
-      "attribution_reporting:closure_compile",
       "histograms:closure_compile",
       "process:closure_compile",
     ]
diff --git a/content/browser/resources/attribution_reporting/BUILD.gn b/content/browser/resources/attribution_reporting/BUILD.gn
index fbdeee4..5a826cd1 100644
--- a/content/browser/resources/attribution_reporting/BUILD.gn
+++ b/content/browser/resources/attribution_reporting/BUILD.gn
@@ -2,22 +2,42 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import("//third_party/closure_compiler/compile_js.gni")
+import("//tools/polymer/html_to_js.gni")
+import("//tools/typescript/ts_library.gni")
 
-js_type_check("closure_compile") {
-  closure_flags = default_closure_args + mojom_js_args + [
-                    "js_module_root=" + rebase_path(".", root_build_dir),
-                    "js_module_root=" + rebase_path(
-                            "$root_gen_dir/mojom-webui/content/browser/attribution_reporting",
-                            root_build_dir),
-                  ]
-  deps = [ ":attribution_internals" ]
+html_to_js("web_components") {
+  js_files = [ "attribution_internals_table.ts" ]
 }
 
-js_library("attribution_internals") {
+# Copy (via creating sym links) all the other files into the same folder for
+# ts_library.
+copy("copy_files") {
+  deps = [ "//content/browser/attribution_reporting:mojo_bindings_webui_js" ]
+  sources = [
+    "$root_gen_dir/mojom-webui/content/browser/attribution_reporting/attribution_internals.mojom-webui.js",
+    "./attribution_internals.ts",
+    "./table_model.ts",
+  ]
+  outputs = [ "$target_gen_dir/{{source_file_part}}" ]
+}
+
+ts_library("build_ts") {
+  root_dir = target_gen_dir
+  out_dir = "$target_gen_dir/tsc"
+  tsconfig_base = "tsconfig_base.json"
+  in_files = [
+    "attribution_internals.ts",
+    "attribution_internals_table.ts",
+    "table_model.ts",
+    "attribution_internals.mojom-webui.js",
+  ]
   deps = [
-    "//content/browser/attribution_reporting:mojo_bindings_webui_js",
-    "//ui/webui/resources/js:assert.m",
-    "//ui/webui/resources/js:util.m",
+    "//ui/webui/resources:library",
+    "//ui/webui/resources/mojo:library",
+  ]
+  definitions = [ "//tools/typescript/definitions/chrome_send.d.ts" ]
+  extra_deps = [
+    ":copy_files",
+    ":web_components",
   ]
 }
diff --git a/content/browser/resources/attribution_reporting/attribution_internals.css b/content/browser/resources/attribution_reporting/attribution_internals.css
index d697d28..f9ef6f9 100644
--- a/content/browser/resources/attribution_reporting/attribution_internals.css
+++ b/content/browser/resources/attribution_reporting/attribution_internals.css
@@ -49,71 +49,16 @@
   padding: 19px;
 }
 
-.table-wrapper {
-  background-color: #fff;
-  border-color: rgba(0,0,0,.12);
-  border-radius: 4px;
-  border-style: solid;
-  border-width: 1px;
-  overflow-x: scroll;
-}
-
-table {
-  border: 0;
-  border-collapse: collapse;
-}
-
-tbody tr {
-  border-top-color: rgba(0,0,0,.12);
-  border-top-style: solid;
-  border-top-width: 1px;
-}
-
-thead tr {
-  border: 0;
-}
-
-table td,
-table th {
-  padding-inline-end: 16px;
-  padding-inline-start: 16px;
-}
-
-th[aria-sort] {
-  cursor: pointer;
-}
-
-th[aria-sort]::after {
-  content: '⬍';
-}
-
-th[aria-sort='ascending']::after {
-  content: '⬆';
-}
-
-th[aria-sort='descending']::after {
-  content: '⬇';
-}
-
 button {
   font: inherit;
   margin-inline-end: 30px;
 }
 
-.http-error {
-  background-color: rgb(250,199,192);
-}
-
-.debug-url {
-  color: rgb(66,135,245);
-  font-weight: bold;
-}
-
-tab::before {
+div[slot='tab']::before {
   color: transparent;
   content: '⬤ ';
 }
 
-tab.unread::before {
+div[slot='tab'].unread::before {
   color: rgb(26,115,232);
 }
diff --git a/content/browser/resources/attribution_reporting/attribution_internals.html b/content/browser/resources/attribution_reporting/attribution_internals.html
index 90b6355..7f9d8a4 100644
--- a/content/browser/resources/attribution_reporting/attribution_internals.html
+++ b/content/browser/resources/attribution_reporting/attribution_internals.html
@@ -7,7 +7,6 @@
   <meta charset="utf-8">
   <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
   <link rel="stylesheet" href="chrome://resources/css/roboto.css">
-  <link rel="stylesheet" href="chrome://resources/css/tabs.css">
 
   <link rel="stylesheet" href="attribution_internals.css">
   <script type="module" src="attribution_internals.js"></script>
@@ -22,48 +21,48 @@
 <button id="clear-data">Clear all attribution data</button>
 </header>
 <main>
-  <tabbox>
-    <tabs>
-      <tab id="sources-tab">Sources</tab>
-      <tab id="triggers-tab">Triggers</tab>
-      <tab id="event-level-reports-tab">Event-Level Reports</tab>
-      <tab id="aggregatable-reports-tab">Aggregatable Reports</tab>
-    </tabs>
-    <tabpanels>
-      <tabpanel>
-        <h2>Sources</h2>
-        <div class="content">
-          <div class="table-wrapper" id="source-table-wrapper"></div>
+  <cr-tab-box hidden>
+    <div id="sources-tab" slot="tab">Sources</div>
+    <div id="triggers-tab" slot="tab">Triggers</div>
+    <div id="event-level-reports-tab" slot="tab">Event-Level Reports</div>
+    <div id="aggregatable-reports-tab" slot="tab">Aggregatable Reports</div>
+    <div slot="panel">
+      <h2>Sources</h2>
+      <div class="content">
+        <attribution-internals-table id="sourceTable">
+        </attribution-internals-table>
+      </div>
+    </div>
+    <div slot="panel">
+      <h2>Triggers</h2>
+      <div class="content">
+        <attribution-internals-table id="triggerTable">
+        </attribution-internals-table>
+      </div>
+    </div>
+    <div slot="panel">
+      <h2>Sent and Pending Event-Level Reports</h2>
+      <div class="content">
+        <div>
+          <button id="send-reports" disabled>Send Selected Pending Event-Level Reports</button>
+          <label id="show-debug-event-reports"><input type="checkbox" checked>Show Debug Reports<span></span></label>
         </div>
-      </tabpanel>
-      <tabpanel>
-        <h2>Triggers</h2>
-        <div class="content">
-          <div class="table-wrapper" id="trigger-table-wrapper"></div>
+        <attribution-internals-table id="reportTable">
+        </attribution-internals-table>
+      </div>
+    </div>
+    <div slot="panel">
+      <h2>Sent and Pending Aggregatable Reports</h2>
+      <div class="content">
+        <div>
+          <button id="send-aggregatable-reports" disabled>Send Selected Pending Aggregatable Reports</button>
+          <label id="show-debug-aggregatable-reports"><input type="checkbox" checked>Show Debug Reports<span></span></label>
         </div>
-      </tabpanel>
-      <tabpanel>
-        <h2>Sent and Pending Event-Level Reports</h2>
-        <div class="content">
-          <div>
-            <button id="send-reports" disabled>Send Selected Pending Event-Level Reports</button>
-            <label id="show-debug-event-reports"><input type="checkbox" checked>Show Debug Reports<span></span></label>
-          </div>
-          <div class="table-wrapper" id="report-table-wrapper"></div>
-        </div>
-      </tabpanel>
-      <tabpanel>
-        <h2>Sent and Pending Aggregatable Reports</h2>
-        <div class="content">
-          <div>
-            <button id="send-aggregatable-reports" disabled>Send Selected Pending Aggregatable Reports</button>
-            <label id="show-debug-aggregatable-reports"><input type="checkbox" checked>Show Debug Reports<span></span></label>
-          </div>
-          <div class="table-wrapper" id="aggregatable-report-table-wrapper"></div>
-        </div>
-      </tabpanel>
-    </tabpanels>
-  </tabbox>
+        <attribution-internals-table id="aggregatableReportTable">
+        </attribution-internals-table>
+      </div>
+    </div>
+  </cr-tab-box>
 </main>
 </body>
 </html>
diff --git a/content/browser/resources/attribution_reporting/attribution_internals.js b/content/browser/resources/attribution_reporting/attribution_internals.js
deleted file mode 100644
index 05e5fc97..0000000
--- a/content/browser/resources/attribution_reporting/attribution_internals.js
+++ /dev/null
@@ -1,1168 +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.
-
-import {assert} from 'chrome://resources/js/assert.m.js';
-import {decorate} from 'chrome://resources/js/cr/ui.m.js';
-import {TabBox} from 'chrome://resources/js/cr/ui/tabs.js';
-import {getTrustedHTML} from 'chrome://resources/js/static_types.js';
-import {$, getRequiredElement, queryRequiredElement} from 'chrome://resources/js/util.m.js';
-import {Origin} from 'chrome://resources/mojo/url/mojom/origin.mojom-webui.js';
-
-import {Handler as AttributionInternalsHandler, HandlerRemote as AttributionInternalsHandlerRemote, ObserverInterface, ObserverReceiver, ReportStatus, ReportType, SourceType, WebUIReport, WebUISource, WebUISource_Attributability, WebUITrigger, WebUITrigger_Status} from './attribution_internals.mojom-webui.js';
-
-/**
- * @template T
- * @param {!T} a
- * @param {!T} b
- * @return {number}
- */
-function compareDefault(a, b) {
-  if (a < b) {
-    return -1;
-  }
-  if (a > b) {
-    return 1;
-  }
-  return 0;
-}
-
-/**
- * @param {string} key
- * @param {*} value
- * @return {*}
- */
-function bigint_replacer(key, value) {
-  return typeof value === 'bigint' ? value.toString() : value;
-}
-
-/**
- * @template T
- * @abstract
- */
-class Column {
-  constructor() {
-    /** @type {?function(!T, !T): number} */
-    this.compare;
-  }
-
-  /**
-   * @param {!Element} td
-   * @param {!T} row
-   * @abstract
-   */
-  render(td, row) {}
-
-  /**
-   * @param {!Element} th
-   * @abstract
-   */
-  renderHeader(th) {}
-}
-
-/**
- * @template T
- * @template V
- * @extends {Column<T>}
- */
-class ValueColumn extends Column {
-  /**
-   * @param {string} header
-   * @param {function(!T): !V} getValue
-   * @param {?function(!T, !T): number} compare
-   */
-  constructor(
-      header, getValue,
-      compare = (a, b) => compareDefault(getValue(a), getValue(b))) {
-    super();
-
-    this.header = header;
-
-    /** @protected */
-    this.getValue = getValue;
-
-    this.compare = compare;
-  }
-
-  /** @override */
-  render(td, row) {
-    td.innerText = this.getValue(row);
-  }
-
-  /** @override */
-  renderHeader(th) {
-    th.innerText = this.header;
-  }
-}
-
-/**
- * @template T
- * @extends {ValueColumn<T, Date>}
- */
-class DateColumn extends ValueColumn {
-  /**
-   * @param {string} header
-   * @param {function(!T): Date} getValue
-   */
-  constructor(header, getValue) {
-    super(header, getValue);
-  }
-
-  /** @override */
-  render(td, row) {
-    td.innerText = this.getValue(row).toLocaleString();
-  }
-}
-
-/**
- * @template T
- * @extends {ValueColumn<T, string>}
- */
-class CodeColumn extends ValueColumn {
-  /**
-   * @param {string} header
-   * @param {function(!T): string} getValue
-   */
-  constructor(header, getValue) {
-    super(header, getValue, /*compare=*/ null);
-  }
-
-  /** @override */
-  render(td, row) {
-    const code = td.ownerDocument.createElement('code');
-    code.innerText = this.getValue(row);
-
-    const pre = td.ownerDocument.createElement('pre');
-    pre.appendChild(code);
-
-    td.appendChild(pre);
-  }
-}
-
-const debugPathPattern =
-    /(?<=\/\.well-known\/attribution-reporting\/)debug(?=\/)/;
-
-/**
- * @extends {ValueColumn<Report, string>}
- */
-class ReportUrlColumn extends ValueColumn {
-  constructor() {
-    super('Report URL', (e) => e.reportUrl);
-  }
-
-  /** @override */
-  render(td, row) {
-    if (!row.isDebug) {
-      td.innerText = row.reportUrl;
-      return;
-    }
-
-    const [pre, post] = row.reportUrl.split(debugPathPattern, 2);
-    td.appendChild(new Text(pre));
-
-    const span = td.ownerDocument.createElement('span');
-    span.classList.add('debug-url');
-    span.innerText = 'debug';
-    td.appendChild(span);
-
-    td.appendChild(new Text(post));
-  }
-}
-
-/**
- * @template T
- * @abstract
- */
-class TableModel {
-  constructor() {
-    /** @type {!Array<Column<T>>} */
-    this.cols;
-
-    /** @type {string} */
-    this.emptyRowText;
-
-    /** @type {number} */
-    this.sortIdx = -1;
-
-    /** @type {!Set<function()>} */
-    this.rowsChangedListeners = new Set();
-  }
-
-  /**
-   * @param {!Element} tr
-   * @param {T} data
-   */
-  styleRow(tr, data) {}
-
-  /**
-   * @abstract
-   * @return {!Array<!T>}
-   */
-  getRows() {}
-
-  notifyRowsChanged() {
-    this.rowsChangedListeners.forEach((f) => f());
-  }
-}
-
-class Selectable {
-  constructor() {
-    this.input = document.createElement('input');
-    this.input.type = 'checkbox';
-  }
-}
-
-/**
- * @template T
- * @extends {Column<T>}
- */
-class SelectionColumn extends Column {
-  /**
-   * @param {!TableModel<T>} model
-   */
-  constructor(model) {
-    super();
-
-    this.model = model;
-
-    this.selectAll = document.createElement('input');
-    this.selectAll.type = 'checkbox';
-    this.selectAll.addEventListener('input', () => {
-      const checked = this.selectAll.checked;
-      this.model.getRows().forEach((row) => {
-        if (!row.input.disabled) {
-          row.input.checked = checked;
-        }
-      });
-      this.notifySelectionChanged(checked);
-    });
-
-    this.listener = () => this.onChange();
-    this.model.rowsChangedListeners.add(this.listener);
-
-    /** @type {!Set<function(boolean)>} */
-    this.selectionChangedListeners = new Set();
-  }
-
-  /** @override */
-  render(td, row) {
-    td.appendChild(row.input);
-  }
-
-  /** @override */
-  renderHeader(th) {
-    th.appendChild(this.selectAll);
-  }
-
-  onChange() {
-    let anySelectable = false;
-    let anySelected = false;
-    let anyUnselected = false;
-
-    this.model.getRows().forEach((row) => {
-      // addEventListener deduplicates, so only one event will be fired per
-      // input.
-      row.input.addEventListener('input', this.listener);
-
-      if (row.input.disabled) {
-        return;
-      }
-
-      anySelectable = true;
-
-      if (row.input.checked) {
-        anySelected = true;
-      } else {
-        anyUnselected = true;
-      }
-    });
-
-    this.selectAll.disabled = !anySelectable;
-    this.selectAll.checked = anySelected && !anyUnselected;
-    this.selectAll.indeterminate = anySelected && anyUnselected;
-
-    this.notifySelectionChanged(anySelected);
-  }
-
-  /** @param {boolean} anySelected */
-  notifySelectionChanged(anySelected) {
-    this.selectionChangedListeners.forEach((f) => f(anySelected));
-  }
-}
-
-/**
- * Table abstracts the logic for rendering and sorting a table by dynamically
- * modifying a given <div>'s prototype and managing its children. The table's
- * columns are supplied by a TableModel supplied to the decorate function. Each
- * Column knows how to render the underlying value of the row type T, and
- * optionally sort rows of type T by that value.
- *
- * @template T
- * @extends {HTMLDivElement}
- */
-class Table {
-  constructor() {
-    /** @private {!TableModel<T>} */
-    this.model;
-
-    /** @private {boolean} */
-    this.sortDesc;
-
-    /** @private {!Element} */
-    this.tbody;
-  }
-
-  /**
-   * @template T
-   * @param {!Element} self
-   * @param {!TableModel<T>} model
-   */
-  static decorate(self, model) {
-    self.__proto__ = Table.prototype;
-    self = /** @type {!Table} */ (self);
-
-    self.model = model;
-    self.sortDesc = false;
-
-    const tr = self.ownerDocument.createElement('tr');
-    self.model.cols.forEach((col, idx) => {
-      const th = self.ownerDocument.createElement('th');
-      th.scope = 'col';
-      col.renderHeader(th);
-
-      if (col.compare) {
-        th.role = 'button';
-        Table.setSortAttrs(th, /*sortDesc=*/ null);
-        th.addEventListener('click', () => self.changeSortHeader(idx));
-      }
-
-      tr.appendChild(th);
-    });
-
-    const thead = self.ownerDocument.createElement('thead');
-    thead.appendChild(tr);
-
-    self.tbody = self.ownerDocument.createElement('tbody');
-    self.setSpanningText(self.model.emptyRowText);
-
-    const table = self.ownerDocument.createElement('table');
-    table.appendChild(thead);
-    table.appendChild(self.tbody);
-
-    self.appendChild(table);
-
-    self.model.rowsChangedListeners.add(() => self.updateTbody());
-  }
-
-  /**
-   * @param {string} text
-   * @private
-   */
-  setSpanningText(text) {
-    const td = this.ownerDocument.createElement('td');
-    td.innerText = text;
-    td.colSpan = this.model.cols.length;
-
-    const tr = this.ownerDocument.createElement('tr');
-    tr.appendChild(td);
-
-    this.tbody.appendChild(tr);
-  }
-
-  /**
-   * @param {!Element} th
-   * @param {?boolean} sortDesc
-   * @private
-   */
-  static setSortAttrs(th, sortDesc) {
-    let nextDir;
-    if (sortDesc === null) {
-      th.ariaSort = 'none';
-      nextDir = 'ascending';
-    } else if (sortDesc) {
-      th.ariaSort = 'descending';
-      nextDir = 'ascending';
-    } else {
-      th.ariaSort = 'ascending';
-      nextDir = 'descending';
-    }
-
-    th.title = `Sort by ${th.innerText} ${nextDir}`;
-    th.ariaLabel = th.title;
-  }
-
-  /**
-   * @param {number} idx
-   * @private
-   */
-  changeSortHeader(idx) {
-    const ths = this.querySelectorAll('thead th');
-
-    if (idx === this.model.sortIdx) {
-      this.sortDesc = !this.sortDesc;
-    } else {
-      this.sortDesc = false;
-      if (this.model.sortIdx >= 0) {
-        Table.setSortAttrs(ths[this.model.sortIdx], /*descending=*/ null);
-      }
-    }
-
-    this.model.sortIdx = idx;
-    Table.setSortAttrs(ths[this.model.sortIdx], this.sortDesc);
-    this.updateTbody();
-  }
-
-  /**
-   * @param {!Array<T>} rows
-   * @private
-   */
-  sort(rows) {
-    if (this.model.sortIdx < 0) {
-      return;
-    }
-
-    const multiplier = this.sortDesc ? -1 : 1;
-    rows.sort(
-        (a, b) =>
-            this.model.cols[this.model.sortIdx].compare(a, b) * multiplier);
-  }
-
-  updateTbody() {
-    this.tbody.innerText = '';
-
-    const rows = this.model.getRows();
-
-    if (rows.length === 0) {
-      this.setSpanningText(this.model.emptyRowText);
-      return;
-    }
-
-    this.sort(rows);
-
-    rows.forEach((row) => {
-      const tr = this.ownerDocument.createElement('tr');
-      this.model.cols.forEach((col) => {
-        const td = this.ownerDocument.createElement('td');
-        col.render(td, row);
-        tr.appendChild(td);
-      });
-      this.model.styleRow(tr, row);
-      this.tbody.appendChild(tr);
-    });
-  }
-}
-
-Table.prototype.__proto__ = HTMLDivElement.prototype;
-
-class Source {
-  /**
-   * @param {!WebUISource} mojo
-   */
-  constructor(mojo) {
-    this.sourceEventId = mojo.sourceEventId;
-    this.impressionOrigin = OriginToText(mojo.impressionOrigin);
-    this.attributionDestination = mojo.attributionDestination;
-    this.reportingOrigin = OriginToText(mojo.reportingOrigin);
-    this.impressionTime = new Date(mojo.impressionTime);
-    this.expiryTime = new Date(mojo.expiryTime);
-    this.sourceType = SourceTypeToText(mojo.sourceType);
-    this.priority = mojo.priority;
-    this.filterData = JSON.stringify(mojo.filterData, null, ' ');
-    this.aggregatableSource = JSON.stringify(mojo.aggregatableSource, bigint_replacer, ' ');
-    this.debugKey = mojo.debugKey ? mojo.debugKey.value : '';
-    this.dedupKeys = mojo.dedupKeys.join(', ');
-    this.status = AttributabilityToText(mojo.attributability);
-  }
-}
-
-/** @extends {TableModel<Source>} */
-class SourceTableModel extends TableModel {
-  constructor() {
-    super();
-
-    this.cols = [
-      new ValueColumn('Source Event ID', (e) => e.sourceEventId),
-      new ValueColumn('Status', (e) => e.status),
-      new ValueColumn('Source Origin', (e) => e.impressionOrigin),
-      new ValueColumn('Destination', (e) => e.attributionDestination),
-      new ValueColumn('Report To', (e) => e.reportingOrigin),
-      new DateColumn('Source Registration Time', (e) => e.impressionTime),
-      new DateColumn('Expiry Time', (e) => e.expiryTime),
-      new ValueColumn('Source Type', (e) => e.sourceType),
-      new ValueColumn('Priority', (e) => e.priority),
-      new CodeColumn('Filter Data', (e) => e.filterData),
-      new CodeColumn('Aggregatable Source', (e) => e.aggregatableSource),
-      new ValueColumn('Debug Key', (e) => e.debugKey),
-      new ValueColumn('Dedup Keys', (e) => e.dedupKeys, /*compare=*/ null),
-    ];
-
-    this.emptyRowText = 'No sources.';
-
-    // Sort by source registration time by default.
-    this.sortIdx = 5;
-
-    /** @type {!Array<!Source>} */
-    this.unstoredSources = [];
-
-    /** @type {!Array<!Source>} */
-    this.storedSources = [];
-  }
-
-  /** @override */
-  getRows() {
-    return this.unstoredSources.concat(this.storedSources);
-  }
-
-  /** @param {!Array<!Source>} storedSources */
-  setStoredSources(storedSources) {
-    this.storedSources = storedSources;
-    this.notifyRowsChanged();
-  }
-
-  /** @param {!Source} source */
-  addUnstoredSource(source) {
-    // Prevent the page from consuming ever more memory if the user leaves the
-    // page open for a long time.
-    if (this.unstoredSources.length >= 1000) {
-      this.unstoredSources = [];
-    }
-
-    this.unstoredSources.push(source);
-    this.notifyRowsChanged();
-  }
-
-  clear() {
-    this.storedSources = [];
-    this.unstoredSources = [];
-    this.notifyRowsChanged();
-  }
-}
-
-class Trigger {
-  /**
-   * @param {!WebUITrigger} mojo
-   */
-  constructor(mojo) {
-    this.triggerTime = new Date(mojo.triggerTime);
-    this.destinationOrigin = OriginToText(mojo.destinationOrigin);
-    this.reportingOrigin = OriginToText(mojo.reportingOrigin);
-    this.filters = JSON.stringify(mojo.filters, null, ' ');
-    this.debugKey = mojo.debugKey ? mojo.debugKey.value : '';
-
-    this.eventTriggers = JSON.stringify(
-        mojo.eventTriggers.map((e) => {
-          // Omit the dedup key, filters, and not filters if they are empty for
-          // brevity.
-          return {
-            'data': e.data,
-            'priority': e.priority,
-            'deduplication_key': e.dedupKey ? e.dedupKey.value : undefined,
-            'filters': Object.entries(e.filters).length > 0 ? e.filters :
-                                                              undefined,
-            'not_filters': Object.entries(e.notFilters).length > 0 ?
-                e.notFilters :
-                undefined,
-          };
-        }),
-        bigint_replacer, ' ');
-
-    this.eventLevelStatus = TriggerStatusToText(mojo.eventLevelStatus);
-    this.aggregatableStatus = TriggerStatusToText(mojo.aggregatableStatus);
-  }
-}
-
-/** @extends {TableModel<Trigger>} */
-class TriggerTableModel extends TableModel {
-  constructor() {
-    super();
-
-    this.cols = [
-      new DateColumn('Trigger Time', (e) => e.triggerTime),
-      new ValueColumn('Event-Level Status', (e) => e.eventLevelStatus),
-      new ValueColumn('Aggregatable Status', (e) => e.aggregatableStatus),
-      new ValueColumn('Destination', (e) => e.destinationOrigin),
-      new ValueColumn('Report To', (e) => e.reportingOrigin),
-      new ValueColumn('Debug Key', (e) => e.debugKey),
-      new CodeColumn('Filters', (e) => e.filters),
-      new CodeColumn('Event Triggers', (e) => e.eventTriggers),
-    ];
-
-    this.emptyRowText = 'No triggers.';
-
-    // Sort by trigger time by default.
-    this.sortIdx = 0;
-
-    /** @type {!Array<!Trigger>} */
-    this.triggers = [];
-  }
-
-  /** @override */
-  getRows() {
-    return this.triggers;
-  }
-
-  /** @param {!Trigger} trigger */
-  addTrigger(trigger) {
-    // Prevent the page from consuming ever more memory if the user leaves the
-    // page open for a long time.
-    if (this.triggers.length >= 1000) {
-      this.triggers = [];
-    }
-
-    this.triggers.push(trigger);
-    this.notifyRowsChanged();
-  }
-
-  clear() {
-    this.triggers = [];
-    this.notifyRowsChanged();
-  }
-}
-
-class Report extends Selectable {
-  /**
-   * @param {!WebUIReport} mojo
-   */
-  constructor(mojo) {
-    super();
-
-    this.id = mojo.id;
-    this.reportBody = mojo.reportBody;
-    this.reportUrl = mojo.reportUrl.url;
-    this.triggerTime = new Date(mojo.triggerTime);
-    this.reportTime = new Date(mojo.reportTime);
-
-    // Only pending reports are selectable.
-    if (mojo.status.pending === undefined) {
-      this.input.disabled = true;
-    }
-
-    this.isDebug = this.reportUrl.indexOf(
-                       '/.well-known/attribution-reporting/debug/') >= 0;
-
-    if (mojo.status.sent !== undefined) {
-      this.status = `Sent: HTTP ${mojo.status.sent}`;
-      this.httpResponseCode = mojo.status.sent;
-    } else if (mojo.status.pending !== undefined) {
-      this.status = 'Pending';
-    } else if (mojo.status.replacedByHigherPriorityReport !== undefined) {
-      this.status = `Replaced by higher-priority report: ${
-          mojo.status.replacedByHigherPriorityReport}`;
-    } else if (mojo.status.prohibitedByBrowserPolicy !== undefined) {
-      this.status = 'Prohibited by browser policy';
-    } else if (mojo.status.networkError !== undefined) {
-      this.status = `Network error: ${mojo.status.networkError}`;
-    } else if (mojo.status.failedToAssemble !== undefined) {
-      this.status = 'Dropped due to assembly failure';
-    } else {
-      throw new Error('invalid ReportStatus union');
-    }
-  }
-}
-
-/** @extends {Report} */
-class EventLevelReport extends Report {
-  /**
-   * @param {!WebUIReport} mojo
-   */
-  constructor(mojo) {
-    super(mojo);
-
-    this.reportPriority = mojo.data.eventLevelData.priority;
-    this.attributedTruthfully = mojo.data.eventLevelData.attributedTruthfully;
-  }
-}
-
-/** @extends {Report} */
-class AggregatableAttributionReport extends Report {
-  /**
-   * @param {!WebUIReport} mojo
-   */
-  constructor(mojo) {
-    super(mojo);
-
-    this.contributions = JSON.stringify(
-        mojo.data.aggregatableAttributionData.contributions, bigint_replacer, ' ');
-  }
-}
-
-/**
- * @extends {TableModel<Report>}
- */
-class ReportTableModel extends TableModel {
-  /**
-   * @param {!Element} showDebugReportsContainer
-   * @param {!Element} sendReportsButton
-   */
-  constructor(showDebugReportsContainer, sendReportsButton) {
-    super();
-
-    this.showDebugReportsCheckbox = queryRequiredElement(
-        'input[type="checkbox"]', showDebugReportsContainer);
-    this.hiddenDebugReportsSpan =
-        queryRequiredElement('span', showDebugReportsContainer);
-
-    this.sendReportsButton = sendReportsButton;
-
-    this.selectionColumn = new SelectionColumn(this);
-
-    this.emptyRowText = 'No sent or pending reports.';
-
-    /** @type {!Array<!Report>} */
-    this.sentOrDroppedReports = [];
-
-    /** @type {!Array<!Report>} */
-    this.storedReports = [];
-
-    /** @type {!Array<!Report>} */
-    this.debugReports = [];
-
-    this.showDebugReportsCheckbox.addEventListener(
-        'input', () => this.notifyRowsChanged());
-
-    this.sendReportsButton.addEventListener('click', () => this.sendReports());
-    this.selectionColumn.selectionChangedListeners.add((anySelected) => {
-      this.sendReportsButton.disabled = !anySelected;
-    });
-
-    this.rowsChangedListeners.add(() => this.updateHiddenDebugReportsSpan());
-  }
-
-  /** @override */
-  styleRow(tr, report) {
-    tr.classList.toggle(
-        'http-error',
-        report.httpResponseCode < 200 || report.httpResponseCode >= 400);
-  }
-
-  /** @override */
-  getRows() {
-    let rows = this.sentOrDroppedReports.concat(this.storedReports);
-    if (this.showDebugReportsCheckbox.checked) {
-      rows = rows.concat(this.debugReports);
-    }
-    return rows;
-  }
-
-  /** @param {!Array<!Report>} storedReports */
-  setStoredReports(storedReports) {
-    this.storedReports = storedReports;
-    this.notifyRowsChanged();
-  }
-
-  /** @param {!Report} report */
-  addSentOrDroppedReport(report) {
-    // Prevent the page from consuming ever more memory if the user leaves the
-    // page open for a long time.
-    if (this.sentOrDroppedReports.length + this.debugReports.length >= 1000) {
-      this.sentOrDroppedReports = [];
-      this.debugReports = [];
-    }
-
-    if (report.isDebug) {
-      this.debugReports.push(report);
-    } else {
-      this.sentOrDroppedReports.push(report);
-    }
-
-    this.notifyRowsChanged();
-  }
-
-  clear() {
-    this.storedReports = [];
-    this.sentOrDroppedReports = [];
-    this.debugReports = [];
-    this.notifyRowsChanged();
-  }
-
-  /** @private */
-  updateHiddenDebugReportsSpan() {
-    this.hiddenDebugReportsSpan.innerText =
-        this.showDebugReportsCheckbox.checked ?
-        '' :
-        ` (${this.debugReports.length} hidden)`;
-  }
-
-  /**
-   * Sends all selected reports.
-   * Disables the button while the reports are still being sent.
-   * Observer.onReportsChanged and Observer.onSourcesChanged will be called
-   * automatically as reports are deleted, so there's no need to manually refresh
-   * the data on completion.
-   * @private
-   */
-  sendReports() {
-    const ids = [];
-    this.storedReports.forEach((report) => {
-      if (!report.input.disabled && report.input.checked) {
-        ids.push(report.id);
-      }
-    });
-
-    if (ids.length === 0) {
-      return;
-    }
-
-    const previousText = this.sendReportsButton.innerText;
-
-    this.sendReportsButton.disabled = true;
-    this.sendReportsButton.innerText = 'Sending...';
-
-    pageHandler.sendReports(ids).then(() => {
-      this.sendReportsButton.innerText = previousText;
-    });
-  }
-}
-
-/** @extends {ReportTableModel} */
-class EventLevelReportTableModel extends ReportTableModel {
-  /**
-   * @param {!Element} showDebugReportsContainer
-   * @param {!Element} sendReportsButton
-   */
-  constructor(showDebugReportsContainer, sendReportsButton) {
-    super(showDebugReportsContainer, sendReportsButton);
-
-    this.cols = [
-      this.selectionColumn,
-      new CodeColumn('Report Body', (e) => e.reportBody),
-      new ValueColumn('Status', (e) => e.status),
-      new ReportUrlColumn(),
-      new DateColumn('Trigger Time', (e) => e.triggerTime),
-      new DateColumn('Report Time', (e) => e.reportTime),
-      new ValueColumn('Report Priority', (e) => e.reportPriority),
-      new ValueColumn(
-          'Randomized Report', (e) => e.attributedTruthfully ? 'no' : 'yes'),
-    ];
-
-    // Sort by report time by default.
-    this.sortIdx = 5;
-  }
-}
-
-/** @extends {ReportTableModel} */
-class AggregatableAttributionReportTableModel extends ReportTableModel {
-  /**
-   * @param {!Element} showDebugReportsContainer
-   * @param {!Element} sendReportsButton
-   */
-  constructor(showDebugReportsContainer, sendReportsButton) {
-    super(showDebugReportsContainer, sendReportsButton);
-
-    this.cols = [
-      this.selectionColumn,
-      new CodeColumn('Report Body', (e) => e.reportBody),
-      new ValueColumn('Status', (e) => e.status),
-      new ReportUrlColumn(),
-      new DateColumn('Trigger Time', (e) => e.triggerTime),
-      new DateColumn('Report Time', (e) => e.reportTime),
-      new CodeColumn('Histograms', (e) => e.contributions),
-    ];
-
-    // Sort by report time by default.
-    this.sortIdx = 5;
-  }
-}
-
-
-/**
- * Reference to the backend providing all the data.
- * @type {?AttributionInternalsHandlerRemote}
- */
-let pageHandler = null;
-
-
-/** @type {?SourceTableModel} */
-let sourceTableModel = null;
-
-/** @type {?TriggerTableModel} */
-let triggerTableModel = null;
-
-/** @type {?EventLevelReportTableModel} */
-let eventLevelReportTableModel = null;
-
-/** @type {?AggregatableAttributionReportTableModel} */
-let aggregatableAttributionReportTableModel = null;
-
-/**
- * Converts a mojo origin into a user-readable string, omitting default ports.
- * @param {Origin} origin Origin to convert
- * @return {string}
- */
-function OriginToText(origin) {
-  if (origin.host.length === 0) {
-    return 'Null';
-  }
-
-  let result = origin.scheme + '://' + origin.host;
-
-  if ((origin.scheme === 'https' && origin.port !== 443) ||
-      (origin.scheme === 'http' && origin.port !== 80)) {
-    result += ':' + origin.port;
-  }
-  return result;
-}
-
-/**
- * Converts a mojo SourceType into a user-readable string.
- * @param {SourceType} sourceType Source type to convert
- * @return {string}
- */
-function SourceTypeToText(sourceType) {
-  switch (sourceType) {
-    case SourceType.kNavigation:
-      return 'Navigation';
-    case SourceType.kEvent:
-      return 'Event';
-    default:
-      return sourceType.toString();
-  }
-}
-
-/**
- * Converts a mojo Attributability into a user-readable string.
- * @param {WebUISource_Attributability} attributability
- *     Attributability to convert
- * @return {string}
- */
-function AttributabilityToText(attributability) {
-  switch (attributability) {
-    case WebUISource_Attributability.kAttributable:
-      return 'Attributable';
-    case WebUISource_Attributability.kNoised:
-      return 'Unattributable: noised';
-    case WebUISource_Attributability.kReplacedByNewerSource:
-      return 'Unattributable: replaced by newer source';
-    case WebUISource_Attributability.kReachedEventLevelAttributionLimit:
-      return 'Attributable: reached event-level attribution limit';
-    case WebUISource_Attributability.kInternalError:
-      return 'Rejected: internal error';
-    case WebUISource_Attributability.kInsufficientSourceCapacity:
-      return 'Rejected: insufficient source capacity';
-    case WebUISource_Attributability
-        .kInsufficientUniqueDestinationCapacity:
-      return 'Rejected: insufficient unique destination capacity';
-    case WebUISource_Attributability.kExcessiveReportingOrigins:
-      return 'Rejected: excessive reporting origins';
-    case WebUISource_Attributability.kProhibitedByBrowserPolicy:
-      return 'Rejected: prohibited by browser policy';
-    default:
-      return attributability.toString();
-  }
-}
-
-/**
- * @param {WebUITrigger_Status} status
- * @return {string}
- */
-function TriggerStatusToText(status) {
-  switch (status) {
-    case WebUITrigger_Status.kSuccess:
-      return 'Success: Report stored';
-    case WebUITrigger_Status.kInternalError:
-      return 'Failure: Internal error';
-    case WebUITrigger_Status.kNoMatchingSources:
-      return 'Failure: No matching sources';
-    case WebUITrigger_Status.kNoMatchingSourceFilterData:
-      return 'Failure: No matching source filter data';
-    case WebUITrigger_Status.kNoReportCapacityForDestinationSite:
-      return 'Failure: No report capacity for destination site';
-    case WebUITrigger_Status.kExcessiveAttributions:
-      return 'Failure: Excessive attributions';
-    case WebUITrigger_Status.kExcessiveReportingOrigins:
-      return 'Failure: Excessive reporting origins';
-    case WebUITrigger_Status.kDeduplicated:
-      return 'Failure: Deduplicated against an earlier report';
-    case WebUITrigger_Status.kLowPriority:
-      return 'Failure: Priority too low';
-    case WebUITrigger_Status.kNoised:
-      return 'Failure: Noised';
-    case WebUITrigger_Status.kNoHistograms:
-      return 'Failure: No source histograms';
-    case WebUITrigger_Status.kInsufficientBudget:
-      return 'Failure: Insufficient budget';
-    case WebUITrigger_Status.kNotRegistered:
-      return 'Failure: No aggregatable data present';
-    case WebUITrigger_Status.kProhibitedByBrowserPolicy:
-      return 'Failure: Prohibited by browser policy';
-    default:
-      return status.toString();
-  }
-}
-
-/**
- * Fetch all sources, pending reports, and sent reports from the
- * backend and populate the tables. Also update measurement enabled status.
- */
-function updatePageData() {
-  // Get the feature status for Attribution Reporting and populate it.
-  pageHandler.isAttributionReportingEnabled().then((response) => {
-    $('feature-status-content').innerText =
-        response.enabled ? 'enabled' : 'disabled';
-    $('feature-status-content').classList.toggle('disabled', !response.enabled);
-
-    $('debug-mode-content').innerHTML =
-        getTrustedHTML`The #conversion-measurement-debug-mode flag is
- <strong>enabled</strong>, reports are sent immediately and never pending.`;
-
-    if (!response.debugMode) {
-      $('debug-mode-content').innerText = '';
-    }
-  });
-
-  updateSources();
-  updateReports(ReportType.kEventLevel);
-  updateReports(ReportType.kAggregatableAttribution);
-}
-
-function updateSources() {
-  pageHandler.getActiveSources().then((response) => {
-    sourceTableModel.setStoredSources(
-        response.sources.map((mojo) => new Source(mojo)));
-  });
-}
-
-/**
- * @param {!ReportType} reportType
- */
-function updateReports(reportType) {
-  pageHandler.getReports(reportType).then((response) => {
-    switch (reportType) {
-      case ReportType.kEventLevel:
-        eventLevelReportTableModel.setStoredReports(
-            response.reports
-                .filter((mojo) => mojo.data.eventLevelData !== undefined)
-                .map((mojo) => new EventLevelReport(mojo)));
-        break;
-      case ReportType.kAggregatableAttribution:
-        aggregatableAttributionReportTableModel.setStoredReports(
-            response.reports
-                .filter(
-                    (mojo) =>
-                        mojo.data.aggregatableAttributionData !== undefined)
-                .map((mojo) => new AggregatableAttributionReport(mojo)));
-        break;
-    }
-  });
-}
-
-/**
- * Deletes all data stored by the conversions backend.
- * Observer.onReportsChanged and Observer.onSourcesChanged will be called
- * automatically as reports are deleted, so there's no need to manually refresh
- * the data on completion.
- */
-function clearStorage() {
-  sourceTableModel.clear();
-  triggerTableModel.clear();
-  eventLevelReportTableModel.clear();
-  aggregatableAttributionReportTableModel.clear();
-  pageHandler.clearStorage();
-}
-
-/**
- * @param {!WebUIReport} mojo
- */
-function addSentOrDroppedReport(mojo) {
-  if (mojo.data.eventLevelData !== undefined) {
-    eventLevelReportTableModel.addSentOrDroppedReport(
-        new EventLevelReport(mojo));
-  } else {
-    aggregatableAttributionReportTableModel.addSentOrDroppedReport(
-        new AggregatableAttributionReport(mojo));
-  }
-}
-
-/** @implements {ObserverInterface} */
-class Observer {
-  /** @override */
-  onSourcesChanged() {
-    updateSources();
-  }
-
-  /** @override */
-  onReportsChanged(reportType) {
-    updateReports(reportType);
-  }
-
-  /** @override */
-  onSourceRejectedOrDeactivated(mojo) {
-    sourceTableModel.addUnstoredSource(new Source(mojo));
-  }
-
-  /** @override */
-  onReportSent(mojo) {
-    addSentOrDroppedReport(mojo);
-  }
-
-  /** @override */
-  onReportDropped(mojo) {
-    addSentOrDroppedReport(mojo);
-  }
-
-  /** @override */
-  onTriggerHandled(mojo) {
-    triggerTableModel.addTrigger(new Trigger(mojo));
-  }
-}
-
-/**
- * @param {!TableModel<?>} model
- * @param {!Element} tab
- */
-function installUnreadIndicator(model, tab) {
-  model.rowsChangedListeners.add(() => {
-    if (!tab.selected) {
-      tab.classList.add('unread');
-    }
-  });
-
-  tab.addEventListener('selectedChange', () => {
-    if (tab.selected) {
-      tab.classList.remove('unread');
-    }
-  });
-}
-
-document.addEventListener('DOMContentLoaded', function() {
-  // Setup the mojo interface.
-  pageHandler = AttributionInternalsHandler.getRemote();
-
-  sourceTableModel = new SourceTableModel();
-  triggerTableModel = new TriggerTableModel();
-  eventLevelReportTableModel = new EventLevelReportTableModel(
-      getRequiredElement('show-debug-event-reports'),
-      getRequiredElement('send-reports'));
-  aggregatableAttributionReportTableModel =
-      new AggregatableAttributionReportTableModel(
-          getRequiredElement('show-debug-aggregatable-reports'),
-          getRequiredElement('send-aggregatable-reports'));
-
-  installUnreadIndicator(sourceTableModel, getRequiredElement('sources-tab'));
-  installUnreadIndicator(triggerTableModel, getRequiredElement('triggers-tab'));
-  installUnreadIndicator(
-      eventLevelReportTableModel,
-      getRequiredElement('event-level-reports-tab'));
-  installUnreadIndicator(
-      aggregatableAttributionReportTableModel,
-      getRequiredElement('aggregatable-reports-tab'));
-
-  $('refresh').addEventListener('click', updatePageData);
-  $('clear-data').addEventListener('click', clearStorage);
-
-  Table.decorate(getRequiredElement('source-table-wrapper'), sourceTableModel);
-  Table.decorate(
-      getRequiredElement('trigger-table-wrapper'), triggerTableModel);
-  Table.decorate(
-      getRequiredElement('report-table-wrapper'), eventLevelReportTableModel);
-  Table.decorate(
-      getRequiredElement('aggregatable-report-table-wrapper'),
-      aggregatableAttributionReportTableModel);
-
-  decorate('tabbox', TabBox);
-
-  const receiver = new ObserverReceiver(new Observer());
-  pageHandler.addObserver(receiver.$.bindNewPipeAndPassRemote());
-
-  updatePageData();
-});
diff --git a/content/browser/resources/attribution_reporting/attribution_internals.ts b/content/browser/resources/attribution_reporting/attribution_internals.ts
new file mode 100644
index 0000000..de4bbbb
--- /dev/null
+++ b/content/browser/resources/attribution_reporting/attribution_internals.ts
@@ -0,0 +1,935 @@
+// 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.
+
+import 'chrome://resources/cr_elements/cr_tab_box/cr_tab_box.js';
+import './attribution_internals_table.js';
+
+import {assert} from 'chrome://resources/js/assert_ts.js';
+import {getTrustedHTML} from 'chrome://resources/js/static_types.js';
+import {Origin} from 'chrome://resources/mojo/url/mojom/origin.mojom-webui.js';
+
+import {Handler as AttributionInternalsHandler, HandlerRemote as AttributionInternalsHandlerRemote, ObserverInterface, ObserverReceiver, ReportID, ReportType, SourceType, WebUIReport, WebUISource, WebUISource_Attributability, WebUITrigger, WebUITrigger_Status} from './attribution_internals.mojom-webui.js';
+import {AttributionInternalsTableElement} from './attribution_internals_table.js';
+import {Column, TableModel} from './table_model.js';
+
+function compareDefault<T>(a: T, b: T): number {
+  if (a < b) {
+    return -1;
+  }
+  if (a > b) {
+    return 1;
+  }
+  return 0;
+}
+
+function bigintReplacer(_key: string, value: any): any {
+  return typeof value === 'bigint' ? value.toString() : value;
+}
+
+class ValueColumn<T, V> implements Column<T> {
+  compare: (a: T, b: T) => number;
+  header: string;
+  protected getValue: (param: T) => V;
+
+  constructor(
+      header: string, getValue: (param: T) => V,
+      compare?: ((a: T, b: T) => number)) {
+    this.header = header;
+    this.getValue = getValue;
+    if (compare) {
+      this.compare = compare;
+    } else {
+      this.compare = (a: T, b: T) => compareDefault(getValue(a), getValue(b));
+    }
+  }
+
+  render(td: HTMLElement, row: T) {
+    td.innerText = `${this.getValue(row)}`;
+  }
+
+  renderHeader(th: HTMLElement) {
+    th.innerText = `${this.header}`;
+  }
+}
+
+class DateColumn<T> extends ValueColumn<T, Date> {
+  constructor(header: string, getValue: (p: T) => Date) {
+    super(header, getValue);
+  }
+
+  override render(td: HTMLElement, row: T) {
+    td.innerText = this.getValue(row).toLocaleString();
+  }
+}
+
+class CodeColumn<T> extends ValueColumn<T, string> {
+  constructor(header: string, getValue: (p: T) => string) {
+    super(header, getValue);
+  }
+
+  override render(td: HTMLElement, row: T) {
+    const code = td.ownerDocument.createElement('code');
+    code.innerText = this.getValue(row);
+
+    const pre = td.ownerDocument.createElement('pre');
+    pre.appendChild(code);
+
+    td.appendChild(pre);
+  }
+}
+
+const debugPathPattern: RegExp =
+    /(?<=\/\.well-known\/attribution-reporting\/)debug(?=\/)/;
+
+class ReportUrlColumn extends ValueColumn<Report, string> {
+  constructor() {
+    super('Report URL', (e) => e.reportUrl);
+  }
+
+  override render(td: HTMLElement, row: Report) {
+    if (!row.isDebug) {
+      td.innerText = row.reportUrl;
+      return;
+    }
+
+    const [pre, post] = row.reportUrl.split(debugPathPattern, 2);
+    td.appendChild(new Text(pre));
+
+    const span = td.ownerDocument.createElement('span');
+    span.classList.add('debug-url');
+    span.innerText = 'debug';
+    td.appendChild(span);
+
+    td.appendChild(new Text(post));
+  }
+}
+
+class Selectable {
+  input: HTMLInputElement;
+
+  constructor() {
+    this.input = document.createElement('input');
+    this.input.type = 'checkbox';
+  }
+}
+
+class SelectionColumn<T extends Selectable> implements Column<T> {
+  compare: ((a: T, b: T) => number)|null;
+  model: TableModel<T>;
+  selectAll: HTMLInputElement;
+  listener: () => void;
+  selectionChangedListeners: Set<(param: boolean) => void>;
+
+  constructor(model: TableModel<T>) {
+    this.compare = null;
+    this.model = model;
+
+    this.selectAll = document.createElement('input');
+    this.selectAll.type = 'checkbox';
+    this.selectAll.addEventListener('input', () => {
+      const checked = this.selectAll.checked;
+      this.model.getRows().forEach((row) => {
+        if (!row.input.disabled) {
+          row.input.checked = checked;
+        }
+      });
+      this.notifySelectionChanged(checked);
+    });
+
+    this.listener = () => this.onChange();
+    this.model.rowsChangedListeners.add(this.listener);
+    this.selectionChangedListeners = new Set();
+  }
+
+  render(td: HTMLElement, row: T) {
+    td.appendChild(row.input);
+  }
+
+  renderHeader(th: HTMLElement) {
+    th.appendChild(this.selectAll);
+  }
+
+  onChange() {
+    let anySelectable = false;
+    let anySelected = false;
+    let anyUnselected = false;
+
+    this.model.getRows().forEach((row) => {
+      // addEventListener deduplicates, so only one event will be fired per
+      // input.
+      row.input.addEventListener('input', this.listener);
+
+      if (row.input.disabled) {
+        return;
+      }
+
+      anySelectable = true;
+
+      if (row.input.checked) {
+        anySelected = true;
+      } else {
+        anyUnselected = true;
+      }
+    });
+
+    this.selectAll.disabled = !anySelectable;
+    this.selectAll.checked = anySelected && !anyUnselected;
+    this.selectAll.indeterminate = anySelected && anyUnselected;
+
+    this.notifySelectionChanged(anySelected);
+  }
+
+  notifySelectionChanged(anySelected: boolean) {
+    this.selectionChangedListeners.forEach((f) => f(anySelected));
+  }
+}
+
+class Source {
+  sourceEventId: bigint;
+  impressionOrigin: string;
+  attributionDestination: string;
+  reportingOrigin: string;
+  impressionTime: Date;
+  expiryTime: Date;
+  sourceType: string;
+  filterData: string;
+  aggregatableSource: string;
+  debugKey: string;
+  dedupKeys: string;
+  priority: bigint;
+  status: string;
+
+  constructor(mojo: WebUISource) {
+    this.sourceEventId = mojo.sourceEventId;
+    this.impressionOrigin = originToText(mojo.impressionOrigin);
+    this.attributionDestination = mojo.attributionDestination;
+    this.reportingOrigin = originToText(mojo.reportingOrigin);
+    this.impressionTime = new Date(mojo.impressionTime);
+    this.expiryTime = new Date(mojo.expiryTime);
+    this.sourceType = sourceTypeToText(mojo.sourceType);
+    this.priority = mojo.priority;
+    this.filterData = JSON.stringify(mojo.filterData, null, ' ');
+    this.aggregatableSource =
+        JSON.stringify(mojo.aggregatableSource, bigintReplacer, ' ');
+    this.debugKey = mojo.debugKey ? mojo.debugKey.value.toString() : '';
+    this.dedupKeys = mojo.dedupKeys.join(', ');
+    this.status = attributabilityToText(mojo.attributability);
+  }
+}
+
+class SourceTableModel extends TableModel<Source> {
+  storedSources: Source[] = [];
+  unstoredSources: Source[] = [];
+
+  constructor() {
+    super();
+
+    this.cols = [
+      new ValueColumn<Source, bigint>(
+          'Source Event ID', (e) => e.sourceEventId),
+      new ValueColumn<Source, string>('Status', (e) => e.status),
+      new ValueColumn<Source, string>(
+          'Source Origin', (e) => e.impressionOrigin),
+      new ValueColumn<Source, string>(
+          'Destination', (e) => e.attributionDestination),
+      new ValueColumn<Source, string>('Report To', (e) => e.reportingOrigin),
+      new DateColumn<Source>(
+          'Source Registration Time', (e) => e.impressionTime),
+      new DateColumn<Source>('Expiry Time', (e) => e.expiryTime),
+      new ValueColumn<Source, string>('Source Type', (e) => e.sourceType),
+      new ValueColumn<Source, bigint>('Priority', (e) => e.priority),
+      new CodeColumn<Source>('Filter Data', (e) => e.filterData),
+      new CodeColumn<Source>(
+          'Aggregatable Source', (e) => e.aggregatableSource),
+      new ValueColumn<Source, string>('Debug Key', (e) => e.debugKey),
+      new ValueColumn<Source, string>('Dedup Keys', (e) => e.dedupKeys),
+    ];
+
+    this.emptyRowText = 'No sources.';
+
+    // Sort by source registration time by default.
+    this.sortIdx = 5;
+  }
+
+  override getRows() {
+    return this.unstoredSources.concat(this.storedSources);
+  }
+
+  setStoredSources(storedSources: Source[]) {
+    this.storedSources = storedSources;
+    this.notifyRowsChanged();
+  }
+
+  addUnstoredSource(source: Source) {
+    // Prevent the page from consuming ever more memory if the user leaves the
+    // page open for a long time.
+    if (this.unstoredSources.length >= 1000) {
+      this.unstoredSources = [];
+    }
+
+    this.unstoredSources.push(source);
+    this.notifyRowsChanged();
+  }
+
+  clear() {
+    this.storedSources = [];
+    this.unstoredSources = [];
+    this.notifyRowsChanged();
+  }
+}
+
+class Trigger {
+  triggerTime: Date;
+  destinationOrigin: string;
+  reportingOrigin: string;
+  filters: string;
+  debugKey: string;
+  eventTriggers: string;
+  eventLevelStatus: string;
+  aggregatableStatus: string;
+
+  constructor(mojo: WebUITrigger) {
+    this.triggerTime = new Date(mojo.triggerTime);
+    this.destinationOrigin = originToText(mojo.destinationOrigin);
+    this.reportingOrigin = originToText(mojo.reportingOrigin);
+    this.filters = JSON.stringify(mojo.filters, null, ' ');
+    this.debugKey = mojo.debugKey ? mojo.debugKey.value.toString() : '';
+
+    this.eventTriggers = JSON.stringify(
+        mojo.eventTriggers.map((e) => {
+          // Omit the dedup key, filters, and not filters if they are empty for
+          // brevity.
+          return {
+            'data': e.data,
+            'priority': e.priority,
+            'deduplication_key': e.dedupKey ? e.dedupKey.value : undefined,
+            'filters': Object.entries(e.filters).length > 0 ? e.filters :
+                                                              undefined,
+            'not_filters': Object.entries(e.notFilters).length > 0 ?
+                e.notFilters :
+                undefined,
+          };
+        }),
+        bigintReplacer, ' ');
+
+    this.eventLevelStatus = triggerStatusToText(mojo.eventLevelStatus);
+    this.aggregatableStatus = triggerStatusToText(mojo.aggregatableStatus);
+  }
+}
+
+class TriggerTableModel extends TableModel<Trigger> {
+  triggers: Trigger[] = [];
+
+  constructor() {
+    super();
+
+    this.cols = [
+      new DateColumn<Trigger>('Trigger Time', (e) => e.triggerTime),
+      new ValueColumn<Trigger, string>(
+          'Event-Level Status', (e) => e.eventLevelStatus),
+      new ValueColumn<Trigger, string>(
+          'Aggregatable Status', (e) => e.aggregatableStatus),
+      new ValueColumn<Trigger, string>(
+          'Destination', (e) => e.destinationOrigin),
+      new ValueColumn<Trigger, string>('Report To', (e) => e.reportingOrigin),
+      new ValueColumn<Trigger, string>('Debug Key', (e) => e.debugKey),
+      new CodeColumn<Trigger>('Filters', (e) => e.filters),
+      new CodeColumn<Trigger>('Event Triggers', (e) => e.eventTriggers),
+    ];
+
+    this.emptyRowText = 'No triggers.';
+
+    // Sort by trigger time by default.
+    this.sortIdx = 0;
+  }
+
+  override getRows() {
+    return this.triggers;
+  }
+
+  addTrigger(trigger: Trigger) {
+    // Prevent the page from consuming ever more memory if the user leaves the
+    // page open for a long time.
+    if (this.triggers.length >= 1000) {
+      this.triggers = [];
+    }
+
+    this.triggers.push(trigger);
+    this.notifyRowsChanged();
+  }
+
+  clear() {
+    this.triggers = [];
+    this.notifyRowsChanged();
+  }
+}
+
+class Report extends Selectable {
+  id: ReportID;
+  reportBody: string;
+  reportUrl: string;
+  triggerTime: Date;
+  reportTime: Date;
+  isDebug: boolean;
+  status: string;
+  httpResponseCode?: number;
+
+  constructor(mojo: WebUIReport) {
+    super();
+
+    this.id = mojo.id;
+    this.reportBody = mojo.reportBody;
+    this.reportUrl = mojo.reportUrl.url;
+    this.triggerTime = new Date(mojo.triggerTime);
+    this.reportTime = new Date(mojo.reportTime);
+
+    // Only pending reports are selectable.
+    if (mojo.status.pending === undefined) {
+      this.input.disabled = true;
+    }
+
+    this.isDebug = this.reportUrl.indexOf(
+                       '/.well-known/attribution-reporting/debug/') >= 0;
+
+    if (mojo.status.sent !== undefined) {
+      this.status = `Sent: HTTP ${mojo.status.sent}`;
+      this.httpResponseCode = mojo.status.sent;
+    } else if (mojo.status.pending !== undefined) {
+      this.status = 'Pending';
+    } else if (mojo.status.replacedByHigherPriorityReport !== undefined) {
+      this.status = `Replaced by higher-priority report: ${
+          mojo.status.replacedByHigherPriorityReport}`;
+    } else if (mojo.status.prohibitedByBrowserPolicy !== undefined) {
+      this.status = 'Prohibited by browser policy';
+    } else if (mojo.status.networkError !== undefined) {
+      this.status = `Network error: ${mojo.status.networkError}`;
+    } else if (mojo.status.failedToAssemble !== undefined) {
+      this.status = 'Dropped due to assembly failure';
+    } else {
+      throw new Error('invalid ReportStatus union');
+    }
+  }
+}
+
+class EventLevelReport extends Report {
+  reportPriority: bigint;
+  attributedTruthfully: boolean;
+
+  constructor(mojo: WebUIReport) {
+    super(mojo);
+
+    this.reportPriority = mojo.data.eventLevelData!.priority;
+    this.attributedTruthfully = mojo.data.eventLevelData!.attributedTruthfully;
+  }
+}
+
+class AggregatableAttributionReport extends Report {
+  contributions: string;
+
+  constructor(mojo: WebUIReport) {
+    super(mojo);
+
+    this.contributions = JSON.stringify(
+        mojo.data.aggregatableAttributionData!.contributions, bigintReplacer,
+        ' ');
+  }
+}
+
+class ReportTableModel extends TableModel<Report> {
+  showDebugReportsCheckbox: HTMLInputElement;
+  hiddenDebugReportsSpan: HTMLSpanElement;
+  sendReportsButton: HTMLButtonElement;
+  selectionColumn: SelectionColumn<Report>;
+  sentOrDroppedReports: Report[] = [];
+  storedReports: Report[] = [];
+  debugReports: Report[] = [];
+
+  constructor(
+      showDebugReportsContainer: HTMLElement,
+      sendReportsButton: HTMLButtonElement) {
+    super();
+
+    const showDebugReportsCheckbox =
+        showDebugReportsContainer.querySelector<HTMLInputElement>(
+            'input[type="checkbox"]');
+    assert(showDebugReportsCheckbox);
+    this.showDebugReportsCheckbox = showDebugReportsCheckbox;
+
+    const hiddenDebugReportsSpan =
+        showDebugReportsContainer.querySelector('span');
+    assert(hiddenDebugReportsSpan);
+    this.hiddenDebugReportsSpan = hiddenDebugReportsSpan;
+
+    this.sendReportsButton = sendReportsButton;
+
+    this.selectionColumn = new SelectionColumn(this);
+
+    this.emptyRowText = 'No sent or pending reports.';
+
+    this.showDebugReportsCheckbox.addEventListener(
+        'input', () => this.notifyRowsChanged());
+
+    this.sendReportsButton.addEventListener('click', () => this.sendReports_());
+    this.selectionColumn.selectionChangedListeners.add(
+        (anySelected: boolean) => {
+          this.sendReportsButton.disabled = !anySelected;
+        });
+
+    this.rowsChangedListeners.add(() => this.updateHiddenDebugReportsSpan_());
+  }
+
+  override styleRow(tr: HTMLElement, report: Report) {
+    tr.classList.toggle(
+        'http-error',
+        report.httpResponseCode !== undefined &&
+            (report.httpResponseCode < 200 || report.httpResponseCode >= 400));
+  }
+
+  override getRows() {
+    let rows = this.sentOrDroppedReports.concat(this.storedReports);
+    if (this.showDebugReportsCheckbox.checked) {
+      rows = rows.concat(this.debugReports);
+    }
+    return rows;
+  }
+
+  setStoredReports(storedReports: Report[]) {
+    this.storedReports = storedReports;
+    this.notifyRowsChanged();
+  }
+
+  addSentOrDroppedReport(report: Report) {
+    // Prevent the page from consuming ever more memory if the user leaves the
+    // page open for a long time.
+    if (this.sentOrDroppedReports.length + this.debugReports.length >= 1000) {
+      this.sentOrDroppedReports = [];
+      this.debugReports = [];
+    }
+
+    if (report.isDebug) {
+      this.debugReports.push(report);
+    } else {
+      this.sentOrDroppedReports.push(report);
+    }
+
+    this.notifyRowsChanged();
+  }
+
+  clear() {
+    this.storedReports = [];
+    this.sentOrDroppedReports = [];
+    this.debugReports = [];
+    this.notifyRowsChanged();
+  }
+
+  private updateHiddenDebugReportsSpan_() {
+    this.hiddenDebugReportsSpan.innerText =
+        this.showDebugReportsCheckbox.checked ?
+        '' :
+        ` (${this.debugReports.length} hidden)`;
+  }
+
+  /**
+   * Sends all selected reports.
+   * Disables the button while the reports are still being sent.
+   * Observer.onReportsChanged and Observer.onSourcesChanged will be called
+   * automatically as reports are deleted, so there's no need to manually
+   * refresh the data on completion.
+   */
+  private sendReports_() {
+    const ids: ReportID[] = [];
+    this.storedReports.forEach((report) => {
+      if (!report.input.disabled && report.input.checked) {
+        ids.push(report.id);
+      }
+    });
+
+    if (ids.length === 0) {
+      return;
+    }
+
+    const previousText = this.sendReportsButton.innerText;
+
+    this.sendReportsButton.disabled = true;
+    this.sendReportsButton.innerText = 'Sending...';
+
+    assert(pageHandler);
+    pageHandler.sendReports(ids).then(() => {
+      this.sendReportsButton.innerText = previousText;
+    });
+  }
+}
+
+class EventLevelReportTableModel extends ReportTableModel {
+  constructor(
+      showDebugReportsContainer: HTMLElement,
+      sendReportsButton: HTMLButtonElement) {
+    super(showDebugReportsContainer, sendReportsButton);
+
+    this.cols = [
+      this.selectionColumn,
+      new CodeColumn<Report>('Report Body', (e) => e.reportBody),
+      new ValueColumn<Report, string>('Status', (e) => e.status),
+      new ReportUrlColumn(),
+      new DateColumn<Report>('Trigger Time', (e) => e.triggerTime),
+      new DateColumn<Report>('Report Time', (e) => e.reportTime),
+      new ValueColumn<Report, bigint>(
+          'Report Priority', (e) => (e as EventLevelReport).reportPriority),
+      new ValueColumn<Report, string>(
+          'Randomized Report',
+          (e) => (e as EventLevelReport).attributedTruthfully ? 'no' : 'yes'),
+    ];
+
+    // Sort by report time by default.
+    this.sortIdx = 5;
+  }
+}
+
+class AggregatableAttributionReportTableModel extends ReportTableModel {
+  constructor(
+      showDebugReportsContainer: HTMLElement,
+      sendReportsButton: HTMLButtonElement) {
+    super(showDebugReportsContainer, sendReportsButton);
+
+    this.cols = [
+      this.selectionColumn,
+      new CodeColumn<Report>('Report Body', (e) => e.reportBody),
+      new ValueColumn<Report, string>('Status', (e) => e.status),
+      new ReportUrlColumn(),
+      new DateColumn<Report>('Trigger Time', (e) => e.triggerTime),
+      new DateColumn<Report>('Report Time', (e) => e.reportTime),
+      new CodeColumn<Report>(
+          'Histograms',
+          (e) => (e as AggregatableAttributionReport).contributions),
+    ];
+
+    // Sort by report time by default.
+    this.sortIdx = 5;
+  }
+}
+
+
+/**
+ * Reference to the backend providing all the data.
+ */
+let pageHandler: AttributionInternalsHandlerRemote|null = null;
+
+let sourceTableModel: SourceTableModel|null = null;
+
+let triggerTableModel: TriggerTableModel|null = null;
+
+let eventLevelReportTableModel: EventLevelReportTableModel|null = null;
+
+let aggregatableAttributionReportTableModel:
+    AggregatableAttributionReportTableModel|null = null;
+
+/**
+ * Converts a mojo origin into a user-readable string, omitting default ports.
+ * @param origin Origin to convert
+ */
+function originToText(origin: Origin): string {
+  if (origin.host.length === 0) {
+    return 'Null';
+  }
+
+  let result = origin.scheme + '://' + origin.host;
+
+  if ((origin.scheme === 'https' && origin.port !== 443) ||
+      (origin.scheme === 'http' && origin.port !== 80)) {
+    result += ':' + origin.port;
+  }
+  return result;
+}
+
+/**
+ * Converts a mojo SourceType into a user-readable string.
+ * @param sourceType Source type to convert
+ */
+function sourceTypeToText(sourceType: SourceType): string {
+  switch (sourceType) {
+    case SourceType.kNavigation:
+      return 'Navigation';
+    case SourceType.kEvent:
+      return 'Event';
+    default:
+      return sourceType.toString();
+  }
+}
+
+/**
+ * Converts a mojo Attributability into a user-readable string.
+ * @param attributability Attributability to convert
+ */
+function attributabilityToText(attributability: WebUISource_Attributability):
+    string {
+  switch (attributability) {
+    case WebUISource_Attributability.kAttributable:
+      return 'Attributable';
+    case WebUISource_Attributability.kNoised:
+      return 'Unattributable: noised';
+    case WebUISource_Attributability.kReplacedByNewerSource:
+      return 'Unattributable: replaced by newer source';
+    case WebUISource_Attributability.kReachedEventLevelAttributionLimit:
+      return 'Attributable: reached event-level attribution limit';
+    case WebUISource_Attributability.kInternalError:
+      return 'Rejected: internal error';
+    case WebUISource_Attributability.kInsufficientSourceCapacity:
+      return 'Rejected: insufficient source capacity';
+    case WebUISource_Attributability.kInsufficientUniqueDestinationCapacity:
+      return 'Rejected: insufficient unique destination capacity';
+    case WebUISource_Attributability.kExcessiveReportingOrigins:
+      return 'Rejected: excessive reporting origins';
+    case WebUISource_Attributability.kProhibitedByBrowserPolicy:
+      return 'Rejected: prohibited by browser policy';
+    default:
+      return attributability.toString();
+  }
+}
+
+function triggerStatusToText(status: WebUITrigger_Status): string {
+  switch (status) {
+    case WebUITrigger_Status.kSuccess:
+      return 'Success: Report stored';
+    case WebUITrigger_Status.kInternalError:
+      return 'Failure: Internal error';
+    case WebUITrigger_Status.kNoMatchingSources:
+      return 'Failure: No matching sources';
+    case WebUITrigger_Status.kNoMatchingSourceFilterData:
+      return 'Failure: No matching source filter data';
+    case WebUITrigger_Status.kNoReportCapacityForDestinationSite:
+      return 'Failure: No report capacity for destination site';
+    case WebUITrigger_Status.kExcessiveAttributions:
+      return 'Failure: Excessive attributions';
+    case WebUITrigger_Status.kExcessiveReportingOrigins:
+      return 'Failure: Excessive reporting origins';
+    case WebUITrigger_Status.kDeduplicated:
+      return 'Failure: Deduplicated against an earlier report';
+    case WebUITrigger_Status.kLowPriority:
+      return 'Failure: Priority too low';
+    case WebUITrigger_Status.kNoised:
+      return 'Failure: Noised';
+    case WebUITrigger_Status.kNoHistograms:
+      return 'Failure: No source histograms';
+    case WebUITrigger_Status.kInsufficientBudget:
+      return 'Failure: Insufficient budget';
+    case WebUITrigger_Status.kNotRegistered:
+      return 'Failure: No aggregatable data present';
+    case WebUITrigger_Status.kProhibitedByBrowserPolicy:
+      return 'Failure: Prohibited by browser policy';
+    default:
+      return status.toString();
+  }
+}
+
+/**
+ * Fetch all sources, pending reports, and sent reports from the
+ * backend and populate the tables. Also update measurement enabled status.
+ */
+function updatePageData() {
+  assert(pageHandler);
+  // Get the feature status for Attribution Reporting and populate it.
+  pageHandler.isAttributionReportingEnabled().then((response) => {
+    const featureStatusContent =
+        document.querySelector<HTMLElement>('#feature-status-content');
+    assert(featureStatusContent);
+    featureStatusContent.innerText = response.enabled ? 'enabled' : 'disabled';
+    featureStatusContent.classList.toggle('disabled', !response.enabled);
+
+    const debugModeContent =
+        document.querySelector<HTMLElement>('#debug-mode-content');
+    assert(debugModeContent);
+    const html = getTrustedHTML`The #conversion-measurement-debug-mode flag is
+ <strong>enabled</strong>, reports are sent immediately and never pending.`;
+    debugModeContent.innerHTML = html as unknown as string;
+
+    if (!response.debugMode) {
+      debugModeContent.innerText = '';
+    }
+  });
+
+  updateSources();
+  updateReports(ReportType.kEventLevel);
+  updateReports(ReportType.kAggregatableAttribution);
+}
+
+function updateSources() {
+  assert(pageHandler);
+  pageHandler.getActiveSources().then((response) => {
+    assert(sourceTableModel);
+    sourceTableModel.setStoredSources(
+        response.sources.map((mojo) => new Source(mojo)));
+  });
+}
+
+function updateReports(reportType: ReportType) {
+  assert(pageHandler);
+  pageHandler.getReports(reportType).then((response) => {
+    switch (reportType) {
+      case ReportType.kEventLevel:
+        assert(eventLevelReportTableModel);
+        eventLevelReportTableModel.setStoredReports(
+            response.reports
+                .filter((mojo) => mojo.data.eventLevelData !== undefined)
+                .map((mojo) => new EventLevelReport(mojo)));
+        break;
+      case ReportType.kAggregatableAttribution:
+        assert(aggregatableAttributionReportTableModel);
+        aggregatableAttributionReportTableModel.setStoredReports(
+            response.reports
+                .filter(
+                    (mojo) =>
+                        mojo.data.aggregatableAttributionData !== undefined)
+                .map((mojo) => new AggregatableAttributionReport(mojo)));
+        break;
+    }
+  });
+}
+
+/**
+ * Deletes all data stored by the conversions backend.
+ * Observer.onReportsChanged and Observer.onSourcesChanged will be called
+ * automatically as reports are deleted, so there's no need to manually refresh
+ * the data on completion.
+ */
+function clearStorage() {
+  assert(sourceTableModel);
+  sourceTableModel.clear();
+  assert(triggerTableModel);
+  triggerTableModel.clear();
+  assert(eventLevelReportTableModel);
+  eventLevelReportTableModel.clear();
+  assert(aggregatableAttributionReportTableModel);
+  aggregatableAttributionReportTableModel.clear();
+  assert(pageHandler);
+  pageHandler.clearStorage();
+}
+
+function addSentOrDroppedReport(mojo: WebUIReport) {
+  if (mojo.data.eventLevelData !== undefined) {
+    assert(eventLevelReportTableModel);
+    eventLevelReportTableModel.addSentOrDroppedReport(
+        new EventLevelReport(mojo));
+  } else {
+    assert(aggregatableAttributionReportTableModel);
+    aggregatableAttributionReportTableModel.addSentOrDroppedReport(
+        new AggregatableAttributionReport(mojo));
+  }
+}
+
+class Observer implements ObserverInterface {
+  onSourcesChanged() {
+    updateSources();
+  }
+
+  onReportsChanged(reportType: ReportType) {
+    updateReports(reportType);
+  }
+
+  onSourceRejectedOrDeactivated(mojo: WebUISource) {
+    assert(sourceTableModel);
+    sourceTableModel.addUnstoredSource(new Source(mojo));
+  }
+
+  onReportSent(mojo: WebUIReport) {
+    addSentOrDroppedReport(mojo);
+  }
+
+  onReportDropped(mojo: WebUIReport) {
+    addSentOrDroppedReport(mojo);
+  }
+
+  onTriggerHandled(mojo: WebUITrigger) {
+    assert(triggerTableModel);
+    triggerTableModel.addTrigger(new Trigger(mojo));
+  }
+}
+
+function installUnreadIndicator(model: TableModel<any>, tab: HTMLElement|null) {
+  assert(tab);
+
+  model.rowsChangedListeners.add(() => {
+    if (!tab.hasAttribute('selected')) {
+      tab.classList.add('unread');
+    }
+  });
+}
+
+document.addEventListener('DOMContentLoaded', function() {
+  // Setup the mojo interface.
+  pageHandler = AttributionInternalsHandler.getRemote();
+
+  sourceTableModel = new SourceTableModel();
+  triggerTableModel = new TriggerTableModel();
+  const showDebugReports =
+      document.querySelector<HTMLButtonElement>('#show-debug-event-reports');
+  assert(showDebugReports);
+  const sendReports =
+      document.querySelector<HTMLButtonElement>('#send-reports');
+  assert(sendReports);
+  eventLevelReportTableModel =
+      new EventLevelReportTableModel(showDebugReports, sendReports);
+  const showDebugAggregatableReports =
+      document.querySelector<HTMLElement>('#show-debug-aggregatable-reports');
+  assert(showDebugAggregatableReports);
+  const sendAggregatableReports =
+      document.querySelector<HTMLButtonElement>('#send-aggregatable-reports');
+  assert(sendAggregatableReports);
+  aggregatableAttributionReportTableModel =
+      new AggregatableAttributionReportTableModel(
+          showDebugAggregatableReports, sendAggregatableReports);
+
+  const tabBox = document.querySelector('cr-tab-box');
+  assert(tabBox);
+  tabBox.addEventListener('selected-index-change', e => {
+    const tabs = document.querySelectorAll<HTMLElement>('div[slot=\'tab\']');
+    tabs[(e as CustomEvent<number>).detail]!.classList.remove('unread');
+  });
+
+  installUnreadIndicator(
+      sourceTableModel, document.querySelector<HTMLElement>('#sources-tab'));
+  installUnreadIndicator(
+      triggerTableModel, document.querySelector<HTMLElement>('#triggers-tab'));
+  installUnreadIndicator(
+      eventLevelReportTableModel,
+      document.querySelector<HTMLElement>('#event-level-reports-tab'));
+  installUnreadIndicator(
+      aggregatableAttributionReportTableModel,
+      document.querySelector<HTMLElement>('#aggregatable-reports-tab'));
+
+  const refresh = document.querySelector('#refresh');
+  assert(refresh);
+  refresh.addEventListener('click', updatePageData);
+  const clearData = document.querySelector('#clear-data');
+  assert(clearData);
+  clearData.addEventListener('click', clearStorage);
+
+  const sourceTable =
+      document.querySelector<AttributionInternalsTableElement<Source>>(
+          '#sourceTable');
+  assert(sourceTable);
+  sourceTable.setModel(sourceTableModel!);
+  const triggerTable =
+      document.querySelector<AttributionInternalsTableElement<Trigger>>(
+          '#triggerTable');
+  assert(triggerTable);
+  triggerTable.setModel(triggerTableModel!);
+  const reportTable =
+      document.querySelector<AttributionInternalsTableElement<Report>>(
+          '#reportTable');
+  assert(reportTable);
+  reportTable.setModel(eventLevelReportTableModel!);
+  const aggregatableReportTable =
+      document.querySelector<AttributionInternalsTableElement<Report>>(
+          '#aggregatableReportTable');
+  assert(aggregatableReportTable);
+  aggregatableReportTable.setModel(aggregatableAttributionReportTableModel!);
+
+  tabBox.hidden = false;
+
+  const receiver = new ObserverReceiver(new Observer());
+  assert(pageHandler);
+  pageHandler.addObserver(receiver.$.bindNewPipeAndPassRemote());
+
+  updatePageData();
+});
diff --git a/content/browser/resources/attribution_reporting/attribution_internals_table.html b/content/browser/resources/attribution_reporting/attribution_internals_table.html
new file mode 100644
index 0000000..6bc06d29
--- /dev/null
+++ b/content/browser/resources/attribution_reporting/attribution_internals_table.html
@@ -0,0 +1,63 @@
+<style>
+  :host {
+    background-color: #fff;
+    border-color: rgba(0,0,0,.12);
+    border-radius: 4px;
+    border-style: solid;
+    border-width: 1px;
+    display: block;
+    overflow-x: scroll;
+  }
+
+  table {
+    border: 0;
+    border-collapse: collapse;
+  }
+
+  tbody tr {
+    border-top-color: rgba(0,0,0,.12);
+    border-top-style: solid;
+    border-top-width: 1px;
+  }
+
+  thead tr {
+    border: 0;
+  }
+
+  table td,
+  table th {
+    padding-inline-end: 16px;
+    padding-inline-start: 16px;
+  }
+
+  th[aria-sort] {
+    cursor: pointer;
+  }
+
+  th[aria-sort]::after {
+    content: '⬍';
+  }
+
+  th[aria-sort='ascending']::after {
+    content: '⬆';
+  }
+
+  th[aria-sort='descending']::after {
+    content: '⬇';
+  }
+
+  .http-error {
+    background-color: rgb(250,199,192);
+  }
+
+  .debug-url {
+    color: rgb(66,135,245);
+    font-weight: bold;
+  }
+</style>
+<table>
+  <thead>
+    <tr></tr>
+  </thead>
+  <tbody></tbody>
+</table>
diff --git a/content/browser/resources/attribution_reporting/attribution_internals_table.ts b/content/browser/resources/attribution_reporting/attribution_internals_table.ts
new file mode 100644
index 0000000..97a86cf
--- /dev/null
+++ b/content/browser/resources/attribution_reporting/attribution_internals_table.ts
@@ -0,0 +1,140 @@
+// Copyright 2022 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} from 'chrome://resources/js/assert_ts.js';
+import {CustomElement} from 'chrome://resources/js/custom_element.js';
+import {getTrustedHTML} from 'chrome://resources/js/static_types.js';
+
+import {TableModel} from './table_model.js';
+
+/**
+ * Helper function for setting sort attributes on |th|.
+ */
+function setSortAttrs(th: HTMLElement, sortDesc: boolean|null) {
+  let nextDir;
+  if (sortDesc === null) {
+    th.ariaSort = 'none';
+    nextDir = 'ascending';
+  } else if (sortDesc) {
+    th.ariaSort = 'descending';
+    nextDir = 'ascending';
+  } else {
+    th.ariaSort = 'ascending';
+    nextDir = 'descending';
+  }
+
+  th.title = `Sort by ${th.innerText} ${nextDir}`;
+  th.ariaLabel = th.title;
+}
+
+/**
+ * Table abstracts the logic for rendering and sorting a table. The table's
+ * columns are supplied by a TableModel supplied to the decorate function. Each
+ * Column knows how to render the underlying value of the row type T, and
+ * optionally sort rows of type T by that value.
+ */
+export class AttributionInternalsTableElement<T> extends CustomElement {
+  static override get template() {
+    return getTrustedHTML`{__html_template__}`;
+  }
+
+  private model_: TableModel<T>|null = null;
+  private sortDesc_: boolean = false;
+
+  setModel(model: TableModel<T>) {
+    this.model_ = model;
+    this.sortDesc_ = false;
+
+    const tr = this.$<HTMLElement>('tr');
+    assert(tr);
+    model.cols.forEach((col, idx) => {
+      const th = document.createElement('th');
+      th.scope = 'col';
+      col.renderHeader(th);
+
+      if (col.compare) {
+        th.setAttribute('role', 'button');
+        setSortAttrs(th, /*sortDesc=*/ null);
+        th.addEventListener('click', () => this.changeSortHeader_(idx));
+      }
+
+      tr.appendChild(th);
+    });
+
+    this.addSpanningText_();
+    this.model_.rowsChangedListeners.add(() => this.updateTbody());
+  }
+
+  private addSpanningText_() {
+    const td = document.createElement('td');
+    assert(this.model_);
+    td.textContent = this.model_.emptyRowText;
+    td.colSpan = this.model_.cols.length;
+    const tr = document.createElement('tr');
+    tr.appendChild(td);
+    const tbody = this.$<HTMLElement>('tbody');
+    assert(tbody);
+    tbody.appendChild(tr);
+  }
+
+  private changeSortHeader_(idx: number) {
+    const ths = this.$all<HTMLElement>('thead th');
+
+    assert(this.model_);
+    if (idx === this.model_.sortIdx) {
+      this.sortDesc_ = !this.sortDesc_;
+    } else {
+      this.sortDesc_ = false;
+      if (this.model_.sortIdx >= 0) {
+        setSortAttrs(ths[this.model_.sortIdx]!, /*descending=*/ null);
+      }
+    }
+
+    this.model_.sortIdx = idx;
+    setSortAttrs(ths[this.model_.sortIdx]!, this.sortDesc_);
+    this.updateTbody();
+  }
+
+  private sort_(rows: T[]) {
+    assert(this.model_);
+    if (this.model_.sortIdx < 0) {
+      return;
+    }
+
+    const multiplier = this.sortDesc_ ? -1 : 1;
+    rows.sort(
+        (a, b) => this.model_!.cols[this.model_!.sortIdx]!.compare!(a, b) *
+            multiplier);
+  }
+
+  updateTbody() {
+    const tbody = this.$<HTMLElement>('tbody');
+    assert(tbody);
+    tbody.innerText = '';
+
+    assert(this.model_);
+    const rows = this.model_.getRows();
+    if (rows.length === 0) {
+      this.addSpanningText_();
+      return;
+    }
+
+    this.sort_(rows);
+
+    rows.forEach((row) => {
+      const tr = document.createElement('tr');
+      assert(this.model_);
+      this.model_.cols.forEach((col) => {
+        const td = document.createElement('td');
+        col.render(td, row);
+        tr.appendChild(td);
+      });
+      this.model_.styleRow(tr, row);
+      tbody.appendChild(tr);
+    });
+  }
+}
+
+customElements.define(
+    'attribution-internals-table', AttributionInternalsTableElement);
diff --git a/content/browser/resources/attribution_reporting/table_model.ts b/content/browser/resources/attribution_reporting/table_model.ts
new file mode 100644
index 0000000..d503ac3
--- /dev/null
+++ b/content/browser/resources/attribution_reporting/table_model.ts
@@ -0,0 +1,32 @@
+// Copyright 2022 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.
+
+export interface Column<T> {
+  compare: ((a: T, b: T) => number)|null;
+
+  render(td: HTMLElement, row: T): void;
+
+  renderHeader(th: HTMLElement): void;
+}
+
+export class TableModel<T> {
+  cols: Column<T>[] = [];
+  emptyRowText: string = '';
+  sortIdx: number = -1;
+  rowsChangedListeners: Set<() => void>;
+
+  constructor() {
+    this.rowsChangedListeners = new Set();
+  }
+
+  styleRow(_tr: Element, _data: T) {}
+
+  getRows(): T[] {
+    return [];
+  }
+
+  notifyRowsChanged() {
+    this.rowsChangedListeners.forEach(f => f());
+  }
+}
diff --git a/content/browser/resources/attribution_reporting/tsconfig_base.json b/content/browser/resources/attribution_reporting/tsconfig_base.json
new file mode 100644
index 0000000..99a81eca
--- /dev/null
+++ b/content/browser/resources/attribution_reporting/tsconfig_base.json
@@ -0,0 +1,6 @@
+{
+  "extends": "../../../../tools/typescript/tsconfig_base.json",
+  "compilerOptions": {
+    "allowJs": true
+  }
+}
diff --git a/content/browser/ssl/ssl_manager.cc b/content/browser/ssl/ssl_manager.cc
index 13f3bf63..a726ed7 100644
--- a/content/browser/ssl/ssl_manager.cc
+++ b/content/browser/ssl/ssl_manager.cc
@@ -357,16 +357,20 @@
   OnCertErrorInternal(std::move(handler));
 }
 
-void SSLManager::DidStartResourceResponse(const GURL& url,
-                                          bool has_certificate_errors) {
-  if (!url.SchemeIsCryptographic() || has_certificate_errors)
+void SSLManager::DidStartResourceResponse(
+    const url::SchemeHostPort& final_response_url,
+    bool has_certificate_errors) {
+  const std::string& scheme = final_response_url.scheme();
+  const std::string& host = final_response_url.host();
+
+  if (!GURL::SchemeIsCryptographic(scheme) || has_certificate_errors)
     return;
 
   // If the scheme is https: or wss and the cert did not have any errors, revoke
   // any previous decisions that have occurred.
   if (!ssl_host_state_delegate_ ||
       !ssl_host_state_delegate_->HasAllowException(
-          url.host(), controller_->DeprecatedGetWebContents())) {
+          host, controller_->DeprecatedGetWebContents())) {
     return;
   }
 
@@ -374,7 +378,7 @@
   // clear out any exceptions that were made by the user for bad
   // certificates. This intentionally does not apply to cached resources
   // (see https://crbug.com/634553 for an explanation).
-  ssl_host_state_delegate_->RevokeUserAllowExceptions(url.host());
+  ssl_host_state_delegate_->RevokeUserAllowExceptions(host);
 }
 
 void SSLManager::OnCertErrorInternal(std::unique_ptr<SSLErrorHandler> handler) {
diff --git a/content/browser/ssl/ssl_manager.h b/content/browser/ssl/ssl_manager.h
index f9ada2c..4ee3e8b 100644
--- a/content/browser/ssl/ssl_manager.h
+++ b/content/browser/ssl/ssl_manager.h
@@ -15,6 +15,7 @@
 #include "net/base/net_errors.h"
 #include "net/cert/cert_status_flags.h"
 #include "url/gurl.h"
+#include "url/scheme_host_port.h"
 
 namespace net {
 class SSLInfo;
@@ -67,7 +68,8 @@
   NavigationControllerImpl* controller() { return controller_; }
 
   void DidCommitProvisionalLoad(const LoadCommittedDetails& details);
-  void DidStartResourceResponse(const GURL& url, bool has_certificate_errors);
+  void DidStartResourceResponse(const url::SchemeHostPort& final_response_url,
+                                bool has_certificate_errors);
 
   // The following methods are called when a page includes insecure
   // content. These methods update the SSLStatus on the NavigationEntry
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index 5443604..d649967 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -2843,6 +2843,15 @@
       network_context_client_receiver_.BindNewPipeAndPassRemote());
   network_context_.set_disconnect_handler(base::BindOnce(
       &StoragePartitionImpl::InitNetworkContext, weak_factory_.GetWeakPtr()));
+
+  if (base::FeatureList::IsEnabled(features::kPreloadCookies)) {
+    mojo::Remote<::network::mojom::CookieManager> cookie_manager;
+    mojo::PendingRemote<::network::mojom::CookieManager> cookie_manager_remote;
+    network_context_->GetCookieManager(
+        cookie_manager_remote.InitWithNewPipeAndPassReceiver());
+    cookie_manager.Bind(std::move(cookie_manager_remote));
+    cookie_manager->GetAllCookies(base::NullCallback());
+  }
 }
 
 network::mojom::URLLoaderFactory*
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index 51cdba5..b94e87b6 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -5568,7 +5568,7 @@
         ->controller()
         .ssl_manager()
         ->DidStartResourceResponse(
-            navigation_handle->GetURL(),
+            url::SchemeHostPort(navigation_handle->GetURL()),
             navigation_handle->GetSSLInfo().has_value()
                 ? net::IsCertStatusError(
                       navigation_handle->GetSSLInfo()->cert_status)
diff --git a/content/browser/web_package/signed_exchange_request_handler_browsertest.cc b/content/browser/web_package/signed_exchange_request_handler_browsertest.cc
index fe6c0004..c2b51b2439 100644
--- a/content/browser/web_package/signed_exchange_request_handler_browsertest.cc
+++ b/content/browser/web_package/signed_exchange_request_handler_browsertest.cc
@@ -1839,6 +1839,10 @@
 class SignedExchangePKPBrowserTest
     : public SignedExchangeRequestHandlerBrowserTest {
  public:
+  SignedExchangePKPBrowserTest() {
+    scoped_feature_list_.InitAndEnableFeature(
+        net::features::kStaticKeyPinningEnforcement);
+  }
   void SetUpOnMainThread() override {
     SignedExchangeRequestHandlerBrowserTest::SetUpOnMainThread();
 
@@ -1918,6 +1922,8 @@
   // Only used when NetworkService is disabled. Accessed on IO thread.
   std::unique_ptr<net::ScopedTransportSecurityStateSource>
       transport_security_state_source_;
+
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_P(SignedExchangePKPBrowserTest, PKPViolation) {
diff --git a/content/browser/webid/federated_auth_request_impl.cc b/content/browser/webid/federated_auth_request_impl.cc
index bdd0b503..02f2e9ab 100644
--- a/content/browser/webid/federated_auth_request_impl.cc
+++ b/content/browser/webid/federated_auth_request_impl.cc
@@ -757,9 +757,7 @@
     IdentityProviderMetadata idp_metadata) {
   bool is_token_valid = IsEndpointUrlValid(endpoints_.token);
   bool is_accounts_valid = IsEndpointUrlValid(endpoints_.accounts);
-  bool is_client_metadata_valid =
-      IsEndpointUrlValid(endpoints_.client_metadata);
-  if (!is_token_valid || !is_accounts_valid || !is_client_metadata_valid) {
+  if (!is_token_valid || !is_accounts_valid) {
     std::string message =
         "Manifest is missing or has an invalid URL for the following "
         "endpoints:\n";
@@ -769,9 +767,6 @@
     if (!is_accounts_valid) {
       message += "\"accounts_endpoint\"\n";
     }
-    if (!is_client_metadata_valid) {
-      message += "\"client_metadata_endpoint\"\n";
-    }
     render_frame_host_->AddMessageToConsole(
         blink::mojom::ConsoleMessageLevel::kError, message);
     RecordRequestIdTokenStatus(IdTokenStatus::kManifestInvalidResponse,
@@ -910,71 +905,31 @@
     idp_metadata.brand_icon = bitmaps[0];
   }
 
-  network_manager_->FetchClientMetadata(
-      endpoints_.client_metadata, client_id_,
-      base::BindOnce(
-          &FederatedAuthRequestImpl::OnClientMetadataResponseReceived,
-          weak_ptr_factory_.GetWeakPtr(), std::move(idp_metadata)));
+  if (IsEndpointUrlValid(endpoints_.client_metadata)) {
+    network_manager_->FetchClientMetadata(
+        endpoints_.client_metadata, client_id_,
+        base::BindOnce(
+            &FederatedAuthRequestImpl::OnClientMetadataResponseReceived,
+            weak_ptr_factory_.GetWeakPtr(), std::move(idp_metadata)));
+  } else {
+    network_manager_->SendAccountsRequest(
+        endpoints_.accounts, client_id_,
+        base::BindOnce(&FederatedAuthRequestImpl::OnAccountsResponseReceived,
+                       weak_ptr_factory_.GetWeakPtr(), idp_metadata));
+  }
 }
 
 void FederatedAuthRequestImpl::OnClientMetadataResponseReceived(
     IdentityProviderMetadata idp_metadata,
     IdpNetworkRequestManager::FetchStatus status,
     IdpNetworkRequestManager::ClientMetadata data) {
-  switch (status) {
-    case IdpNetworkRequestManager::FetchStatus::kHttpNotFoundError: {
-      RecordRequestIdTokenStatus(IdTokenStatus::kClientMetadataHttpNotFound,
-                                 render_frame_host_->GetPageUkmSourceId());
-      CompleteRequest(
-          FederatedAuthRequestResult::kErrorFetchingClientMetadataHttpNotFound,
-          "",
-          /*should_call_callback=*/false);
-      return;
-    }
-    case IdpNetworkRequestManager::FetchStatus::kNoResponseError: {
-      RecordRequestIdTokenStatus(IdTokenStatus::kClientMetadataNoResponse,
-                                 render_frame_host_->GetPageUkmSourceId());
-      CompleteRequest(
-          FederatedAuthRequestResult::kErrorFetchingClientMetadataNoResponse,
-          "",
-          /*should_call_callback=*/false);
-      return;
-    }
-    case IdpNetworkRequestManager::FetchStatus::kInvalidResponseError: {
-      RecordRequestIdTokenStatus(IdTokenStatus::kClientMetadataInvalidResponse,
-                                 render_frame_host_->GetPageUkmSourceId());
-      CompleteRequest(FederatedAuthRequestResult::
-                          kErrorFetchingClientMetadataInvalidResponse,
-                      "",
-                      /*should_call_callback=*/false);
-      return;
-    }
-    case IdpNetworkRequestManager::FetchStatus::kSuccess: {
-      // Since the |privacy_policy_url| is required, consider the result an
-      // invalid response in the case where the parser returns an empty value
-      // for it or an invalid url.
-      GURL pp_url(data.privacy_policy_url);
-      if (!pp_url.is_valid()) {
-        RecordRequestIdTokenStatus(
-            IdTokenStatus::kClientMetadataMissingPrivacyPolicyUrl,
-            render_frame_host_->GetPageUkmSourceId());
-        CompleteRequest(FederatedAuthRequestResult::
-                            kErrorClientMetadataMissingPrivacyPolicyUrl,
-                        "", /*should_call_callback=*/false);
-        return;
-      }
-      client_metadata_ = data;
-
-      network_manager_->SendAccountsRequest(
-          endpoints_.accounts, client_id_,
-          base::BindOnce(&FederatedAuthRequestImpl::OnAccountsResponseReceived,
-                         weak_ptr_factory_.GetWeakPtr(), idp_metadata));
-      return;
-    }
-    case IdpNetworkRequestManager::FetchStatus::kInvalidRequestError: {
-      NOTREACHED();
-    }
-  }
+  // TODO(yigu): Clean up the client metadata related errors for metrics and
+  // console logs.
+  client_metadata_ = data;
+  network_manager_->SendAccountsRequest(
+      endpoints_.accounts, client_id_,
+      base::BindOnce(&FederatedAuthRequestImpl::OnAccountsResponseReceived,
+                     weak_ptr_factory_.GetWeakPtr(), idp_metadata));
 }
 
 void FederatedAuthRequestImpl::DownloadBitmap(
diff --git a/content/browser/webid/federated_auth_request_impl_unittest.cc b/content/browser/webid/federated_auth_request_impl_unittest.cc
index e863a07..b9d5418 100644
--- a/content/browser/webid/federated_auth_request_impl_unittest.cc
+++ b/content/browser/webid/federated_auth_request_impl_unittest.cc
@@ -191,11 +191,6 @@
     RequestIdTokenStatus::kSuccess, FederatedAuthRequestResult::kSuccess,
     FETCH_ENDPOINT_ALL_REQUEST_ID_TOKEN};
 
-MockClientIdConfiguration BuildClientMetadataErrorResponse(
-    FetchStatus fetch_status) {
-  return {fetch_status, "", ""};
-}
-
 // Helper class for receiving the mojo method callback.
 class AuthRequestCallbackHelper {
  public:
@@ -1045,25 +1040,14 @@
   EXPECT_EQ("Provider's FedCM manifest configuration is invalid.", messages[1]);
 }
 
-// Test that request fails if manifest is missing client metadata endpoint.
+// Test that client metadata endpoint is not required in manifest.
 TEST_F(BasicFederatedAuthRequestImplTest, MissingClientMetadataEndpoint) {
   MockConfiguration configuration = kConfigurationValid;
   configuration.manifest.client_metadata_endpoint = "";
   RequestExpectations expectations = {
-      RequestIdTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorFetchingManifestInvalidResponse,
-      FetchedEndpoint::MANIFEST | FetchedEndpoint::MANIFEST_LIST};
+      RequestIdTokenStatus::kSuccess, FederatedAuthRequestResult::kSuccess,
+      FETCH_ENDPOINT_ALL_REQUEST_ID_TOKEN & ~FetchedEndpoint::CLIENT_METADATA};
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
-
-  std::vector<std::string> messages =
-      RenderFrameHostTester::For(main_rfh())->GetConsoleMessages();
-  ASSERT_EQ(2U, messages.size());
-  EXPECT_EQ(
-      "Manifest is missing or has an invalid URL for the following "
-      "endpoints:\n"
-      "\"client_metadata_endpoint\"\n",
-      messages[0]);
-  EXPECT_EQ("Provider's FedCM manifest configuration is invalid.", messages[1]);
 }
 
 // Test that request fails if the accounts endpoint is in a different origin
@@ -1102,58 +1086,31 @@
   RunAuthTest(kDefaultRequestParameters, expectations, configuration);
 }
 
-// Test that request fails if client metadata cannot be found.
-TEST_F(BasicFederatedAuthRequestImplTest, ClientMetadataNotFound) {
-  MockConfiguration configuration = kConfigurationValid;
-  configuration.client_metadata =
-      BuildClientMetadataErrorResponse(FetchStatus::kHttpNotFoundError);
-  RequestExpectations expectations = {
-      RequestIdTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorFetchingClientMetadataHttpNotFound,
-      FetchedEndpoint::MANIFEST | FetchedEndpoint::CLIENT_METADATA |
-          FetchedEndpoint::MANIFEST_LIST};
-  RunAuthTest(kDefaultRequestParameters, expectations, configuration);
-}
-
-// Test that request fails if client metadata endpoint returns empty response.
-TEST_F(BasicFederatedAuthRequestImplTest, ClientMetadataEmptyResponse) {
-  MockConfiguration configuration = kConfigurationValid;
-  configuration.client_metadata =
-      BuildClientMetadataErrorResponse(FetchStatus::kNoResponseError);
-  RequestExpectations expectations = {
-      RequestIdTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorFetchingClientMetadataNoResponse,
-      FetchedEndpoint::MANIFEST | FetchedEndpoint::CLIENT_METADATA |
-          FetchedEndpoint::MANIFEST_LIST};
-  RunAuthTest(kDefaultRequestParameters, expectations, configuration);
-}
-
-// Test that request fails if client metadata returns invalid response.
-TEST_F(BasicFederatedAuthRequestImplTest, ClientMetadataInvalidResponse) {
-  MockConfiguration configuration = kConfigurationValid;
-  configuration.client_metadata =
-      BuildClientMetadataErrorResponse(FetchStatus::kInvalidResponseError);
-  RequestExpectations expectations = {
-      RequestIdTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorFetchingClientMetadataInvalidResponse,
-      FetchedEndpoint::MANIFEST | FetchedEndpoint::CLIENT_METADATA |
-          FetchedEndpoint::MANIFEST_LIST};
-  RunAuthTest(kDefaultRequestParameters, expectations, configuration);
-}
-
-// Test that request fails if client metadata does not contain a privacy policy
-// URL.
-TEST_F(BasicFederatedAuthRequestImplTest, ClientMetadataNoPrivacyUrl) {
+// Test that privacy policy URL or terms of service is not required in client
+// metadata.
+TEST_F(BasicFederatedAuthRequestImplTest,
+       ClientMetadataNoPrivacyPolicyOrTermsOfServiceUrl) {
   MockConfiguration configuration = kConfigurationValid;
   configuration.client_metadata = kDefaultClientMetadata;
   configuration.client_metadata.privacy_policy_url = "";
   configuration.client_metadata.terms_of_service_url = "";
-  RequestExpectations expectations = {
-      RequestIdTokenStatus::kError,
-      FederatedAuthRequestResult::kErrorClientMetadataMissingPrivacyPolicyUrl,
-      FetchedEndpoint::MANIFEST | FetchedEndpoint::CLIENT_METADATA |
-          FetchedEndpoint::MANIFEST_LIST};
-  RunAuthTest(kDefaultRequestParameters, expectations, configuration);
+  RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
+}
+
+// Test that privacy policy URL is not required in client metadata.
+TEST_F(BasicFederatedAuthRequestImplTest, ClientMetadataNoPrivacyPolicyUrl) {
+  MockConfiguration configuration = kConfigurationValid;
+  configuration.client_metadata = kDefaultClientMetadata;
+  configuration.client_metadata.privacy_policy_url = "";
+  RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
+}
+
+// Test that terms of service URL is not required in client metadata.
+TEST_F(BasicFederatedAuthRequestImplTest, ClientMetadataNoTermsOfServiceUrl) {
+  MockConfiguration configuration = kConfigurationValid;
+  configuration.client_metadata = kDefaultClientMetadata;
+  configuration.client_metadata.terms_of_service_url = "";
+  RunAuthTest(kDefaultRequestParameters, kExpectationSuccess, configuration);
 }
 
 // Test that request fails if all of the endpoints in the manifest are invalid.
@@ -1162,8 +1119,6 @@
   MockConfiguration configuration = kConfigurationValid;
   configuration.manifest.accounts_endpoint = "https://cross-origin-1.com";
   configuration.manifest.token_endpoint = "";
-  configuration.manifest.client_metadata_endpoint =
-      "https://cross-origin-2.com";
   RequestExpectations expectations = {
       RequestIdTokenStatus::kError,
       FederatedAuthRequestResult::kErrorFetchingManifestInvalidResponse,
@@ -1176,8 +1131,7 @@
       "Manifest is missing or has an invalid URL for the following "
       "endpoints:\n"
       "\"id_token_endpoint\"\n"
-      "\"accounts_endpoint\"\n"
-      "\"client_metadata_endpoint\"\n",
+      "\"accounts_endpoint\"\n",
       messages[0]);
   EXPECT_EQ("Provider's FedCM manifest configuration is invalid.", messages[1]);
 }
diff --git a/content/common/frame.mojom b/content/common/frame.mojom
index a7f3652..5aca51e 100644
--- a/content/common/frame.mojom
+++ b/content/common/frame.mojom
@@ -58,7 +58,7 @@
 import "ui/accessibility/mojom/ax_tree_update.mojom";
 import "ui/base/mojom/window_open_disposition.mojom";
 import "ui/gfx/geometry/mojom/geometry.mojom";
-import "url/mojom/origin.mojom";
+import "url/mojom/scheme_host_port.mojom";
 import "url/mojom/url.mojom";
 
 [Native]
@@ -780,9 +780,17 @@
           initiator_policy_container_keep_alive_handle);
 
   // Sent when a subresource response has started.
+  //
+  // |final_response_url| is the final (after all redirects) URL of the
+  // subresource response.  The full URL is not available, because in some
+  // cases the path and query may be sanitized away
+  // - see https://crbug.com/973885.
+  //
   // |cert_status| is the bitmask of status info of the SSL certificate. (see
   // net/cert/cert_status_flags.h).
-  SubresourceResponseStarted(url.mojom.Url url, uint32 cert_status);
+  SubresourceResponseStarted(
+      url.mojom.SchemeHostPort final_response_url,
+      uint32 cert_status);
 
   // Sent when a resource load finished, successfully or not.
   ResourceLoadComplete(blink.mojom.ResourceLoadInfo url_load_info);
diff --git a/content/dev_ui_content_resources.grd b/content/dev_ui_content_resources.grd
index eca376b..601d735 100644
--- a/content/dev_ui_content_resources.grd
+++ b/content/dev_ui_content_resources.grd
@@ -15,8 +15,10 @@
   <translations />
   <release seq="1">
     <includes>
-      <include name="IDR_ATTRIBUTION_INTERNALS_HTML" file="browser/resources/attribution_reporting/attribution_internals.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
-      <include name="IDR_ATTRIBUTION_INTERNALS_JS" file="browser/resources/attribution_reporting/attribution_internals.js" type="BINDATA" />
+      <include name="IDR_ATTRIBUTION_INTERNALS_HTML" file="browser/resources/attribution_reporting/attribution_internals.html" type="BINDATA" />
+      <include name="IDR_ATTRIBUTION_INTERNALS_JS" file="${root_gen_dir}/content/browser/resources/attribution_reporting/tsc/attribution_internals.js" use_base_dir="false" type="BINDATA" />
+      <include name="IDR_ATTRIBUTION_INTERNALS_TABLE_MODEL_JS" file="${root_gen_dir}/content/browser/resources/attribution_reporting/tsc/table_model.js" use_base_dir="false" type="BINDATA" />
+      <include name="IDR_ATTRIBUTION_INTERNALS_TABLE_JS" file="${root_gen_dir}/content/browser/resources/attribution_reporting/tsc/attribution_internals_table.js" use_base_dir="false" type="BINDATA" />
       <include name="IDR_ATTRIBUTION_INTERNALS_CSS" file="browser/resources/attribution_reporting/attribution_internals.css" type="BINDATA" />
       <include name="IDR_ATTRIBUTION_INTERNALS_MOJOM_JS" file="${root_gen_dir}/mojom-webui/content/browser/attribution_reporting/attribution_internals.mojom-webui.js" use_base_dir="false" type="BINDATA" />
       <include name="IDR_GPU_BROWSER_BRIDGE_JS" file="browser/resources/gpu/browser_bridge.js" type="BINDATA" />
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 34407a77..6a36476 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -662,6 +662,10 @@
 const base::Feature kHighPriorityBeforeUnload{
     "HighPriorityBeforeUnload", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Preload cookie database on NetworkContext creation.
+const base::Feature kPreloadCookies{"PreloadCookies",
+                                    base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables exposure of ads APIs in the renderer: Attribution Reporting,
 // FLEDGE, Topics.
 const base::Feature kPrivacySandboxAdsAPIsOverride{
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index f4d4b3f..6cb452a 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -165,6 +165,7 @@
 CONTENT_EXPORT extern const base::Feature kPepperCrossOriginRedirectRestriction;
 CONTENT_EXPORT extern const base::Feature kPictureInPictureV2;
 CONTENT_EXPORT extern const base::Feature kHighPriorityBeforeUnload;
+CONTENT_EXPORT extern const base::Feature kPreloadCookies;
 CONTENT_EXPORT extern const base::Feature kPrivacySandboxAdsAPIsOverride;
 CONTENT_EXPORT extern const base::Feature kPrivateNetworkAccessForWorkers;
 CONTENT_EXPORT extern const base::Feature
diff --git a/content/public/renderer/render_frame_observer.h b/content/public/renderer/render_frame_observer.h
index bd325d0..d921125d 100644
--- a/content/public/renderer/render_frame_observer.h
+++ b/content/public/renderer/render_frame_observer.h
@@ -52,6 +52,10 @@
 struct URLLoaderCompletionStatus;
 }  // namespace network
 
+namespace url {
+class SchemeHostPort;
+}  // namespace url
+
 namespace content {
 
 class RendererPpapiHost;
@@ -238,7 +242,7 @@
   // Complete or Cancel is guaranteed to be called for a response that started.
   // |request_id| uniquely identifies the request within this render frame.
   virtual void DidStartResponse(
-      const GURL& response_url,
+      const url::SchemeHostPort& final_response_url,
       int request_id,
       const network::mojom::URLResponseHead& response_head,
       network::mojom::RequestDestination request_destination) {}
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc
index c4aa4bc4..d7091fc 100644
--- a/content/public/test/browser_test_utils.cc
+++ b/content/public/test/browser_test_utils.cc
@@ -1391,14 +1391,19 @@
   // Prerendering pages will never have user gesture.
   if (adapter.render_frame_host()->GetLifecycleState() ==
       RenderFrameHost::LifecycleState::kPrerendering) {
-    adapter.render_frame_host()->ExecuteJavaScriptForTests(
-        base::UTF8ToUTF16(script), base::NullCallback());
+    ExecuteScriptAsyncWithoutUserGesture(adapter, script);
   } else {
     adapter.render_frame_host()->ExecuteJavaScriptWithUserGestureForTests(
         base::UTF8ToUTF16(script), base::NullCallback());
   }
 }
 
+void ExecuteScriptAsyncWithoutUserGesture(const ToRenderFrameHost& adapter,
+                                          const std::string& script) {
+  adapter.render_frame_host()->ExecuteJavaScriptForTests(
+      base::UTF8ToUTF16(script), base::NullCallback());
+}
+
 bool ExecuteScriptAndExtractDouble(const ToRenderFrameHost& adapter,
                                    const std::string& script, double* result) {
   DCHECK(result);
diff --git a/content/public/test/browser_test_utils.h b/content/public/test/browser_test_utils.h
index 0223ecb..574e640 100644
--- a/content/public/test/browser_test_utils.h
+++ b/content/public/test/browser_test_utils.h
@@ -525,6 +525,11 @@
 void ExecuteScriptAsync(const ToRenderFrameHost& adapter,
                         const std::string& script);
 
+// Same as `content::ExecuteScriptAsync()`, but doesn't send a user gesture to
+// the renderer.
+void ExecuteScriptAsyncWithoutUserGesture(const ToRenderFrameHost& adapter,
+                                          const std::string& script);
+
 // The following methods execute the passed |script| in the specified frame and
 // sets |result| to the value passed to "window.domAutomationController.send" by
 // the executed script. They return true on success, false if the script
diff --git a/content/renderer/pepper/pepper_video_decoder_host.cc b/content/renderer/pepper/pepper_video_decoder_host.cc
index 3235606..09fc9ab 100644
--- a/content/renderer/pepper/pepper_video_decoder_host.cc
+++ b/content/renderer/pepper/pepper_video_decoder_host.cc
@@ -260,10 +260,7 @@
 
   shm_buffers_[shm_id].busy = true;
   decoder_->Decode(media::BitstreamBuffer(
-      decode_id,
-      base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-          shm_buffers_[shm_id].region.Duplicate()),
-      size));
+      decode_id, shm_buffers_[shm_id].region.Duplicate(), size));
 
   return PP_OK_COMPLETIONPENDING;
 }
@@ -560,9 +557,7 @@
   for (const PendingDecode& decode : pending_decodes_) {
     DCHECK(shm_buffers_[decode.shm_id].busy);
     decoder_->Decode(media::BitstreamBuffer(
-        decode.decode_id,
-        base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-            shm_buffers_[decode.shm_id].region.Duplicate()),
+        decode.decode_id, shm_buffers_[decode.shm_id].region.Duplicate(),
         decode.size));
   }
 
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 9e15a2b..64535e5 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -2523,11 +2523,11 @@
     network::mojom::URLResponseHeadPtr response_head,
     network::mojom::RequestDestination request_destination) {
   if (!blink::IsRequestDestinationFrame(request_destination)) {
-    GetFrameHost()->SubresourceResponseStarted(response_url,
-                                               response_head->cert_status);
+    GetFrameHost()->SubresourceResponseStarted(
+        url::SchemeHostPort(response_url), response_head->cert_status);
   }
-  DidStartResponse(response_url, request_id, std::move(response_head),
-                   request_destination);
+  DidStartResponse(url::SchemeHostPort(response_url), request_id,
+                   std::move(response_head), request_destination);
 }
 
 void RenderFrameImpl::NotifyResourceTransferSizeUpdated(
@@ -4395,12 +4395,12 @@
 }
 
 void RenderFrameImpl::DidStartResponse(
-    const GURL& response_url,
+    const url::SchemeHostPort& final_response_url,
     int request_id,
     network::mojom::URLResponseHeadPtr response_head,
     network::mojom::RequestDestination request_destination) {
   for (auto& observer : observers_) {
-    observer.DidStartResponse(response_url, request_id, *response_head,
+    observer.DidStartResponse(final_response_url, request_id, *response_head,
                               request_destination);
   }
 }
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index e33fb6e..ec68866 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -147,6 +147,7 @@
 
 namespace url {
 class Origin;
+class SchemeHostPort;
 }
 
 namespace content {
@@ -736,7 +737,7 @@
   // browser.
   void OnDroppedNavigation();
 
-  void DidStartResponse(const GURL& response_url,
+  void DidStartResponse(const url::SchemeHostPort& final_response_url,
                         int request_id,
                         network::mojom::URLResponseHeadPtr response_head,
                         network::mojom::RequestDestination request_destination);
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 3927479..5e71bfa 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -634,7 +634,11 @@
   }
 
   if (is_mac) {
-    sources += [ "../public/test/text_input_test_utils_mac.mm" ]
+    sources += [
+      "../browser/renderer_host/test_render_widget_host_view_mac_factory.h",
+      "../browser/renderer_host/test_render_widget_host_view_mac_factory.mm",
+      "../public/test/text_input_test_utils_mac.mm",
+    ]
     deps += [
       "//ui/base/mojom",
       "//ui/views:test_support",
@@ -2047,6 +2051,7 @@
     "../browser/child_process_security_policy_unittest.cc",
     "../browser/client_hints/client_hints_unittest.cc",
     "../browser/code_cache/generated_code_cache_unittest.cc",
+    "../browser/code_cache/simple_lru_cache_index_unittest.cc",
     "../browser/compute_pressure/compute_pressure_host_unittest.cc",
     "../browser/compute_pressure/compute_pressure_manager_unittest.cc",
     "../browser/compute_pressure/compute_pressure_quantizer_unittest.cc",
diff --git a/content/test/data/accessibility/aria/aria-alert.html b/content/test/data/accessibility/aria/aria-alert.html
index 2301c3d..00ef417 100644
--- a/content/test/data/accessibility/aria/aria-alert.html
+++ b/content/test/data/accessibility/aria/aria-alert.html
@@ -1,7 +1,6 @@
 <!DOCTYPE html>
 <!--
 @MAC-ALLOW:AXRoleDescription='alert'
-@MAC-ALLOW:AXSubrole=AXApplicationAlert
 @MAC-ALLOW:AXARIAAtomic=*
 @WIN-ALLOW:atomic:*
 @WIN-ALLOW:container-atomic:*
diff --git a/content/test/data/accessibility/aria/aria-article.html b/content/test/data/accessibility/aria/aria-article.html
index 96e7d8f..1c8c0c8a 100644
--- a/content/test/data/accessibility/aria/aria-article.html
+++ b/content/test/data/accessibility/aria/aria-article.html
@@ -1,6 +1,5 @@
 <!--
 @MAC-ALLOW:AXRoleDescription
-@MAC-ALLOW:AXSubrole=AXDocumentArticle
 @MAC-DENY:AXTitle
 @WIN-ALLOW:xml-roles:article
 -->
diff --git a/content/test/data/accessibility/aria/aria-code.html b/content/test/data/accessibility/aria/aria-code.html
index 99f0751..dd8eb56 100644
--- a/content/test/data/accessibility/aria/aria-code.html
+++ b/content/test/data/accessibility/aria/aria-code.html
@@ -1,6 +1,5 @@
 <!DOCTYPE html>
 <!--
-@MAC-ALLOW:AXSubrole=*
 @MAC-ALLOW:AXRoleDescription=*
 @WIN-ALLOW:xml-roles:*
 @AURALINUX-ALLOW:xml-roles:*
diff --git a/content/test/data/accessibility/aria/aria-complementary.html b/content/test/data/accessibility/aria/aria-complementary.html
index e43d917e..c999263 100644
--- a/content/test/data/accessibility/aria/aria-complementary.html
+++ b/content/test/data/accessibility/aria/aria-complementary.html
@@ -1,10 +1,10 @@
 <!--
 @MAC-ALLOW:AXRole=*
-@MAC-ALLOW:AXSubrole=*
 @MAC-ALLOW:AXRoleDescription=*
 @WIN-ALLOW:xml-roles:*
 @AURALINUX-ALLOW:xml-roles:*
 -->
+<!DOCTYPE html>
 <html>
 <body>
   <div role="complementary">
diff --git a/content/test/data/accessibility/aria/aria-contentinfo.html b/content/test/data/accessibility/aria/aria-contentinfo.html
index a8fde58..3b0aedb 100644
--- a/content/test/data/accessibility/aria/aria-contentinfo.html
+++ b/content/test/data/accessibility/aria/aria-contentinfo.html
@@ -1,6 +1,5 @@
 <!--
 @MAC-ALLOW:AXRole=*
-@MAC-ALLOW:AXSubrole=*
 @MAC-ALLOW:AXRoleDescription=*
 @UIA-WIN-ALLOW:LocalizedControlType='content information'
 @UIA-WIN-ALLOW:LocalizedLandmarkType='content information'
@@ -8,6 +7,7 @@
 @WIN-ALLOW:xml-roles:*
 @AURALINUX-ALLOW:xml-roles:*
 -->
+<!DOCTYPE html>
 <html>
 <body>
   <div role="contentinfo">
diff --git a/content/test/data/accessibility/aria/aria-emphasis.html b/content/test/data/accessibility/aria/aria-emphasis.html
index e61e660..b6f08d3 100644
--- a/content/test/data/accessibility/aria/aria-emphasis.html
+++ b/content/test/data/accessibility/aria/aria-emphasis.html
@@ -1,6 +1,5 @@
 <!DOCTYPE html>
 <!--
-@MAC-ALLOW:AXSubrole=*
 @MAC-ALLOW:AXRoleDescription=*
 @WIN-ALLOW:xml-roles:*
 @AURALINUX-ALLOW:xml-roles:*
diff --git a/content/test/data/accessibility/aria/aria-insertion-deletion.html b/content/test/data/accessibility/aria/aria-insertion-deletion.html
index a27547f..45ed847 100644
--- a/content/test/data/accessibility/aria/aria-insertion-deletion.html
+++ b/content/test/data/accessibility/aria/aria-insertion-deletion.html
@@ -1,6 +1,3 @@
-<!--
-@MAC-ALLOW:AXSubrole=*
--->
 <!DOCTYPE html>
 <html>
 <head>
diff --git a/content/test/data/accessibility/aria/aria-level.html b/content/test/data/accessibility/aria/aria-level.html
index f2d666a..e6b18c3 100644
--- a/content/test/data/accessibility/aria/aria-level.html
+++ b/content/test/data/accessibility/aria/aria-level.html
@@ -1,6 +1,5 @@
 <!--
 @BLINK-ALLOW:hierarchicalLevel*
-@MAC-ALLOW:AXSubrole=AXOutlineRow
 @MAC-ALLOW:AXDisclosing
 @MAC-ALLOW:AXDisclosureLevel
 @MAC-ALLOW:AXIndex
diff --git a/content/test/data/accessibility/aria/aria-log.html b/content/test/data/accessibility/aria/aria-log.html
index 3f459bf..f0bf08d 100644
--- a/content/test/data/accessibility/aria/aria-log.html
+++ b/content/test/data/accessibility/aria/aria-log.html
@@ -1,8 +1,8 @@
 <!--
-@MAC-ALLOW:AXSubrole=*
 @WIN-ALLOW:xml-roles:*
 @AURALINUX-ALLOW:xml-roles:*
 -->
+<!DOCTYPE html>
 <html>
 <body>
  <div role="log">
diff --git a/content/test/data/accessibility/aria/aria-marquee.html b/content/test/data/accessibility/aria/aria-marquee.html
index 4529d24d8..1af99d99 100644
--- a/content/test/data/accessibility/aria/aria-marquee.html
+++ b/content/test/data/accessibility/aria/aria-marquee.html
@@ -1,5 +1,4 @@
 <!--
-@MAC-ALLOW:AXSubrole=*
 @MAC-ALLOW:AXRoleDescription
 @MAC-ALLOW:AXARIALive
 @WIN-ALLOW:xml-roles:*
diff --git a/content/test/data/accessibility/aria/aria-navigation.html b/content/test/data/accessibility/aria/aria-navigation.html
index b402afd..2a690af 100644
--- a/content/test/data/accessibility/aria/aria-navigation.html
+++ b/content/test/data/accessibility/aria/aria-navigation.html
@@ -1,6 +1,5 @@
 <!--
 @MAC-ALLOW:AXRoleDescription
-@MAC-ALLOW:AXSubrole=*
 @WIN-ALLOW:xml-roles*
 @AURALINUX-ALLOW:xml-roles*
 -->
diff --git a/content/test/data/accessibility/aria/aria-pressed.html b/content/test/data/accessibility/aria/aria-pressed.html
index 422e83ab..d09be3a 100644
--- a/content/test/data/accessibility/aria/aria-pressed.html
+++ b/content/test/data/accessibility/aria/aria-pressed.html
@@ -3,7 +3,6 @@
 @WIN-ALLOW:PRESSED
 @WIN-ALLOW:MIXED
 @WIN-ALLOW:checkable:true
-@MAC-ALLOW:AXSubrole=*
 @MAC-ALLOW:AXRoleDescription=*
 @AURALINUX-ALLOW:pressed
 @AURALINUX-ALLOW:indeterminate
diff --git a/content/test/data/accessibility/aria/aria-relevant.html b/content/test/data/accessibility/aria/aria-relevant.html
index 296b846..91ebc9a 100644
--- a/content/test/data/accessibility/aria/aria-relevant.html
+++ b/content/test/data/accessibility/aria/aria-relevant.html
@@ -1,5 +1,5 @@
+<!DOCTYPE html>
 <!--
-@MAC-ALLOW:AXSubrole=*
 @MAC-ALLOW:AXARIARelevant
 @WIN-ALLOW:relevant:*
 @WIN-ALLOW:container-relevant:*
@@ -7,7 +7,6 @@
 @BLINK-ALLOW:containerLiveRelevant*
 @AURALINUX-ALLOW:relevant:*
 @AURALINUX-ALLOW:container-relevant:*
-
 -->
 <html>
 <body>
diff --git a/content/test/data/accessibility/aria/aria-strong.html b/content/test/data/accessibility/aria/aria-strong.html
index 6032ec0..0f55c00d 100644
--- a/content/test/data/accessibility/aria/aria-strong.html
+++ b/content/test/data/accessibility/aria/aria-strong.html
@@ -1,6 +1,5 @@
 <!DOCTYPE html>
 <!--
-@MAC-ALLOW:AXSubrole=*
 @MAC-ALLOW:AXRoleDescription=*
 @WIN-ALLOW:xml-roles:*
 @AURALINUX-ALLOW:xml-roles:*
diff --git a/content/test/data/accessibility/aria/aria-subscript.html b/content/test/data/accessibility/aria/aria-subscript.html
index 25e3a0ba..3799c67 100644
--- a/content/test/data/accessibility/aria/aria-subscript.html
+++ b/content/test/data/accessibility/aria/aria-subscript.html
@@ -9,7 +9,7 @@
 @WIN-ALLOW:text*
 @WIN-DENY:text-align*
 @WIN-DENY:text-indent*
-@MAC-ALLOW:AXSubrole-->
+-->
 <!DOCTYPE html>
 <html>
 <body>
diff --git a/content/test/data/accessibility/aria/aria-term.html b/content/test/data/accessibility/aria/aria-term.html
index c70f4658..2257eb9 100644
--- a/content/test/data/accessibility/aria/aria-term.html
+++ b/content/test/data/accessibility/aria/aria-term.html
@@ -1,6 +1,5 @@
 <!--
 @MAC-ALLOW:AXRoleDescription
-@MAC-ALLOW:AXSubrole=AXTerm
 @MAC-DENY:AXTitle
 @WIN-ALLOW:xml-roles*
 @AURALINUX-ALLOW:xml-roles*
diff --git a/content/test/data/accessibility/aria/aria-time.html b/content/test/data/accessibility/aria/aria-time.html
index 3166163..0c8769d3 100644
--- a/content/test/data/accessibility/aria/aria-time.html
+++ b/content/test/data/accessibility/aria/aria-time.html
@@ -1,6 +1,5 @@
 <!DOCTYPE html>
 <!--
-@MAC-ALLOW:AXSubrole=*
 @MAC-ALLOW:AXRoleDescription=*
 @WIN-ALLOW:xml-roles:*
 @AURALINUX-ALLOW:xml-roles:*
diff --git a/content/test/data/accessibility/aria/aria-togglebutton.html b/content/test/data/accessibility/aria/aria-togglebutton.html
index 84e77a75..8ae0e3c 100644
--- a/content/test/data/accessibility/aria/aria-togglebutton.html
+++ b/content/test/data/accessibility/aria/aria-togglebutton.html
@@ -1,7 +1,6 @@
 <!--
 @WIN-DENY:name*
 @WIN-ALLOW:STATE_SYSTEM_*
-@MAC-ALLOW:AXSubrole=*
 @MAC-ALLOW:AXRoleDescription=*
 @AURALINUX-ALLOW:checked
 @AURALINUX-DENY:checkable
diff --git a/content/test/data/accessibility/css/transform.html b/content/test/data/accessibility/css/transform.html
index 008eb8ee..c2543df 100644
--- a/content/test/data/accessibility/css/transform.html
+++ b/content/test/data/accessibility/css/transform.html
@@ -1,5 +1,4 @@
 <!--
-
 @BLINK-ALLOW:pageLocation=*
 @WIN-ALLOW:location=*
 @UIA-WIN-ALLOW:BoundingRectangle=(0, 50,*
diff --git a/content/test/data/accessibility/html/address.html b/content/test/data/accessibility/html/address.html
index 7c68eaf..bdf9dbe 100644
--- a/content/test/data/accessibility/html/address.html
+++ b/content/test/data/accessibility/html/address.html
@@ -1,6 +1,5 @@
 <!--
 @MAC-ALLOW:AXRoleDescription
-@MAC-ALLOW:AXSubrole=*
 @MAC-ALLOW:AXRoleDescription=*
 -->
 <html>
diff --git a/content/test/data/accessibility/html/article.html b/content/test/data/accessibility/html/article.html
index af405911..51d142f 100644
--- a/content/test/data/accessibility/html/article.html
+++ b/content/test/data/accessibility/html/article.html
@@ -1,6 +1,5 @@
 <!--
 @MAC-ALLOW:AXRoleDescription
-@MAC-ALLOW:AXSubrole=AXDocumentArticle
 @MAC-DENY:AXTitle
 @UIA-WIN-ALLOW:ControlType='UIA_GroupControlTypeId'
 @UIA-WIN-ALLOW:LocalizedControlType='article'
diff --git a/content/test/data/accessibility/html/aside.html b/content/test/data/accessibility/html/aside.html
index cf3eaf1d..0bd87fd 100644
--- a/content/test/data/accessibility/html/aside.html
+++ b/content/test/data/accessibility/html/aside.html
@@ -1,7 +1,6 @@
 <!--
 @BLINK-ALLOW:hierarchicalLevel*
 @MAC-ALLOW:AXRoleDescription
-@MAC-ALLOW:AXSubrole=*
 @WIN-ALLOW:xml-roles*
 @AURALINUX-ALLOW:xml-roles*
 -->
diff --git a/content/test/data/accessibility/html/del.html b/content/test/data/accessibility/html/del.html
index e2b6b70..86a608a 100644
--- a/content/test/data/accessibility/html/del.html
+++ b/content/test/data/accessibility/html/del.html
@@ -1,5 +1,4 @@
 <!--
-@MAC-ALLOW:AXSubrole=*
 -->
 <!DOCTYPE html>
 <html>
diff --git a/content/test/data/accessibility/html/dialog.html b/content/test/data/accessibility/html/dialog.html
index 113025e..77fbb3c 100644
--- a/content/test/data/accessibility/html/dialog.html
+++ b/content/test/data/accessibility/html/dialog.html
@@ -1,5 +1,4 @@
 <!--
-@MAC-ALLOW:AXSubrole=*
 -->
 <html>
 <body>
diff --git a/content/test/data/accessibility/html/form-validation-message-after-hide-timeout.html b/content/test/data/accessibility/html/form-validation-message-after-hide-timeout.html
index 44cbe8b4..1cdf38c 100644
--- a/content/test/data/accessibility/html/form-validation-message-after-hide-timeout.html
+++ b/content/test/data/accessibility/html/form-validation-message-after-hide-timeout.html
@@ -1,7 +1,6 @@
 <!--
 @BLINK-ALLOW:live*
 @BLINK-ALLOW:container*
-@MAC-ALLOW:AXSubrole=AXApplicationAlert
 @WAIT-FOR:ready
 -->
 <!-- Ensure that validation errormessage relation is still avalailable after it has been hidden, as long as field is still invalid -->
diff --git a/content/test/data/accessibility/html/form-validation-message.html b/content/test/data/accessibility/html/form-validation-message.html
index 011db68..62ea0628 100644
--- a/content/test/data/accessibility/html/form-validation-message.html
+++ b/content/test/data/accessibility/html/form-validation-message.html
@@ -2,7 +2,6 @@
 @BLINK-ALLOW:live*
 @BLINK-ALLOW:container*
 @WIN-ALLOW:n_relations*
-@MAC-ALLOW:AXSubrole=AXApplicationAlert
 -->
 <!DOCTYPE html>
 <form>
diff --git a/content/test/data/accessibility/html/ins.html b/content/test/data/accessibility/html/ins.html
index 6a9f958..b65971b9e 100644
--- a/content/test/data/accessibility/html/ins.html
+++ b/content/test/data/accessibility/html/ins.html
@@ -1,5 +1,4 @@
 <!--
-@MAC-ALLOW:AXSubrole=*
 -->
 <!DOCTYPE html>
 <html>
diff --git a/content/test/data/accessibility/html/landmark.html b/content/test/data/accessibility/html/landmark.html
index fd0ddf00..c07d697 100644
--- a/content/test/data/accessibility/html/landmark.html
+++ b/content/test/data/accessibility/html/landmark.html
@@ -1,6 +1,5 @@
 <!--
 @MAC-ALLOW:AXRoleDescription
-@MAC-ALLOW:AXSubrole=*
 @WIN-ALLOW:xml-roles:*
 @AURALINUX-ALLOW:xml-roles:*
 -->
diff --git a/content/test/data/accessibility/html/main.html b/content/test/data/accessibility/html/main.html
index 2fb79b8..132e857 100644
--- a/content/test/data/accessibility/html/main.html
+++ b/content/test/data/accessibility/html/main.html
@@ -1,6 +1,5 @@
 <!--
 @MAC-ALLOW:AXRoleDescription
-@MAC-ALLOW:AXSubrole=*
 @WIN-ALLOW:xml-roles:*
 @AURALINUX-ALLOW:xml-roles:*
 -->
diff --git a/content/test/data/accessibility/html/modal-dialog-closed.html b/content/test/data/accessibility/html/modal-dialog-closed.html
index 04953d7..5fb971e 100644
--- a/content/test/data/accessibility/html/modal-dialog-closed.html
+++ b/content/test/data/accessibility/html/modal-dialog-closed.html
@@ -1,5 +1,4 @@
 <!--
-@MAC-ALLOW:AXSubrole=*
 @WIN-ALLOW:haspopup*
 @AURALINUX-ALLOW:haspopup*
 @AURALINUX-ALLOW:expand*
diff --git a/content/test/data/accessibility/html/modal-dialog-in-iframe-closed.html b/content/test/data/accessibility/html/modal-dialog-in-iframe-closed.html
index 9c46c57..62f2cee3 100644
--- a/content/test/data/accessibility/html/modal-dialog-in-iframe-closed.html
+++ b/content/test/data/accessibility/html/modal-dialog-in-iframe-closed.html
@@ -1,5 +1,4 @@
 <!--
-@MAC-ALLOW:AXSubrole=*
 -->
 <html>
 <body>
diff --git a/content/test/data/accessibility/html/modal-dialog-in-iframe-opened.html b/content/test/data/accessibility/html/modal-dialog-in-iframe-opened.html
index 25dabeb..20b4a65 100644
--- a/content/test/data/accessibility/html/modal-dialog-in-iframe-opened.html
+++ b/content/test/data/accessibility/html/modal-dialog-in-iframe-opened.html
@@ -1,5 +1,4 @@
 <!--
-@MAC-ALLOW:AXSubrole=*
 -->
 <html>
 <body>
diff --git a/content/test/data/accessibility/html/modal-dialog-opened.html b/content/test/data/accessibility/html/modal-dialog-opened.html
index 92741e4..d4bc45a8 100644
--- a/content/test/data/accessibility/html/modal-dialog-opened.html
+++ b/content/test/data/accessibility/html/modal-dialog-opened.html
@@ -1,5 +1,4 @@
 <!--
-@MAC-ALLOW:AXSubrole=*
 @AURALINUX-ALLOW:modal
 -->
 <html>
diff --git a/content/test/data/accessibility/html/modal-dialog-stack.html b/content/test/data/accessibility/html/modal-dialog-stack.html
index d019dd7..3e28330 100644
--- a/content/test/data/accessibility/html/modal-dialog-stack.html
+++ b/content/test/data/accessibility/html/modal-dialog-stack.html
@@ -1,5 +1,4 @@
 <!--
-@MAC-ALLOW:AXSubrole=*
 @AURALINUX-ALLOW:modal
 -->
 <html>
diff --git a/content/test/data/accessibility/html/navigation.html b/content/test/data/accessibility/html/navigation.html
index a07a6f4a..1e13970 100644
--- a/content/test/data/accessibility/html/navigation.html
+++ b/content/test/data/accessibility/html/navigation.html
@@ -1,6 +1,5 @@
 <!--
 @MAC-ALLOW:AXRoleDescription
-@MAC-ALLOW:AXSubrole=*
 @WIN-ALLOW:xml-roles*
 @AURALINUX-ALLOW:xml-roles*
 -->
diff --git a/content/test/data/accessibility/mac/selection/set-selection-textarea-expected.txt b/content/test/data/accessibility/mac/selection/set-selection-textarea-expected.txt
index f6566dd..06faf9ef 100644
--- a/content/test/data/accessibility/mac/selection/set-selection-textarea-expected.txt
+++ b/content/test/data/accessibility/mac/selection/set-selection-textarea-expected.txt
@@ -28,3 +28,12 @@
 textarea.AXSelectedTextRange={loc: 20, len: 5}
 AXSelectedTextChanged on AXTextArea AXValue='The quick brown fox jumps over the lazy dog<newline>' AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeUnknown
 textarea.AXSelectedText='jumps'
+// Put cursor after the 2nd word.
+textarea.AXSelectedTextRange={loc: 10, len: 0}
+AXSelectedTextChanged on AXTextArea AXValue='The quick brown fox jumps over the lazy dog<newline>' AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeUnknown
+// Force line break.
+press Enter
+// Select text on the 2nd line (fox).
+textarea.AXSelectedTextRange={loc: 17, len: 3}
+AXSelectedTextChanged on AXTextArea AXValue='The quick <newline>brown fox jumps over the lazy dog<newline>' AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeUnknown
+textarea.AXSelectedText='fox'
diff --git a/content/test/data/accessibility/mac/selection/set-selection-textarea.html b/content/test/data/accessibility/mac/selection/set-selection-textarea.html
index 8c4ebbf..0718e24b 100644
--- a/content/test/data/accessibility/mac/selection/set-selection-textarea.html
+++ b/content/test/data/accessibility/mac/selection/set-selection-textarea.html
@@ -36,6 +36,17 @@
   textarea.AXSelectedTextRange = {loc: 20, len: 5}
   wait for AXSelectedTextChanged on AXTextArea
   textarea.AXSelectedText
+
+@SCRIPT:
+  // Put cursor after the 2nd word.
+  textarea.AXSelectedTextRange = {loc: 10, len: 0}
+  wait for AXSelectedTextChanged on AXTextArea
+  // Force line break.
+  press Enter
+  // Select text on the 2nd line (fox).
+  textarea.AXSelectedTextRange = {loc: 17, len: 3}
+  wait for AXSelectedTextChanged on AXTextArea
+  textarea.AXSelectedText
 -->
 <!DOCTYPE html>
 <html>
diff --git a/content/test/data/accessibility/readme.md b/content/test/data/accessibility/readme.md
index 7597b63..ba4bb22 100644
--- a/content/test/data/accessibility/readme.md
+++ b/content/test/data/accessibility/readme.md
@@ -275,6 +275,16 @@
 and provide the event target. For example:
 `wait for AXFocusedUIElementChanged on AXButton`
 
+You can use `press` instruction to simulate key events.
+The instruction accepts a single parameter which could be a character or
+a key name (as specified in http://www.w3.org/TR/DOM-Level-3-Events-key/).
+For example,
+```
+@SCRIPT:
+  press Enter
+  wait for AXValueChanged
+```
+
 You can use `print tree` to print a snapshot of an accessible tree. For example,
 ```
 @SCRIPT:
diff --git a/content/test/test_render_frame.cc b/content/test/test_render_frame.cc
index 3a5b41d4..9e1d08ea 100644
--- a/content/test/test_render_frame.cc
+++ b/content/test/test_render_frame.cc
@@ -190,7 +190,7 @@
       mojo::PendingRemote<blink::mojom::PolicyContainerHostKeepAliveHandle>)
       override {}
 
-  void SubresourceResponseStarted(const GURL& url,
+  void SubresourceResponseStarted(const url::SchemeHostPort& final_response_url,
                                   net::CertStatus cert_status) override {}
 
   void ResourceLoadComplete(
diff --git a/docs/patterns/bool-init.md b/docs/patterns/bool-init.md
new file mode 100644
index 0000000..df50dd2
--- /dev/null
+++ b/docs/patterns/bool-init.md
@@ -0,0 +1,58 @@
+# The Bool Init Pattern
+
+The `bool Init()` pattern allows a class to have initialization behavior which
+can fail. Since Chromium C++ doesn't allow exceptions, ordinarily constructors
+aren't allowed to fail except by crashing the entire program.
+
+In practice, this pattern looks like this:
+
+    class C {
+     public:
+      C();
+
+      // nodiscard is best practice here, to ensure callers don't ignore
+      // the failure value. It is otherwise VERY easy to accidentally ignore
+      // an Init failure.
+      [[nodiscard]] bool Init();
+    };
+
+and then client classes need to do something like this:
+
+    auto c = std::make_unique<C>(...);
+    if (!c->Init())
+      return WELL_THAT_DIDNT_GO_SO_WELL;
+
+## When To Use This Pattern
+
+Probably don't. The factory pattern or unconditional initialization are
+alternatives that don't require client classes to remember to call `Init()`
+every time they use your class.
+
+This pattern is often used internally as part of a class, but having a public
+`Init` method that clients of your class are expected to call is error-prone -
+it is too easy for client classes to forget to call it, and detecting that error
+requires runtime checking inside your class. As such, you should not add a
+public `Init` method. It is also important for *subclass* clients to remember to
+call `Init`, which adds yet another source of error.
+
+However, this pattern is sometimes used internally as part of the factory
+pattern, in which case the factory often looks like this:
+
+    // static
+    std::unique_ptr<C> C::Make(...) {
+      auto c = std::make_unique<C>(...);
+      if (!c->Init())
+        c.reset();
+      return c;
+    }
+
+That particular use, where there is a single `Init` call site and clients cannot
+otherwise acquire an uninitialized instance of `C`, is much safer but the risks
+involved in subclassing `C` still apply.
+
+## Alternatives / See also:
+
+* The [builder](builder-and-parameter-bundle.md) pattern
+* Unconditional initialization (arrange your object so that initializing it
+  cannot fail)
+* RAII in general
diff --git a/docs/patterns/builder-and-parameter-bundle.md b/docs/patterns/builder-and-parameter-bundle.md
index 37368b26..032d54b3 100644
--- a/docs/patterns/builder-and-parameter-bundle.md
+++ b/docs/patterns/builder-and-parameter-bundle.md
@@ -175,7 +175,8 @@
   which should not be changed once the object is "in use"
 * Your class has invalid sets of configuration options that should be prohibited
 * Your class has complicated configuration options of any sort that want runtime
-  checking (places you might have previously used the `bool Init()` pattern)
+  checking (places you might have previously used the [bool Init()
+  pattern](bool-init.md))
 
 ## Alternatives / See Also
 
diff --git a/extensions/browser/background_script_executor.cc b/extensions/browser/background_script_executor.cc
index 4619ee8..a0d2a84bb 100644
--- a/extensions/browser/background_script_executor.cc
+++ b/extensions/browser/background_script_executor.cc
@@ -188,7 +188,8 @@
       browsertest_util::ScriptUserActivation::kActivate) {
     content::ExecuteScriptAsync(host->host_contents(), script_);
   } else {
-    NOTREACHED() << "Not yet supported. Use ExecuteScriptInBackgroundPage().";
+    content::ExecuteScriptAsyncWithoutUserGesture(host->host_contents(),
+                                                  script_);
   }
   return true;
 }
diff --git a/extensions/browser/browsertest_util.cc b/extensions/browser/browsertest_util.cc
index a1df642..c1292e8 100644
--- a/extensions/browser/browsertest_util.cc
+++ b/extensions/browser/browsertest_util.cc
@@ -33,30 +33,6 @@
     const std::string& extension_id,
     const std::string& script,
     ScriptUserActivation script_user_activation) {
-  // BackgroundScriptExecutor does not yet support kDontActivate.
-  // TODO(https://crbug.com/1319642): Make it so.
-  if (script_user_activation == ScriptUserActivation::kDontActivate) {
-    ExtensionHost* host =
-        ProcessManager::Get(context)->GetBackgroundHostForExtension(
-            extension_id);
-    if (!host) {
-      ADD_FAILURE() << "Extension " << extension_id
-                    << " has no background page.";
-      return std::string();
-    }
-
-    std::string result;
-    bool success = content::ExecuteScriptWithoutUserGestureAndExtractString(
-        host->host_contents(), script, &result);
-    if (!success) {
-      ADD_FAILURE() << "Executing script failed: " << GetScriptToLog(script);
-      result.clear();
-    }
-    return result;
-  }
-
-  DCHECK_EQ(ScriptUserActivation::kActivate, script_user_activation);
-
   BackgroundScriptExecutor script_executor(context);
   // Legacy scripts were written to pass the (string) result via
   // window.domAutomationController.send().
diff --git a/fuchsia/engine/browser/frame_impl.cc b/fuchsia/engine/browser/frame_impl.cc
index f3f45b98..ad0198ef 100644
--- a/fuchsia/engine/browser/frame_impl.cc
+++ b/fuchsia/engine/browser/frame_impl.cc
@@ -604,13 +604,13 @@
   if (!context_->has_cast_streaming_enabled() || !receiver_session_client_)
     return;
 
-  mojo::AssociatedRemote<cast_streaming::mojom::CastStreamingReceiver>
-      cast_streaming_receiver;
+  mojo::AssociatedRemote<cast_streaming::mojom::DemuxerConnector>
+      demuxer_connector;
   navigation_handle->GetRenderFrameHost()
       ->GetRemoteAssociatedInterfaces()
-      ->GetInterface(&cast_streaming_receiver);
+      ->GetInterface(&demuxer_connector);
   receiver_session_client_->SetCastStreamingReceiver(
-      std::move(cast_streaming_receiver));
+      std::move(demuxer_connector));
 }
 
 void FrameImpl::UpdateRenderViewZoomLevel(
diff --git a/fuchsia/engine/browser/receiver_session_client.cc b/fuchsia/engine/browser/receiver_session_client.cc
index 00ee109c..24472c4 100644
--- a/fuchsia/engine/browser/receiver_session_client.cc
+++ b/fuchsia/engine/browser/receiver_session_client.cc
@@ -21,8 +21,8 @@
 ReceiverSessionClient::~ReceiverSessionClient() = default;
 
 void ReceiverSessionClient::SetCastStreamingReceiver(
-    mojo::AssociatedRemote<cast_streaming::mojom::CastStreamingReceiver>
-        cast_streaming_receiver) {
+    mojo::AssociatedRemote<cast_streaming::mojom::DemuxerConnector>
+        demuxer_connector) {
   DCHECK(message_port_request_);
 
   // TODO: Add streaming session Constraints based on system capabilities
@@ -47,5 +47,5 @@
                 std::move(port));
           },
           std::move(message_port_request_)));
-  receiver_session_->StartStreamingAsync(std::move(cast_streaming_receiver));
+  receiver_session_->StartStreamingAsync(std::move(demuxer_connector));
 }
diff --git a/fuchsia/engine/browser/receiver_session_client.h b/fuchsia/engine/browser/receiver_session_client.h
index 1d753d59..c9ea7b601 100644
--- a/fuchsia/engine/browser/receiver_session_client.h
+++ b/fuchsia/engine/browser/receiver_session_client.h
@@ -25,8 +25,8 @@
   ReceiverSessionClient& operator=(const ReceiverSessionClient&) = delete;
 
   void SetCastStreamingReceiver(
-      mojo::AssociatedRemote<cast_streaming::mojom::CastStreamingReceiver>
-          cast_streaming_receiver);
+      mojo::AssociatedRemote<cast_streaming::mojom::DemuxerConnector>
+          demuxer_connector);
 
  private:
   // Populated in the ctor, and removed when |receiver_session_| is created in
diff --git a/gpu/command_buffer/client/webgpu_implementation.cc b/gpu/command_buffer/client/webgpu_implementation.cc
index 1429e2d..5d6cebe 100644
--- a/gpu/command_buffer/client/webgpu_implementation.cc
+++ b/gpu/command_buffer/client/webgpu_implementation.cc
@@ -480,22 +480,21 @@
 #endif
 }
 
-void WebGPUImplementation::EnsureAwaitingFlush(bool* needs_flush) {
+bool WebGPUImplementation::EnsureAwaitingFlush() {
 #if BUILDFLAG(USE_DAWN)
   // If there is already a flush waiting, we don't need to flush.
   // We only want to set |needs_flush| on state transition from
   // false -> true.
   if (dawn_wire_->serializer()->AwaitingFlush()) {
-    *needs_flush = false;
-    return;
+    return false;
   }
 
   // Set the state to waiting for flush, and then write |needs_flush|.
   // Could still be false if there's no data to flush.
   dawn_wire_->serializer()->SetAwaitingFlush(true);
-  *needs_flush = dawn_wire_->serializer()->AwaitingFlush();
+  return dawn_wire_->serializer()->AwaitingFlush();
 #else
-  *needs_flush = false;
+  return false;
 #endif
 }
 
diff --git a/gpu/command_buffer/client/webgpu_implementation.h b/gpu/command_buffer/client/webgpu_implementation.h
index 14449fe..aeaaf9259 100644
--- a/gpu/command_buffer/client/webgpu_implementation.h
+++ b/gpu/command_buffer/client/webgpu_implementation.h
@@ -115,7 +115,7 @@
 
   // WebGPUInterface implementation
   void FlushCommands() override;
-  void EnsureAwaitingFlush(bool* needs_flush) override;
+  bool EnsureAwaitingFlush() override;
   void FlushAwaitingCommands() override;
   scoped_refptr<APIChannel> GetAPIChannel() const override;
   ReservedTexture ReserveTexture(WGPUDevice device) override;
diff --git a/gpu/command_buffer/client/webgpu_interface.h b/gpu/command_buffer/client/webgpu_interface.h
index 8d23aba..4ce466a 100644
--- a/gpu/command_buffer/client/webgpu_interface.h
+++ b/gpu/command_buffer/client/webgpu_interface.h
@@ -54,7 +54,7 @@
   // if a flush has already been indicated, or a flush is not needed (there may
   // be no commands to flush). Returns true if the caller should schedule a
   // flush.
-  virtual void EnsureAwaitingFlush(bool* needs_flush) = 0;
+  virtual bool EnsureAwaitingFlush() = 0;
 
   // If the awaiting flush flag is set, flushes commands. Otherwise, does
   // nothing.
diff --git a/gpu/command_buffer/client/webgpu_interface_stub.cc b/gpu/command_buffer/client/webgpu_interface_stub.cc
index 501d65a..b523e49 100644
--- a/gpu/command_buffer/client/webgpu_interface_stub.cc
+++ b/gpu/command_buffer/client/webgpu_interface_stub.cc
@@ -48,7 +48,9 @@
   return api_channel_;
 }
 void WebGPUInterfaceStub::FlushCommands() {}
-void WebGPUInterfaceStub::EnsureAwaitingFlush(bool* needs_flush) {}
+bool WebGPUInterfaceStub::EnsureAwaitingFlush() {
+  return false;
+}
 void WebGPUInterfaceStub::FlushAwaitingCommands() {}
 ReservedTexture WebGPUInterfaceStub::ReserveTexture(WGPUDevice) {
   return {nullptr, 0, 0, 0, 0};
diff --git a/gpu/command_buffer/client/webgpu_interface_stub.h b/gpu/command_buffer/client/webgpu_interface_stub.h
index 4cd97eb..1581f2e 100644
--- a/gpu/command_buffer/client/webgpu_interface_stub.h
+++ b/gpu/command_buffer/client/webgpu_interface_stub.h
@@ -26,7 +26,7 @@
   // WebGPUInterface implementation
   scoped_refptr<APIChannel> GetAPIChannel() const override;
   void FlushCommands() override;
-  void EnsureAwaitingFlush(bool* needs_flush) override;
+  bool EnsureAwaitingFlush() override;
   void FlushAwaitingCommands() override;
   ReservedTexture ReserveTexture(WGPUDevice device) override;
   void RequestAdapterAsync(
diff --git a/gpu/command_buffer/service/BUILD.gn b/gpu/command_buffer/service/BUILD.gn
index 1cd32a52..3218ff3 100644
--- a/gpu/command_buffer/service/BUILD.gn
+++ b/gpu/command_buffer/service/BUILD.gn
@@ -282,10 +282,14 @@
 
   if (use_dawn) {
     sources += [
+      "dawn_instance.cc",
+      "dawn_instance.h",
       "dawn_platform.cc",
       "dawn_platform.h",
       "dawn_service_memory_transfer_service.cc",
       "dawn_service_memory_transfer_service.h",
+      "dawn_service_serializer.cc",
+      "dawn_service_serializer.h",
       "webgpu_decoder_impl.cc",
       "webgpu_decoder_impl.h",
     ]
diff --git a/gpu/command_buffer/service/dawn_instance.cc b/gpu/command_buffer/service/dawn_instance.cc
new file mode 100644
index 0000000..0865764
--- /dev/null
+++ b/gpu/command_buffer/service/dawn_instance.cc
@@ -0,0 +1,73 @@
+// Copyright 2022 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 "gpu/command_buffer/service/dawn_instance.h"
+
+#include "base/base_paths.h"
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "build/buildflag.h"
+#include "gpu/config/gpu_preferences.h"
+
+#if BUILDFLAG(IS_MAC)
+#include "base/mac/bundle_locations.h"
+#include "base/mac/foundation_util.h"
+#endif
+
+namespace gpu::webgpu {
+
+// static
+std::unique_ptr<DawnInstance> DawnInstance::Create(
+    dawn::platform::Platform* platform,
+    const GpuPreferences& gpu_preferences) {
+  std::string dawn_search_path;
+  base::FilePath module_path;
+#if BUILDFLAG(IS_MAC)
+  if (base::mac::AmIBundled()) {
+    dawn_search_path = base::mac::FrameworkBundlePath()
+                           .Append("Libraries")
+                           .AsEndingWithSeparator()
+                           .MaybeAsASCII();
+  }
+  if (dawn_search_path.empty())
+#endif
+  {
+    if (base::PathService::Get(base::DIR_MODULE, &module_path)) {
+      dawn_search_path = module_path.AsEndingWithSeparator().MaybeAsASCII();
+    }
+  }
+  const char* dawn_search_path_c_str = dawn_search_path.c_str();
+
+  WGPUDawnInstanceDescriptor dawn_instance_desc = {
+      .chain =
+          {
+              .sType = WGPUSType_DawnInstanceDescriptor,
+          },
+      .additionalRuntimeSearchPathsCount = dawn_search_path.empty() ? 0u : 1u,
+      .additionalRuntimeSearchPaths = &dawn_search_path_c_str,
+  };
+  WGPUInstanceDescriptor instance_desc = {
+      .nextInChain = &dawn_instance_desc.chain,
+  };
+
+  auto instance = std::make_unique<DawnInstance>(&instance_desc);
+  instance->SetPlatform(platform);
+
+  switch (gpu_preferences.enable_dawn_backend_validation) {
+    case DawnBackendValidationLevel::kDisabled:
+      break;
+    case DawnBackendValidationLevel::kPartial:
+      instance->SetBackendValidationLevel(
+          dawn::native::BackendValidationLevel::Partial);
+      break;
+    case DawnBackendValidationLevel::kFull:
+      instance->SetBackendValidationLevel(
+          dawn::native::BackendValidationLevel::Full);
+      break;
+  }
+
+  return instance;
+}
+
+}  // namespace gpu::webgpu
\ No newline at end of file
diff --git a/gpu/command_buffer/service/dawn_instance.h b/gpu/command_buffer/service/dawn_instance.h
new file mode 100644
index 0000000..8aa4e11
--- /dev/null
+++ b/gpu/command_buffer/service/dawn_instance.h
@@ -0,0 +1,36 @@
+// Copyright 2022 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 GPU_COMMAND_BUFFER_SERVICE_DAWN_INSTANCE_H_
+#define GPU_COMMAND_BUFFER_SERVICE_DAWN_INSTANCE_H_
+
+#include <dawn/native/DawnNative.h>
+#include <dawn/platform/DawnPlatform.h>
+
+#include <memory>
+
+namespace dawn::platform {
+class Platform;
+}  // namespace dawn::platform
+
+namespace gpu {
+
+struct GpuPreferences;
+
+namespace webgpu {
+
+class DawnInstance : public dawn::native::Instance {
+ public:
+  static std::unique_ptr<DawnInstance> Create(
+      dawn::platform::Platform* platform,
+      const GpuPreferences& gpu_preferences);
+
+ private:
+  using dawn::native::Instance::Instance;
+};
+
+}  // namespace webgpu
+}  // namespace gpu
+
+#endif  // GPU_COMMAND_BUFFER_SERVICE_DAWN_INSTANCE_H_
diff --git a/gpu/command_buffer/service/dawn_service_serializer.cc b/gpu/command_buffer/service/dawn_service_serializer.cc
new file mode 100644
index 0000000..086b06e
--- /dev/null
+++ b/gpu/command_buffer/service/dawn_service_serializer.cc
@@ -0,0 +1,91 @@
+// 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.
+
+#include "gpu/command_buffer/service/dawn_service_serializer.h"
+
+#include "base/trace_event/trace_event.h"
+#include "gpu/command_buffer/common/webgpu_cmd_format.h"
+#include "gpu/command_buffer/service/decoder_client.h"
+#include "ipc/ipc_channel.h"
+
+namespace gpu::webgpu {
+
+namespace {
+
+constexpr size_t kMaxWireBufferSize =
+    std::min(IPC::Channel::kMaximumMessageSize,
+             static_cast<size_t>(1024 * 1024));
+
+constexpr size_t kDawnReturnCmdsOffset =
+    offsetof(cmds::DawnReturnCommandsInfo, deserialized_buffer);
+
+static_assert(kDawnReturnCmdsOffset < kMaxWireBufferSize, "");
+
+}  // anonymous namespace
+
+DawnServiceSerializer::DawnServiceSerializer(DecoderClient* client)
+    : client_(client),
+      buffer_(kMaxWireBufferSize),
+      put_offset_(offsetof(cmds::DawnReturnCommandsInfo, deserialized_buffer)) {
+  // We prepopulate the message with the header and keep it between flushes so
+  // we never need to write it again.
+  cmds::DawnReturnCommandsInfoHeader* header =
+      reinterpret_cast<cmds::DawnReturnCommandsInfoHeader*>(&buffer_[0]);
+  header->return_data_header.return_data_type =
+      DawnReturnDataType::kDawnCommands;
+}
+
+DawnServiceSerializer::~DawnServiceSerializer() = default;
+
+size_t DawnServiceSerializer::GetMaximumAllocationSize() const {
+  return kMaxWireBufferSize - kDawnReturnCmdsOffset;
+}
+
+void* DawnServiceSerializer::GetCmdSpace(size_t size) {
+  // Note: Dawn will never call this function with |size| >
+  // GetMaximumAllocationSize().
+  DCHECK_LE(put_offset_, kMaxWireBufferSize);
+  DCHECK_LE(size, GetMaximumAllocationSize());
+
+  // Statically check that kMaxWireBufferSize + kMaxWireBufferSize is
+  // a valid uint32_t. We can add put_offset_ and size without overflow.
+  static_assert(base::CheckAdd(kMaxWireBufferSize, kMaxWireBufferSize)
+                    .IsValid<uint32_t>(),
+                "");
+  uint32_t next_offset = put_offset_ + static_cast<uint32_t>(size);
+  if (next_offset > buffer_.size()) {
+    Flush();
+    // TODO(enga): Keep track of how much command space the application is using
+    // and adjust the buffer size accordingly.
+
+    DCHECK_EQ(put_offset_, kDawnReturnCmdsOffset);
+    next_offset = put_offset_ + static_cast<uint32_t>(size);
+  }
+
+  uint8_t* ptr = &buffer_[put_offset_];
+  put_offset_ = next_offset;
+  return ptr;
+}
+
+bool DawnServiceSerializer::NeedsFlush() const {
+  return put_offset_ > kDawnReturnCmdsOffset;
+}
+
+bool DawnServiceSerializer::Flush() {
+  if (NeedsFlush()) {
+    TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("gpu.dawn"),
+                 "DawnServiceSerializer::Flush", "bytes", put_offset_);
+
+    static uint32_t return_trace_id = 0;
+    TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("gpu.dawn"),
+                           "DawnReturnCommands", return_trace_id++,
+                           TRACE_EVENT_FLAG_FLOW_OUT);
+
+    client_->HandleReturnData(base::make_span(buffer_.data(), put_offset_));
+    put_offset_ = kDawnReturnCmdsOffset;
+  }
+  return true;
+}
+
+}  //  namespace gpu::webgpu
\ No newline at end of file
diff --git a/gpu/command_buffer/service/dawn_service_serializer.h b/gpu/command_buffer/service/dawn_service_serializer.h
new file mode 100644
index 0000000..7536969d
--- /dev/null
+++ b/gpu/command_buffer/service/dawn_service_serializer.h
@@ -0,0 +1,38 @@
+// 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 GPU_COMMAND_BUFFER_SERVICE_DAWN_SERVICE_SERIALIZER_H_
+#define GPU_COMMAND_BUFFER_SERVICE_DAWN_SERVICE_SERIALIZER_H_
+
+#include <dawn_wire/WireClient.h>
+
+#include <memory>
+
+#include "base/memory/raw_ptr.h"
+
+namespace gpu {
+
+class DecoderClient;
+
+namespace webgpu {
+
+class DawnServiceSerializer : public dawn::wire::CommandSerializer {
+ public:
+  explicit DawnServiceSerializer(DecoderClient* client);
+  ~DawnServiceSerializer() override;
+  size_t GetMaximumAllocationSize() const final;
+  void* GetCmdSpace(size_t size) final;
+  bool Flush() final;
+  bool NeedsFlush() const;
+
+ private:
+  raw_ptr<DecoderClient> client_;
+  std::vector<uint8_t> buffer_;
+  size_t put_offset_;
+};
+
+}  // namespace webgpu
+}  // namespace gpu
+
+#endif  // GPU_COMMAND_BUFFER_SERVICE_DAWN_SERVICE_SERIALIZER_H_
\ No newline at end of file
diff --git a/gpu/command_buffer/service/webgpu_decoder_impl.cc b/gpu/command_buffer/service/webgpu_decoder_impl.cc
index 7447f2f..a157778b 100644
--- a/gpu/command_buffer/service/webgpu_decoder_impl.cc
+++ b/gpu/command_buffer/service/webgpu_decoder_impl.cc
@@ -13,22 +13,21 @@
 #include <memory>
 #include <vector>
 
-#include "base/base_paths.h"
 #include "base/bits.h"
-#include "base/files/file_path.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/raw_ptr.h"
 #include "base/numerics/checked_math.h"
-#include "base/path_service.h"
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
 #include "components/viz/common/resources/resource_format_utils.h"
 #include "gpu/command_buffer/common/mailbox.h"
 #include "gpu/command_buffer/common/webgpu_cmd_format.h"
 #include "gpu/command_buffer/service/command_buffer_service.h"
+#include "gpu/command_buffer/service/dawn_instance.h"
 #include "gpu/command_buffer/service/dawn_platform.h"
 #include "gpu/command_buffer/service/dawn_service_memory_transfer_service.h"
+#include "gpu/command_buffer/service/dawn_service_serializer.h"
 #include "gpu/command_buffer/service/decoder_client.h"
 #include "gpu/command_buffer/service/shared_context_state.h"
 #include "gpu/command_buffer/service/shared_image_factory.h"
@@ -37,7 +36,6 @@
 #include "gpu/command_buffer/service/skia_utils.h"
 #include "gpu/command_buffer/service/webgpu_decoder.h"
 #include "gpu/config/gpu_preferences.h"
-#include "ipc/ipc_channel.h"
 #include "third_party/skia/include/core/SkPromiseImageTexture.h"
 #include "third_party/skia/include/gpu/GrBackendSemaphore.h"
 #include "ui/gl/gl_context_egl.h"
@@ -49,25 +47,11 @@
 #include "ui/gl/gl_angle_util_win.h"
 #endif
 
-#if BUILDFLAG(IS_MAC)
-#include "base/mac/bundle_locations.h"
-#include "base/mac/foundation_util.h"
-#endif
-
 namespace gpu {
 namespace webgpu {
 
 namespace {
 
-constexpr size_t kMaxWireBufferSize =
-    std::min(IPC::Channel::kMaximumMessageSize,
-             static_cast<size_t>(1024 * 1024));
-
-constexpr size_t kDawnReturnCmdsOffset =
-    offsetof(cmds::DawnReturnCommandsInfo, deserialized_buffer);
-
-static_assert(kDawnReturnCmdsOffset < kMaxWireBufferSize, "");
-
 static constexpr uint32_t kAllowedWritableMailboxTextureUsages =
     static_cast<uint32_t>(WGPUTextureUsage_CopyDst |
                           WGPUTextureUsage_RenderAttachment |
@@ -80,83 +64,6 @@
 static constexpr uint32_t kAllowedMailboxTextureUsages =
     kAllowedWritableMailboxTextureUsages | kAllowedReadableMailboxTextureUsages;
 
-class WireServerCommandSerializer : public dawn::wire::CommandSerializer {
- public:
-  explicit WireServerCommandSerializer(DecoderClient* client);
-  ~WireServerCommandSerializer() override = default;
-  size_t GetMaximumAllocationSize() const final;
-  void* GetCmdSpace(size_t size) final;
-  bool Flush() final;
-  bool NeedsFlush() const;
-
- private:
-  raw_ptr<DecoderClient> client_;
-  std::vector<uint8_t> buffer_;
-  size_t put_offset_;
-};
-
-WireServerCommandSerializer::WireServerCommandSerializer(DecoderClient* client)
-    : client_(client),
-      buffer_(kMaxWireBufferSize),
-      put_offset_(offsetof(cmds::DawnReturnCommandsInfo, deserialized_buffer)) {
-  // We prepopulate the message with the header and keep it between flushes so
-  // we never need to write it again.
-  cmds::DawnReturnCommandsInfoHeader* header =
-      reinterpret_cast<cmds::DawnReturnCommandsInfoHeader*>(&buffer_[0]);
-  header->return_data_header.return_data_type =
-      DawnReturnDataType::kDawnCommands;
-}
-
-size_t WireServerCommandSerializer::GetMaximumAllocationSize() const {
-  return kMaxWireBufferSize - kDawnReturnCmdsOffset;
-}
-
-void* WireServerCommandSerializer::GetCmdSpace(size_t size) {
-  // Note: Dawn will never call this function with |size| >
-  // GetMaximumAllocationSize().
-  DCHECK_LE(put_offset_, kMaxWireBufferSize);
-  DCHECK_LE(size, GetMaximumAllocationSize());
-
-  // Statically check that kMaxWireBufferSize + kMaxWireBufferSize is
-  // a valid uint32_t. We can add put_offset_ and size without overflow.
-  static_assert(base::CheckAdd(kMaxWireBufferSize, kMaxWireBufferSize)
-                    .IsValid<uint32_t>(),
-                "");
-  uint32_t next_offset = put_offset_ + static_cast<uint32_t>(size);
-  if (next_offset > buffer_.size()) {
-    Flush();
-    // TODO(enga): Keep track of how much command space the application is using
-    // and adjust the buffer size accordingly.
-
-    DCHECK_EQ(put_offset_, kDawnReturnCmdsOffset);
-    next_offset = put_offset_ + static_cast<uint32_t>(size);
-  }
-
-  uint8_t* ptr = &buffer_[put_offset_];
-  put_offset_ = next_offset;
-  return ptr;
-}
-
-bool WireServerCommandSerializer::NeedsFlush() const {
-  return put_offset_ > kDawnReturnCmdsOffset;
-}
-
-bool WireServerCommandSerializer::Flush() {
-  if (NeedsFlush()) {
-    TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("gpu.dawn"),
-                 "WireServerCommandSerializer::Flush", "bytes", put_offset_);
-
-    static uint32_t return_trace_id = 0;
-    TRACE_EVENT_WITH_FLOW0(TRACE_DISABLED_BY_DEFAULT("gpu.dawn"),
-                           "DawnReturnCommands", return_trace_id++,
-                           TRACE_EVENT_FLAG_FLOW_OUT);
-
-    client_->HandleReturnData(base::make_span(buffer_.data(), put_offset_));
-    put_offset_ = kDawnReturnCmdsOffset;
-  }
-  return true;
-}
-
 WGPUAdapterType PowerPreferenceToDawnAdapterType(
     PowerPreference power_preference) {
   switch (power_preference) {
@@ -483,8 +390,8 @@
       shared_image_representation_factory_;
 
   std::unique_ptr<dawn::platform::Platform> dawn_platform_;
+  std::unique_ptr<DawnInstance> dawn_instance_;
   std::unique_ptr<DawnServiceMemoryTransferService> memory_transfer_service_;
-  std::unique_ptr<dawn::native::Instance> dawn_instance_;
   std::vector<dawn::native::Adapter> dawn_adapters_;
 
   bool enable_unsafe_webgpu_ = false;
@@ -493,7 +400,7 @@
   std::vector<std::string> force_disabled_toggles_;
 
   std::unique_ptr<dawn::wire::WireServer> wire_server_;
-  std::unique_ptr<WireServerCommandSerializer> wire_serializer_;
+  std::unique_ptr<DawnServiceSerializer> wire_serializer_;
 
   // Helper class whose derived implementations holds a representation
   // and its ScopedAccess, ensuring safe destruction order.
@@ -969,53 +876,10 @@
               shared_image_manager,
               memory_tracker)),
       dawn_platform_(new DawnPlatform()),
+      dawn_instance_(
+          DawnInstance::Create(dawn_platform_.get(), gpu_preferences)),
       memory_transfer_service_(new DawnServiceMemoryTransferService(this)),
-      wire_serializer_(new WireServerCommandSerializer(client)) {
-  std::string dawn_search_path;
-  base::FilePath module_path;
-#if BUILDFLAG(IS_MAC)
-  if (base::mac::AmIBundled()) {
-    dawn_search_path = base::mac::FrameworkBundlePath()
-                           .Append("Libraries")
-                           .AsEndingWithSeparator()
-                           .MaybeAsASCII();
-  }
-  if (dawn_search_path.empty())
-#endif
-  {
-    if (base::PathService::Get(base::DIR_MODULE, &module_path)) {
-      dawn_search_path = module_path.AsEndingWithSeparator().MaybeAsASCII();
-    }
-  }
-  const char* dawn_search_path_c_str = dawn_search_path.c_str();
-
-  WGPUDawnInstanceDescriptor dawn_instance_desc = {
-      .chain =
-          {
-              .sType = WGPUSType_DawnInstanceDescriptor,
-          },
-      .additionalRuntimeSearchPathsCount = dawn_search_path.empty() ? 0u : 1u,
-      .additionalRuntimeSearchPaths = &dawn_search_path_c_str,
-  };
-  WGPUInstanceDescriptor instance_desc = {
-      .nextInChain = &dawn_instance_desc.chain,
-  };
-  dawn_instance_ = std::make_unique<dawn::native::Instance>(&instance_desc);
-
-  dawn_instance_->SetPlatform(dawn_platform_.get());
-  switch (gpu_preferences.enable_dawn_backend_validation) {
-    case DawnBackendValidationLevel::kDisabled:
-      break;
-    case DawnBackendValidationLevel::kPartial:
-      dawn_instance_->SetBackendValidationLevel(
-          dawn::native::BackendValidationLevel::Partial);
-      break;
-    case DawnBackendValidationLevel::kFull:
-      dawn_instance_->SetBackendValidationLevel(
-          dawn::native::BackendValidationLevel::Full);
-      break;
-  }
-
+      wire_serializer_(new DawnServiceSerializer(client)) {
   enable_unsafe_webgpu_ = gpu_preferences.enable_unsafe_webgpu;
   use_webgpu_adapter_ = gpu_preferences.use_webgpu_adapter;
   force_enabled_toggles_ = gpu_preferences.enabled_dawn_features_list;
diff --git a/gpu/webgpu/BUILD.gn b/gpu/webgpu/BUILD.gn
new file mode 100644
index 0000000..e3e6065d
--- /dev/null
+++ b/gpu/webgpu/BUILD.gn
@@ -0,0 +1,8 @@
+# Copyright 2022 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("common") {
+  sources = [ "callback.h" ]
+  public_deps = [ "//base" ]
+}
diff --git a/gpu/webgpu/OWNERS b/gpu/webgpu/OWNERS
new file mode 100644
index 0000000..47d4a394
--- /dev/null
+++ b/gpu/webgpu/OWNERS
@@ -0,0 +1,3 @@
+cwallez@chromium.org
+kainino@chromium.org
+enga@chromium.org
diff --git a/gpu/webgpu/callback.h b/gpu/webgpu/callback.h
new file mode 100644
index 0000000..00a65431
--- /dev/null
+++ b/gpu/webgpu/callback.h
@@ -0,0 +1,153 @@
+// 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 GPU_WEBGPU_CALLBACK_H_
+#define GPU_WEBGPU_CALLBACK_H_
+
+#include <memory>
+
+#include "base/callback.h"
+
+namespace gpu::webgpu {
+
+// WGPUCallback<Callback> is a heap-allocated version of
+// base::OnceCallback or base::RepeatingCallback.
+// It is allocated on the heap so that it can be reinterpret_cast to/from
+// void* and passed to WGPU C callbacks.
+//
+// Example:
+//   WGPUOnceCallback<F>* callback =
+//     BindWGPUOnceCallback(func, arg1);
+//
+//   // |someWGPUFunction| expects callback function with arguments:
+//   //    Args... args, void* userdata.
+//   // When it is called, it will forward to func(arg1, args...).
+//   GetProcs().someWGPUFunction(
+//     callback->UnboundCallback(), callback->AsUserdata());
+template <typename Callback>
+class WGPUCallbackBase;
+
+template <typename Callback>
+class WGPUOnceCallback;
+
+template <typename Callback>
+class WGPURepeatingCallback;
+
+template <template <typename> class BaseCallbackTemplate,
+          typename R,
+          typename... Args>
+class WGPUCallbackBase<BaseCallbackTemplate<R(Args...)>> {
+  using BaseCallback = BaseCallbackTemplate<R(Args...)>;
+
+  static constexpr bool is_once_callback =
+      std::is_same<BaseCallback, base::OnceCallback<R(Args...)>>::value;
+  static constexpr bool is_repeating_callback =
+      std::is_same<BaseCallback, base::RepeatingCallback<R(Args...)>>::value;
+  static_assert(
+      is_once_callback || is_repeating_callback,
+      "Callback must be base::OnceCallback or base::RepeatingCallback");
+
+ public:
+  explicit WGPUCallbackBase(BaseCallback callback)
+      : callback_(std::move(callback)) {}
+
+  void* AsUserdata() { return static_cast<void*>(this); }
+
+ protected:
+  using UnboundCallbackFunction = R (*)(Args..., void*);
+
+  static WGPUCallbackBase* FromUserdata(void* userdata) {
+    return static_cast<WGPUCallbackBase*>(userdata);
+  }
+
+  R Run(Args... args) && {
+    static_assert(
+        is_once_callback,
+        "Run on a moved receiver must only be called on a once callback.");
+    return std::move(callback_).Run(std::forward<Args>(args)...);
+  }
+
+  R Run(Args... args) const& {
+    static_assert(is_repeating_callback,
+                  "Run on a unmoved receiver must only be called on a "
+                  "repeating callback.");
+    return callback_.Run(std::forward<Args>(args)...);
+  }
+
+ private:
+  BaseCallback callback_;
+};
+
+template <typename R, typename... Args>
+class WGPUOnceCallback<R(Args...)>
+    : public WGPUCallbackBase<base::OnceCallback<R(Args...)>> {
+  using BaseCallback = base::OnceCallback<R(Args...)>;
+
+ public:
+  using WGPUCallbackBase<BaseCallback>::WGPUCallbackBase;
+
+  typename WGPUCallbackBase<BaseCallback>::UnboundCallbackFunction
+  UnboundCallback() {
+    return CallUnboundOnceCallback;
+  }
+
+ private:
+  static R CallUnboundOnceCallback(Args... args, void* handle) {
+    // After this non-repeating callback is run, it should delete itself.
+    auto callback =
+        std::unique_ptr<WGPUOnceCallback>(static_cast<WGPUOnceCallback*>(
+            WGPUCallbackBase<BaseCallback>::FromUserdata(handle)));
+    return std::move(*callback).Run(std::forward<Args>(args)...);
+  }
+};
+
+template <typename R, typename... Args>
+class WGPURepeatingCallback<R(Args...)>
+    : public WGPUCallbackBase<base::RepeatingCallback<R(Args...)>> {
+  using BaseCallback = base::RepeatingCallback<R(Args...)>;
+
+ public:
+  using WGPUCallbackBase<BaseCallback>::WGPUCallbackBase;
+
+  typename WGPUCallbackBase<BaseCallback>::UnboundCallbackFunction
+  UnboundCallback() {
+    return CallUnboundRepeatingCallback;
+  }
+
+ private:
+  static R CallUnboundRepeatingCallback(Args... args, void* handle) {
+    return static_cast<WGPURepeatingCallback*>(
+               WGPUCallbackBase<BaseCallback>::FromUserdata(handle))
+        ->Run(std::forward<Args>(args)...);
+  }
+};
+
+template <typename FunctionType, typename... BoundParameters>
+auto BindWGPUOnceCallback(FunctionType&& function,
+                          BoundParameters&&... bound_parameters) {
+  static constexpr bool is_method =
+      base::internal::MakeFunctorTraits<FunctionType>::is_method;
+  static constexpr bool is_weak_method =
+      base::internal::IsWeakMethod<is_method, BoundParameters...>();
+  static_assert(!is_weak_method,
+                "BindWGPUOnceCallback cannot be used with weak methods");
+
+  auto cb = base::BindOnce(std::forward<FunctionType>(function),
+                           std::forward<BoundParameters>(bound_parameters)...);
+  return new WGPUOnceCallback<typename decltype(cb)::RunType>(std::move(cb));
+}
+
+template <typename FunctionType, typename... BoundParameters>
+auto BindWGPURepeatingCallback(FunctionType&& function,
+                               BoundParameters&&... bound_parameters) {
+  auto cb =
+      base::BindRepeating(std::forward<FunctionType>(function),
+                          std::forward<BoundParameters>(bound_parameters)...);
+  return std::make_unique<
+      WGPURepeatingCallback<typename decltype(cb)::RunType>>(std::move(cb));
+}
+
+}  // namespace gpu::webgpu
+
+#endif  // GPU_WEBGPU_CALLBACK_H_
diff --git a/infra/archive_config/mac-tagged.json b/infra/archive_config/mac-tagged.json
deleted file mode 100644
index a5a9eb9..0000000
--- a/infra/archive_config/mac-tagged.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
-	"archive_datas": [
-        {
-            "dirs": ["Chromium.app"],
-            "gcs_bucket": "chromium-browser-versioned",
-            "gcs_path": "experimental/{%chromium_version%}/{%builder_name%}/chrome-mac.zip",
-            "archive_type": "ARCHIVE_TYPE_ZIP"
-        },
-        {
-            "files": [
-                "chromedriver"
-            ],
-            "gcs_bucket": "chromium-browser-versioned",
-            "gcs_path": "experimental/{%chromium_version%}/{%builder_name%}/chromedriver_mac64.zip",
-            "archive_type": "ARCHIVE_TYPE_ZIP"
-        }
-    ]
-}
\ No newline at end of file
diff --git a/infra/archive_config/win-tagged.json b/infra/archive_config/win-tagged.json
deleted file mode 100644
index 3d6a435..0000000
--- a/infra/archive_config/win-tagged.json
+++ /dev/null
@@ -1,46 +0,0 @@
-{
-	"archive_datas": [
-        {
-            "files": [
-                "chrome.exe",
-                "chrome.dll",
-                "chrome_100_percent.pak",
-                "chrome_200_percent.pak",
-                "chrome_elf.dll",
-                "chrome_proxy.exe",
-                "chrome_pwa_launcher.exe",
-                "D3DCompiler_47.dll",
-                "elevation_service.exe",
-                "eventlog_provider.dll",
-                "First Run",
-                "icudtl.dat",
-                "interactive_ui_tests.exe",
-                "libEGL.dll",
-                "libGLESv2.dll",
-                "MEIPreload\\manifest.json",
-                "MEIPreload\\preloaded_data.pb",
-                "mojo_core.dll",
-                "nacl_irt_x86_64.nexe",
-                "notification_helper.exe",
-                "resources.pak",
-                "v8_context_snapshot.bin",
-                "vk_swiftshader.dll",
-                "vk_swiftshader_icd.json",
-                "vulkan-1.dll"
-            ],
-            "file_globs": ["*.manifest"],
-            "dirs": ["locales"],
-            "gcs_bucket": "chromium-browser-versioned",
-            "gcs_path": "experimental/{%chromium_version%}/{%builder_name%}/chrome-win.zip",
-            "archive_type": "ARCHIVE_TYPE_ZIP"
-        },
-        {
-            "files": [
-                "chromedriver.exe"
-            ],
-            "gcs_bucket": "chromium-browser-versioned",
-            "gcs_path": "experimental/{%chromium_version%}/{%builder_name%}/chromedriver_win32.zip",
-            "archive_type": "ARCHIVE_TYPE_ZIP"
-        }
-    ]
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/ci/linux-archive-tagged/properties.json b/infra/config/generated/builders/ci/linux-archive-tagged/properties.json
deleted file mode 100644
index dc7d3e2..0000000
--- a/infra/config/generated/builders/ci/linux-archive-tagged/properties.json
+++ /dev/null
@@ -1,62 +0,0 @@
-{
-  "$build/archive": {
-    "archive_datas": [
-      {
-        "archive_type": "ARCHIVE_TYPE_ZIP",
-        "dirs": [
-          "ClearKeyCdm",
-          "locales",
-          "resources"
-        ],
-        "files": [
-          "chrome",
-          "chrome-wrapper",
-          "chrome_100_percent.pak",
-          "chrome_200_percent.pak",
-          "chrome_crashpad_handler",
-          "chrome_sandbox",
-          "icudtl.dat",
-          "libEGL.so",
-          "libGLESv2.so",
-          "libvk_swiftshader.so",
-          "libvulkan.so.1",
-          "MEIPreload/manifest.json",
-          "MEIPreload/preloaded_data.pb",
-          "nacl_helper",
-          "nacl_helper_bootstrap",
-          "nacl_irt_x86_64.nexe",
-          "product_logo_48.png",
-          "resources.pak",
-          "v8_context_snapshot.bin",
-          "vk_swiftshader_icd.json",
-          "xdg-mime",
-          "xdg-settings"
-        ],
-        "gcs_bucket": "chromium-browser-versioned",
-        "gcs_path": "experimental/Linux_x64_Tagged/{%chromium_version%}/chrome-linux.zip"
-      },
-      {
-        "archive_type": "ARCHIVE_TYPE_ZIP",
-        "files": [
-          "chromedriver"
-        ],
-        "gcs_bucket": "chromium-browser-versioned",
-        "gcs_path": "experimental/Linux_x64_Tagged/{%chromium_version%}/chromedriver_linux64.zip"
-      }
-    ]
-  },
-  "$build/reclient": {
-    "instance": "rbe-chromium-trusted",
-    "jobs": 250,
-    "metrics_project": "chromium-reclient-metrics"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/ci/mac-archive-tagged/properties.json b/infra/config/generated/builders/ci/mac-archive-tagged/properties.json
deleted file mode 100644
index a066e03..0000000
--- a/infra/config/generated/builders/ci/mac-archive-tagged/properties.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-  "$build/archive": {
-    "source_side_spec_path": [
-      "src",
-      "infra",
-      "archive_config",
-      "mac-tagged.json"
-    ]
-  },
-  "$build/goma": {
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org",
-    "use_luci_auth": true
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/ci/mac-arm64-archive-tagged/properties.json b/infra/config/generated/builders/ci/mac-arm64-archive-tagged/properties.json
deleted file mode 100644
index a066e03..0000000
--- a/infra/config/generated/builders/ci/mac-arm64-archive-tagged/properties.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-  "$build/archive": {
-    "source_side_spec_path": [
-      "src",
-      "infra",
-      "archive_config",
-      "mac-tagged.json"
-    ]
-  },
-  "$build/goma": {
-    "rpc_extra_params": "?prod",
-    "server_host": "goma.chromium.org",
-    "use_luci_auth": true
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/ci/win-archive-tagged/properties.json b/infra/config/generated/builders/ci/win-archive-tagged/properties.json
deleted file mode 100644
index 42dc664d..0000000
--- a/infra/config/generated/builders/ci/win-archive-tagged/properties.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-  "$build/archive": {
-    "source_side_spec_path": [
-      "src",
-      "infra",
-      "archive_config",
-      "win-tagged.json"
-    ]
-  },
-  "$build/reclient": {
-    "instance": "rbe-chromium-trusted",
-    "jobs": 250,
-    "metrics_project": "chromium-reclient-metrics"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/ci/win32-archive-tagged/properties.json b/infra/config/generated/builders/ci/win32-archive-tagged/properties.json
deleted file mode 100644
index 42dc664d..0000000
--- a/infra/config/generated/builders/ci/win32-archive-tagged/properties.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-  "$build/archive": {
-    "source_side_spec_path": [
-      "src",
-      "infra",
-      "archive_config",
-      "win-tagged.json"
-    ]
-  },
-  "$build/reclient": {
-    "instance": "rbe-chromium-trusted",
-    "jobs": 250,
-    "metrics_project": "chromium-reclient-metrics"
-  },
-  "$recipe_engine/resultdb/test_presentation": {
-    "column_keys": [],
-    "grouping_keys": [
-      "status",
-      "v.test_suite"
-    ]
-  },
-  "builder_group": "chromium",
-  "recipe": "chromium"
-}
\ No newline at end of file
diff --git a/infra/config/generated/builders/try/win_optional_gpu_tests_rel/properties.json b/infra/config/generated/builders/try/win_optional_gpu_tests_rel/properties.json
index d4fde02b..bb8784e 100644
--- a/infra/config/generated/builders/try/win_optional_gpu_tests_rel/properties.json
+++ b/infra/config/generated/builders/try/win_optional_gpu_tests_rel/properties.json
@@ -1,4 +1,46 @@
 {
+  "$build/chromium_tests_builder_config": {
+    "builder_config": {
+      "builder_db": {
+        "entries": [
+          {
+            "builder_id": {
+              "bucket": "try",
+              "builder": "win_optional_gpu_tests_rel",
+              "project": "chromium"
+            },
+            "builder_spec": {
+              "build_gs_bucket": "chromium-gpu-fyi-archive",
+              "builder_group": "tryserver.chromium.win",
+              "execution_mode": "COMPILE_AND_TEST",
+              "legacy_chromium_config": {
+                "apply_configs": [
+                  "mb"
+                ],
+                "build_config": "Release",
+                "config": "chromium",
+                "target_bits": 64
+              },
+              "legacy_gclient_config": {
+                "apply_configs": [
+                  "angle_internal"
+                ],
+                "config": "chromium"
+              }
+            }
+          }
+        ]
+      },
+      "builder_ids": [
+        {
+          "bucket": "try",
+          "builder": "win_optional_gpu_tests_rel",
+          "project": "chromium"
+        }
+      ],
+      "retry_failed_shards": false
+    }
+  },
   "$build/goma": {
     "enable_ats": false,
     "rpc_extra_params": "?prod",
diff --git a/infra/config/generated/luci/cr-buildbucket.cfg b/infra/config/generated/luci/cr-buildbucket.cfg
index ab6342d3..a317d11 100644
--- a/infra/config/generated/luci/cr-buildbucket.cfg
+++ b/infra/config/generated/luci/cr-buildbucket.cfg
@@ -32991,84 +32991,6 @@
       }
     }
     builders {
-      name: "linux-archive-tagged"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:linux-archive-tagged"
-      dimensions: "cores:32"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Ubuntu-18.04"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/ci/linux-archive-tagged/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 25200
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "ci_test_results"
-          test_results {}
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "gpu_ci_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
-            }
-          }
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "blink_web_tests_ci_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://[^/]*blink_web_tests/.+"
-            }
-          }
-        }
-        history_options {
-          use_invocation_timestamp: true
-        }
-      }
-    }
-    builders {
       name: "linux-ash-chromium-builder-fyi-rel"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -36949,84 +36871,6 @@
       }
     }
     builders {
-      name: "mac-archive-tagged"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:mac-archive-tagged"
-      dimensions: "cores:12"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/ci/mac-archive-tagged/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 25200
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "ci_test_results"
-          test_results {}
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "gpu_ci_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
-            }
-          }
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "blink_web_tests_ci_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://[^/]*blink_web_tests/.+"
-            }
-          }
-        }
-        history_options {
-          use_invocation_timestamp: true
-        }
-      }
-    }
-    builders {
       name: "mac-arm64-archive-dbg"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:mac-arm64-archive-dbg"
@@ -37189,84 +37033,6 @@
       }
     }
     builders {
-      name: "mac-arm64-archive-tagged"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:mac-arm64-archive-tagged"
-      dimensions: "cores:12"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-11"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/ci/mac-arm64-archive-tagged/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 25200
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "ci_test_results"
-          test_results {}
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "gpu_ci_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
-            }
-          }
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "blink_web_tests_ci_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://[^/]*blink_web_tests/.+"
-            }
-          }
-        }
-        history_options {
-          use_invocation_timestamp: true
-        }
-      }
-    }
-    builders {
       name: "mac-arm64-on-arm64-rel"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:mac-arm64-on-arm64-rel"
@@ -40135,84 +39901,6 @@
       }
     }
     builders {
-      name: "win-archive-tagged"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:win-archive-tagged"
-      dimensions: "cores:32"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/ci/win-archive-tagged/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 25200
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "ci_test_results"
-          test_results {}
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "gpu_ci_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
-            }
-          }
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "blink_web_tests_ci_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://[^/]*blink_web_tests/.+"
-            }
-          }
-        }
-        history_options {
-          use_invocation_timestamp: true
-        }
-      }
-    }
-    builders {
       name: "win-asan"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builderless:1"
@@ -42440,84 +42128,6 @@
       }
     }
     builders {
-      name: "win32-archive-tagged"
-      swarming_host: "chromium-swarm.appspot.com"
-      dimensions: "builder:win32-archive-tagged"
-      dimensions: "cores:32"
-      dimensions: "cpu:x86-64"
-      dimensions: "os:Windows-10"
-      dimensions: "pool:luci.chromium.ci"
-      exe {
-        cipd_package: "infra/chromium/bootstrapper/${platform}"
-        cipd_version: "latest"
-        cmd: "bootstrapper"
-      }
-      properties:
-        '{'
-        '  "$bootstrap/exe": {'
-        '    "exe": {'
-        '      "cipd_package": "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build",'
-        '      "cipd_version": "refs/heads/main",'
-        '      "cmd": ['
-        '        "luciexe"'
-        '      ]'
-        '    }'
-        '  },'
-        '  "$bootstrap/properties": {'
-        '    "properties_file": "infra/config/generated/builders/ci/win32-archive-tagged/properties.json",'
-        '    "top_level_project": {'
-        '      "ref": "refs/heads/main",'
-        '      "repo": {'
-        '        "host": "chromium.googlesource.com",'
-        '        "project": "chromium/src"'
-        '      }'
-        '    }'
-        '  },'
-        '  "builder_group": "chromium",'
-        '  "led_builder_is_bootstrapped": true,'
-        '  "recipe": "chromium"'
-        '}'
-      execution_timeout_secs: 25200
-      build_numbers: YES
-      service_account: "chromium-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
-      experiments {
-        key: "luci.recipes.use_python3"
-        value: 100
-      }
-      resultdb {
-        enable: true
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "ci_test_results"
-          test_results {}
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "gpu_ci_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://chrome/test:telemetry_gpu_integration_test[^/]*/.+"
-            }
-          }
-        }
-        bq_exports {
-          project: "chrome-luci-data"
-          dataset: "chromium"
-          table: "blink_web_tests_ci_test_results"
-          test_results {
-            predicate {
-              test_id_regexp: "ninja://[^/]*blink_web_tests/.+"
-            }
-          }
-        }
-        history_options {
-          use_invocation_timestamp: true
-        }
-      }
-    }
-    builders {
       name: "win32-arm64-rel"
       swarming_host: "chromium-swarm.appspot.com"
       dimensions: "builder:win32-arm64-rel"
@@ -76480,6 +76090,10 @@
         key: "luci.recipes.use_python3"
         value: 100
       }
+      experiments {
+        key: "remove_src_checkout_experiment"
+        value: 10
+      }
       resultdb {
         enable: true
         bq_exports {
diff --git a/infra/config/generated/luci/luci-milo.cfg b/infra/config/generated/luci/luci-milo.cfg
index 317aba99..d7d654d 100644
--- a/infra/config/generated/luci/luci-milo.cfg
+++ b/infra/config/generated/luci/luci-milo.cfg
@@ -51,11 +51,6 @@
     short_name: "off"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/linux-archive-tagged"
-    category: "chromium|linux"
-    short_name: "tag"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci/mac-archive-dbg"
     category: "chromium|mac"
     short_name: "dbg"
@@ -71,11 +66,6 @@
     short_name: "off"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/mac-archive-tagged"
-    category: "chromium|mac"
-    short_name: "tag"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci/mac-arm64-archive-dbg"
     category: "chromium|mac|arm"
     short_name: "dbg"
@@ -86,11 +76,6 @@
     short_name: "rel"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/mac-arm64-archive-tagged"
-    category: "chromium|mac|arm"
-    short_name: "tag"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci/win32-archive-dbg"
     category: "chromium|win|dbg"
     short_name: "32"
@@ -121,16 +106,6 @@
     short_name: "64"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/win32-archive-tagged"
-    category: "chromium|win|tag"
-    short_name: "32"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.ci/win-archive-tagged"
-    category: "chromium|win|tag"
-    short_name: "64"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci/Win Builder"
     category: "chromium.win|release|builder"
     short_name: "32"
@@ -2772,11 +2747,6 @@
     short_name: "off"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/linux-archive-tagged"
-    category: "linux"
-    short_name: "tag"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci/mac-archive-dbg"
     category: "mac"
     short_name: "dbg"
@@ -2792,11 +2762,6 @@
     short_name: "off"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/mac-archive-tagged"
-    category: "mac"
-    short_name: "tag"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci/mac-arm64-archive-dbg"
     category: "mac|arm"
     short_name: "dbg"
@@ -2807,11 +2772,6 @@
     short_name: "rel"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/mac-arm64-archive-tagged"
-    category: "mac|arm"
-    short_name: "tag"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci/win32-archive-dbg"
     category: "win|dbg"
     short_name: "32"
@@ -2841,16 +2801,6 @@
     category: "win|rel"
     short_name: "64"
   }
-  builders {
-    name: "buildbucket/luci.chromium.ci/win32-archive-tagged"
-    category: "win|tag"
-    short_name: "32"
-  }
-  builders {
-    name: "buildbucket/luci.chromium.ci/win-archive-tagged"
-    category: "win|tag"
-    short_name: "64"
-  }
   header {
     oncalls {
       name: "Chromium"
diff --git a/infra/config/generated/luci/luci-scheduler.cfg b/infra/config/generated/luci/luci-scheduler.cfg
index 38af5dab..f2795f4 100644
--- a/infra/config/generated/luci/luci-scheduler.cfg
+++ b/infra/config/generated/luci/luci-scheduler.cfg
@@ -5541,17 +5541,6 @@
   }
 }
 job {
-  id: "linux-archive-tagged"
-  realm: "ci"
-  schedule: "triggered"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "ci"
-    builder: "linux-archive-tagged"
-  }
-}
-job {
   id: "linux-ash-chromium-builder-fyi-rel"
   realm: "ci"
   acl_sets: "ci"
@@ -6076,17 +6065,6 @@
   }
 }
 job {
-  id: "mac-archive-tagged"
-  realm: "ci"
-  schedule: "triggered"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "ci"
-    builder: "mac-archive-tagged"
-  }
-}
-job {
   id: "mac-arm64-archive-dbg"
   realm: "ci"
   acl_sets: "ci"
@@ -6107,17 +6085,6 @@
   }
 }
 job {
-  id: "mac-arm64-archive-tagged"
-  realm: "ci"
-  schedule: "triggered"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "ci"
-    builder: "mac-arm64-archive-tagged"
-  }
-}
-job {
   id: "mac-arm64-on-arm64-rel"
   realm: "ci"
   acl_sets: "ci"
@@ -6554,17 +6521,6 @@
   }
 }
 job {
-  id: "win-archive-tagged"
-  realm: "ci"
-  schedule: "triggered"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "ci"
-    builder: "win-archive-tagged"
-  }
-}
-job {
   id: "win-asan"
   realm: "ci"
   acl_sets: "ci"
@@ -6893,17 +6849,6 @@
   }
 }
 job {
-  id: "win32-archive-tagged"
-  realm: "ci"
-  schedule: "triggered"
-  acl_sets: "ci"
-  buildbucket {
-    server: "cr-buildbucket.appspot.com"
-    bucket: "ci"
-    builder: "win32-archive-tagged"
-  }
-}
-job {
   id: "win32-arm64-rel"
   realm: "ci"
   acl_sets: "ci"
diff --git a/infra/config/generated/sheriff-rotations/OWNERS b/infra/config/generated/sheriff-rotations/OWNERS
index 9ca933b..8785f6b 100644
--- a/infra/config/generated/sheriff-rotations/OWNERS
+++ b/infra/config/generated/sheriff-rotations/OWNERS
@@ -5,3 +5,4 @@
 per-file chromium.clang.txt=file://infra/config/groups/sheriff-rotations/CHROMIUM_CLANG_OWNERS
 per-file chromium.gpu.txt=file://infra/config/groups/sheriff-rotations/CHROMIUM_GPU_OWNERS
 per-file chromium.txt=file://infra/config/groups/sheriff-rotations/CHROMIUM_OWNERS
+per-file fuchsia.txt=file://infra/config/groups/sheriff-rotations/FUCHSIA_OWNERS
diff --git a/infra/config/subprojects/chromium/ci/chromium.android.star b/infra/config/subprojects/chromium/ci/chromium.android.star
index d6dd759f..207f2ca 100644
--- a/infra/config/subprojects/chromium/ci/chromium.android.star
+++ b/infra/config/subprojects/chromium/ci/chromium.android.star
@@ -710,6 +710,7 @@
 
 ci.builder(
     name = "android-cronet-x86-dbg",
+    branch_selector = branches.STANDARD_MILESTONE,
     console_view_entry = consoles.console_view_entry(
         category = "cronet|x86",
         short_name = "dbg",
@@ -764,6 +765,7 @@
 
 ci.thin_tester(
     name = "android-cronet-x86-dbg-10-tests",
+    branch_selector = branches.STANDARD_MILESTONE,
     console_view_entry = consoles.console_view_entry(
         category = "cronet|test",
         short_name = "10",
diff --git a/infra/config/subprojects/chromium/ci/chromium.star b/infra/config/subprojects/chromium/ci/chromium.star
index 4d4748a..d7ded7e 100644
--- a/infra/config/subprojects/chromium/ci/chromium.star
+++ b/infra/config/subprojects/chromium/ci/chromium.star
@@ -283,68 +283,6 @@
 )
 
 ci.builder(
-    name = "linux-archive-tagged",
-    builderless = False,
-    console_view_entry = consoles.console_view_entry(
-        category = "linux",
-        short_name = "tag",
-    ),
-    cores = 32,
-    execution_timeout = 7 * time.hour,
-    properties = {
-        # The format of these properties is defined at archive/properties.proto
-        "$build/archive": {
-            "archive_datas": [
-                {
-                    "files": [
-                        "chrome",
-                        "chrome-wrapper",
-                        "chrome_100_percent.pak",
-                        "chrome_200_percent.pak",
-                        "chrome_crashpad_handler",
-                        "chrome_sandbox",
-                        "icudtl.dat",
-                        "libEGL.so",
-                        "libGLESv2.so",
-                        "libvk_swiftshader.so",
-                        "libvulkan.so.1",
-                        "MEIPreload/manifest.json",
-                        "MEIPreload/preloaded_data.pb",
-                        "nacl_helper",
-                        "nacl_helper_bootstrap",
-                        "nacl_irt_x86_64.nexe",
-                        "product_logo_48.png",
-                        "resources.pak",
-                        "v8_context_snapshot.bin",
-                        "vk_swiftshader_icd.json",
-                        "xdg-mime",
-                        "xdg-settings",
-                    ],
-                    "dirs": ["ClearKeyCdm", "locales", "resources"],
-                    "gcs_bucket": "chromium-browser-versioned",
-                    "gcs_path": "experimental/Linux_x64_Tagged/{%chromium_version%}/chrome-linux.zip",
-                    "archive_type": "ARCHIVE_TYPE_ZIP",
-                },
-                {
-                    "files": [
-                        "chromedriver",
-                    ],
-                    "gcs_bucket": "chromium-browser-versioned",
-                    "gcs_path": "experimental/Linux_x64_Tagged/{%chromium_version%}/chromedriver_linux64.zip",
-                    "archive_type": "ARCHIVE_TYPE_ZIP",
-                },
-            ],
-        },
-    },
-    schedule = "triggered",
-    sheriff_rotations = args.ignore_default(None),
-    triggered_by = [],
-    goma_backend = None,
-    reclient_jobs = rbe_jobs.DEFAULT,
-    reclient_instance = rbe_instance.DEFAULT,
-)
-
-ci.builder(
     name = "linux-official",
     branch_selector = branches.STANDARD_MILESTONE,
     builderless = False,
@@ -395,31 +333,6 @@
 )
 
 ci.builder(
-    name = "mac-archive-tagged",
-    console_view_entry = consoles.console_view_entry(
-        category = "mac",
-        short_name = "tag",
-    ),
-    cores = 12,
-    execution_timeout = 7 * time.hour,
-    os = os.MAC_DEFAULT,
-    properties = {
-        # The format of these properties is defined at archive/properties.proto
-        "$build/archive": {
-            "source_side_spec_path": [
-                "src",
-                "infra",
-                "archive_config",
-                "mac-tagged.json",
-            ],
-        },
-    },
-    schedule = "triggered",
-    sheriff_rotations = args.ignore_default(None),
-    triggered_by = [],
-)
-
-ci.builder(
     name = "mac-arm64-archive-dbg",
     builder_spec = builder_config.builder_spec(
         gclient_config = builder_config.gclient_config(
@@ -482,31 +395,6 @@
 )
 
 ci.builder(
-    name = "mac-arm64-archive-tagged",
-    console_view_entry = consoles.console_view_entry(
-        category = "mac|arm",
-        short_name = "tag",
-    ),
-    cores = 12,
-    execution_timeout = 7 * time.hour,
-    os = os.MAC_DEFAULT,
-    properties = {
-        # The format of these properties is defined at archive/properties.proto
-        "$build/archive": {
-            "source_side_spec_path": [
-                "src",
-                "infra",
-                "archive_config",
-                "mac-tagged.json",
-            ],
-        },
-    },
-    schedule = "triggered",
-    sheriff_rotations = args.ignore_default(None),
-    triggered_by = [],
-)
-
-ci.builder(
     name = "mac-official",
     branch_selector = branches.DESKTOP_EXTENDED_STABLE_MILESTONE,
     builder_spec = builder_config.builder_spec(
@@ -589,34 +477,6 @@
 )
 
 ci.builder(
-    name = "win-archive-tagged",
-    console_view_entry = consoles.console_view_entry(
-        category = "win|tag",
-        short_name = "64",
-    ),
-    cores = 32,
-    execution_timeout = 7 * time.hour,
-    os = os.WINDOWS_DEFAULT,
-    properties = {
-        # The format of these properties is defined at archive/properties.proto
-        "$build/archive": {
-            "source_side_spec_path": [
-                "src",
-                "infra",
-                "archive_config",
-                "win-tagged.json",
-            ],
-        },
-    },
-    schedule = "triggered",
-    sheriff_rotations = args.ignore_default(None),
-    triggered_by = [],
-    goma_backend = None,
-    reclient_jobs = rbe_jobs.DEFAULT,
-    reclient_instance = rbe_instance.DEFAULT,
-)
-
-ci.builder(
     name = "win-official",
     branch_selector = branches.DESKTOP_EXTENDED_STABLE_MILESTONE,
     builder_spec = builder_config.builder_spec(
@@ -698,34 +558,6 @@
 )
 
 ci.builder(
-    name = "win32-archive-tagged",
-    console_view_entry = consoles.console_view_entry(
-        category = "win|tag",
-        short_name = "32",
-    ),
-    cores = 32,
-    execution_timeout = 7 * time.hour,
-    os = os.WINDOWS_DEFAULT,
-    properties = {
-        # The format of these properties is defined at archive/properties.proto
-        "$build/archive": {
-            "source_side_spec_path": [
-                "src",
-                "infra",
-                "archive_config",
-                "win-tagged.json",
-            ],
-        },
-    },
-    schedule = "triggered",
-    sheriff_rotations = args.ignore_default(None),
-    triggered_by = [],
-    goma_backend = None,
-    reclient_jobs = rbe_jobs.DEFAULT,
-    reclient_instance = rbe_instance.DEFAULT,
-)
-
-ci.builder(
     name = "win32-official",
     branch_selector = branches.DESKTOP_EXTENDED_STABLE_MILESTONE,
     console_view_entry = consoles.console_view_entry(
diff --git a/infra/config/subprojects/chromium/try/tryserver.chromium.win.star b/infra/config/subprojects/chromium/try/tryserver.chromium.win.star
index 53680c7b..01062bed 100644
--- a/infra/config/subprojects/chromium/try/tryserver.chromium.win.star
+++ b/infra/config/subprojects/chromium/try/tryserver.chromium.win.star
@@ -174,6 +174,9 @@
     coverage_test_types = ["unit", "overall"],
     main_list_view = "try",
     tryjob = try_.job(),
+    experiments = {
+        "remove_src_checkout_experiment": 10,
+    },
 )
 
 try_.compilator_builder(
@@ -214,6 +217,26 @@
 
 try_.gpu.optional_tests_builder(
     name = "win_optional_gpu_tests_rel",
+    builder_spec = builder_config.builder_spec(
+        gclient_config = builder_config.gclient_config(
+            config = "chromium",
+            apply_configs = [
+                "angle_internal",
+            ],
+        ),
+        chromium_config = builder_config.chromium_config(
+            config = "chromium",
+            apply_configs = [
+                "mb",
+            ],
+            build_config = builder_config.build_config.RELEASE,
+            target_bits = 64,
+        ),
+        build_gs_bucket = "chromium-gpu-fyi-archive",
+    ),
+    try_settings = builder_config.try_settings(
+        retry_failed_shards = False,
+    ),
     branch_selector = branches.DESKTOP_EXTENDED_STABLE_MILESTONE,
     builderless = True,
     main_list_view = "try",
diff --git a/ios/chrome/browser/ui/follow/followed_web_channel.h b/ios/chrome/browser/ui/follow/followed_web_channel.h
index 612e578..f28bf20 100644
--- a/ios/chrome/browser/ui/follow/followed_web_channel.h
+++ b/ios/chrome/browser/ui/follow/followed_web_channel.h
@@ -17,9 +17,16 @@
 // Title of the web channel.
 @property(nonatomic, copy) NSString* title;
 
+// TODO(crbug.com/1296745): Deprecated.
 // URL of the web channel.
 @property(nonatomic, strong) CrURL* channelURL;
 
+// URL of the web channel web page.
+@property(nonatomic, strong) CrURL* webPageURL;
+
+// URL of the web channel rss.
+@property(nonatomic, strong) CrURL* rssURL;
+
 // URL of the favicon.
 @property(nonatomic, strong) CrURL* faviconURL;
 
@@ -32,6 +39,7 @@
 // Used to request to refollow this web channel, if it has been unfollowed.
 @property(nonatomic, copy) FollowRequestBlock refollowRequestBlock;
 
+// TODO(crbug.com/1296745): Deprecated.
 // Designated initializer with all the properties.
 - (instancetype)initWithTitle:(NSString*)title
                    channelURL:(CrURL*)channelURL
diff --git a/ios/chrome/browser/ui/follow/followed_web_channel.mm b/ios/chrome/browser/ui/follow/followed_web_channel.mm
index 0c17bc4b..431b5d1f 100644
--- a/ios/chrome/browser/ui/follow/followed_web_channel.mm
+++ b/ios/chrome/browser/ui/follow/followed_web_channel.mm
@@ -13,6 +13,7 @@
 
 @implementation FollowedWebChannel
 
+// TODO(crbug.com/1296745): Deprecated.
 - (instancetype)initWithTitle:(NSString*)title
                    channelURL:(CrURL*)channelURL
                    faviconURL:(CrURL*)faviconURL
@@ -35,9 +36,9 @@
 
 - (BOOL)isEqualToFollowedWebChannel:(FollowedWebChannel*)channel {
   return channel && [self.title isEqualToString:channel.title] &&
-         self.channelURL.gurl == channel.channelURL.gurl &&
-         self.faviconURL.gurl == channel.faviconURL.gurl &&
-         self.available == channel.available;
+         self.webPageURL.gurl == channel.webPageURL.gurl &&
+         self.rssURL.gurl == channel.rssURL.gurl &&
+         self.faviconURL.gurl == channel.faviconURL.gurl;
 }
 
 - (BOOL)isEqual:(id)object {
@@ -52,9 +53,9 @@
 
 - (NSUInteger)hash {
   return [self.title hash] ^
-         [base::SysUTF8ToNSString(self.channelURL.gurl.spec()) hash] ^
-         [base::SysUTF8ToNSString(self.faviconURL.gurl.spec()) hash] ^
-         self.available;
+         [base::SysUTF8ToNSString(self.webPageURL.gurl.spec()) hash] ^
+         [base::SysUTF8ToNSString(self.rssURL.gurl.spec()) hash] ^
+         [base::SysUTF8ToNSString(self.faviconURL.gurl.spec()) hash];
 }
 
 @end
diff --git a/ios/chrome/browser/ui/ntp/feed_management/followed_web_channel_item.mm b/ios/chrome/browser/ui/ntp/feed_management/followed_web_channel_item.mm
index 145eb2a..72926c55 100644
--- a/ios/chrome/browser/ui/ntp/feed_management/followed_web_channel_item.mm
+++ b/ios/chrome/browser/ui/ntp/feed_management/followed_web_channel_item.mm
@@ -32,7 +32,8 @@
 }
 
 - (CrURL*)URL {
-  return _followedWebChannel.channelURL;
+  return (_followedWebChannel.rssURL) ? _followedWebChannel.rssURL
+                                      : _followedWebChannel.webPageURL;
 }
 
 - (NSString*)thirdRowText {
diff --git a/ios/chrome/browser/ui/ntp/feed_metrics_recorder.h b/ios/chrome/browser/ui/ntp/feed_metrics_recorder.h
index 6893d4a..ca42599 100644
--- a/ios/chrome/browser/ui/ntp/feed_metrics_recorder.h
+++ b/ios/chrome/browser/ui/ntp/feed_metrics_recorder.h
@@ -25,6 +25,10 @@
   kMaxValue = 6,
 };
 
+namespace base {
+class Time;
+}
+
 // Records different metrics for the NTP feeds.
 @interface FeedMetricsRecorder : NSObject
 
@@ -175,6 +179,17 @@
 // Records that the feed is about to be refreshed.
 - (void)recordFeedWillRefresh;
 
+// Records the state of the Feed setting based on the |enterprisePolicy| being
+// enabled, |feedVisible|, the user being |signedIn|, user having |waaEnabled|
+// and |spywEnabled|, and the |lastRefreshTime| for the Feed.
+- (void)recordFeedSettingsOnStartForEnterprisePolicy:(BOOL)enterprisePolicy
+                                         feedVisible:(BOOL)feedVisible
+                                            signedIn:(BOOL)signedIn
+                                          waaEnabled:(BOOL)waaEnabled
+                                         spywEnabled:(BOOL)spywEnabled
+                                     lastRefreshTime:
+                                         (base::Time)lastRefreshTime;
+
 // The currently selected feed type in the NTP.
 @property(nonatomic, assign) FeedType selectedFeedType;
 
diff --git a/ios/chrome/browser/ui/ntp/feed_metrics_recorder.mm b/ios/chrome/browser/ui/ntp/feed_metrics_recorder.mm
index 11ef7fb2..3a04790 100644
--- a/ios/chrome/browser/ui/ntp/feed_metrics_recorder.mm
+++ b/ios/chrome/browser/ui/ntp/feed_metrics_recorder.mm
@@ -10,6 +10,7 @@
 #import "base/metrics/user_metrics.h"
 #import "base/metrics/user_metrics_action.h"
 #include "base/time/time.h"
+#import "base/time/time.h"
 #import "components/feed/core/v2/public/common_enums.h"
 #import "ios/chrome/browser/ui/content_suggestions/ntp_home_metrics.h"
 
@@ -50,6 +51,34 @@
   kMaxValue = kCannotLoadMoreNoNextPageToken,
 };
 
+// Values for the UMA ContentSuggestions.Feed.UserSettingsOnStart
+// histogram. These values are persisted to logs. Entries should not be
+// renumbered and numeric values should never be reused. This must be kept
+// in sync with FeedUserSettingsOnStart in enums.xml.
+// Reports last known state of user settings which affect Feed content.
+// This includes WAA (whether activity is recorded), and DP (whether
+// Discover personalization is enabled).
+enum class UserSettingsOnStart {
+  // The Feed is disabled by enterprise policy.
+  kFeedNotEnabledByPolicy = 0,
+  // The Feed is enabled by enterprise policy, but the user has hidden and
+  // disabled the Feed, so other user settings beyond sign-in status are not
+  // available.
+  kFeedNotVisibleSignedOut = 1,
+  kFeedNotVisibleSignedIn = 2,
+  // The Feed is enabled, the user is not signed in.
+  kSignedOut = 3,
+  // The Feed is enabled, the user is signed in, and setting states are known.
+  kSignedInWaaOnDpOn = 4,
+  kSignedInWaaOnDpOff = 5,
+  kSignedInWaaOffDpOn = 6,
+  kSignedInWaaOffDpOff = 7,
+  // The Feed is enabled, but there is no recent Feed data, so user settings
+  // state is unknown.
+  kSignedInNoRecentData = 8,
+  kMaxValue = kSignedInNoRecentData,
+};
+
 namespace {
 // User action names for the device orientation having changed.
 const char kDiscoverFeedHistogramDeviceOrientationChangedToPortrait[] =
@@ -208,6 +237,10 @@
 const char kDiscoverFeedBrokenNTPHierarchy[] =
     "ContentSuggestions.Feed.BrokenNTPHierarchy";
 
+// Histogram name for the Feed settings when the App is being start.
+const char kFeedUserSettingsOnStart[] =
+    "ContentSuggestions.Feed.UserSettingsOnStart";
+
 // Minimum scrolling amount to record a FeedEngagementType::kFeedEngaged due to
 // scrolling.
 const int kMinScrollThreshold = 160;
@@ -217,6 +250,9 @@
 
 // The max amount of cards in the Discover Feed.
 const int kMaxCardsInFeed = 50;
+
+// If cached user setting info is older than this, it will not be reported.
+constexpr base::TimeDelta kUserSettingsMaxAge = base::Days(14);
 }  // namespace
 
 @interface FeedMetricsRecorder ()
@@ -556,8 +592,58 @@
   base::RecordAction(base::UserMetricsAction(kFeedWillRefresh));
 }
 
+- (void)recordFeedSettingsOnStartForEnterprisePolicy:(BOOL)enterprisePolicy
+                                         feedVisible:(BOOL)feedVisible
+                                            signedIn:(BOOL)signedIn
+                                          waaEnabled:(BOOL)waaEnabled
+                                         spywEnabled:(BOOL)spywEnabled
+                                     lastRefreshTime:
+                                         (base::Time)lastRefreshTime {
+  UserSettingsOnStart settings =
+      [self userSettingsOnStartForEnterprisePolicy:enterprisePolicy
+                                       feedVisible:feedVisible
+                                          signedIn:signedIn
+                                        waaEnabled:waaEnabled
+                                   lastRefreshTime:lastRefreshTime];
+  base::UmaHistogramEnumeration(kFeedUserSettingsOnStart, settings);
+}
+
 #pragma mark - Private
 
+// Returns the UserSettingsOnStart value based on the user settings.
+- (UserSettingsOnStart)
+    userSettingsOnStartForEnterprisePolicy:(BOOL)enterprisePolicy
+                               feedVisible:(BOOL)feedVisible
+                                  signedIn:(BOOL)signedIn
+                                waaEnabled:(BOOL)waaEnabled
+                           lastRefreshTime:(base::Time)lastRefreshTime {
+  if (!enterprisePolicy) {
+    return UserSettingsOnStart::kFeedNotEnabledByPolicy;
+  }
+
+  if (!feedVisible) {
+    if (signedIn) {
+      return UserSettingsOnStart::kFeedNotVisibleSignedIn;
+    }
+    return UserSettingsOnStart::kFeedNotVisibleSignedOut;
+  }
+
+  if (!signedIn) {
+    return UserSettingsOnStart::kSignedOut;
+  }
+
+  const base::TimeDelta delta = base::Time::Now() - lastRefreshTime;
+  if (delta >= base::TimeDelta() && delta <= kUserSettingsMaxAge) {
+    return UserSettingsOnStart::kSignedInNoRecentData;
+  }
+
+  if (waaEnabled) {
+    return UserSettingsOnStart::kSignedInWaaOnDpOff;
+  } else {
+    return UserSettingsOnStart::kSignedInWaaOffDpOff;
+  }
+}
+
 // Records histogram metrics for Discover feed user actions.
 - (void)recordDiscoverFeedUserActionHistogram:(FeedUserActionType)actionType {
   UMA_HISTOGRAM_ENUMERATION(kDiscoverFeedUserActionHistogram, actionType);
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/BUILD.gn b/ios/chrome/browser/ui/popup_menu/overflow_menu/BUILD.gn
index fbe5c60..6eea3f5 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/BUILD.gn
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/BUILD.gn
@@ -55,6 +55,8 @@
     "//ios/chrome/browser/find_in_page",
     "//ios/chrome/browser/follow",
     "//ios/chrome/browser/follow:enums",
+    "//ios/chrome/browser/follow:tab_helper",
+    "//ios/chrome/browser/follow:utils",
     "//ios/chrome/browser/ntp:features",
     "//ios/chrome/browser/overlays",
     "//ios/chrome/browser/policy",
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.h b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.h
index f97b27f..49ee39ab 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.h
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.h
@@ -82,10 +82,6 @@
 // The current browser policy connector.
 @property(nonatomic, assign) BrowserPolicyConnectorIOS* browserPolicyConnector;
 
-// The follow action state. e.g. If the property value is FollowActionStateHide,
-// "Follow" action should be hidden in the overflow menu.
-@property(nonatomic, assign) FollowActionState followActionState;
-
 // Disconnect the mediator.
 - (void)disconnect;
 
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.mm b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.mm
index 6bbf2bc..492992c 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.mm
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/overflow_menu_mediator.mm
@@ -25,6 +25,9 @@
 #include "ios/chrome/browser/chrome_url_constants.h"
 #import "ios/chrome/browser/find_in_page/find_tab_helper.h"
 #import "ios/chrome/browser/follow/follow_java_script_feature.h"
+#import "ios/chrome/browser/follow/follow_menu_updater.h"
+#import "ios/chrome/browser/follow/follow_tab_helper.h"
+#import "ios/chrome/browser/follow/follow_util.h"
 #import "ios/chrome/browser/ntp/features.h"
 #import "ios/chrome/browser/overlays/public/overlay_presenter.h"
 #import "ios/chrome/browser/overlays/public/overlay_presenter_observer_bridge.h"
@@ -193,6 +196,7 @@
 
 @interface OverflowMenuMediator () <BookmarkModelBridgeObserver,
                                     CRWWebStateObserver,
+                                    FollowMenuUpdater,
                                     IOSLanguageDetectionTabHelperObserving,
                                     OverlayPresenterObserving,
                                     PrefObserverDelegate,
@@ -219,9 +223,6 @@
 // The current web state.
 @property(nonatomic, assign) web::WebState* webState;
 
-// URLs for the current webpage, which are used to match it to a web channel.
-@property(nonatomic, strong) FollowWebPageURLs* webPageURLs;
-
 // Whether an overlay is currently presented over the web content area.
 @property(nonatomic, assign) BOOL webContentAreaShowingOverlay;
 
@@ -249,7 +250,6 @@
 
 @property(nonatomic, strong) OverflowMenuAction* clearBrowsingDataAction;
 @property(nonatomic, strong) OverflowMenuAction* followAction;
-@property(nonatomic, strong) OverflowMenuAction* unfollowAction;
 @property(nonatomic, strong) OverflowMenuAction* addBookmarkAction;
 @property(nonatomic, strong) OverflowMenuAction* editBookmarkAction;
 @property(nonatomic, strong) OverflowMenuAction* readLaterAction;
@@ -296,9 +296,11 @@
     _engagementTracker = nullptr;
   }
 
+  if (self.webState && self.followAction) {
+    FollowTabHelper::FromWebState(self.webState)->remove_follow_menu_updater();
+  }
   self.webState = nullptr;
   self.webStateList = nullptr;
-  self.webPageURLs = nil;
 
   self.bookmarkModel = nullptr;
   self.prefService = nullptr;
@@ -349,6 +351,10 @@
 
   self.webState = (_webStateList) ? _webStateList->GetActiveWebState() : nil;
 
+  if (self.webState && !self.isIncognito && IsWebChannelsEnabled()) {
+    FollowTabHelper::FromWebState(self.webState)->set_follow_menu_updater(self);
+  }
+
   if (_webStateList) {
     _webStateList->AddObserver(_webStateListObserver.get());
   }
@@ -539,15 +545,29 @@
           [weakSelf openNewWindow];
         });
 
-    self.followAction = CreateOverflowMenuAction(
-        IDS_IOS_TOOLS_MENU_FOLLOW, kPlusSymbol, YES, kToolsMenuFollow, ^{
-          [weakSelf updateFollowStatus:YES];
-        });
+    if (!self.isIncognito && IsWebChannelsEnabled() &&
+        GetFollowActionState(self.webState) != FollowActionStateHidden) {
+      DCHECK(UseSymbols());
+      UIImageConfiguration* configuration = [UIImageSymbolConfiguration
+          configurationWithPointSize:kOverflowSymbolPointSize
+                              weight:UIImageSymbolWeightLight
+                               scale:UIImageSymbolScaleMedium];
+      NSString* name = l10n_util::GetNSStringF(IDS_IOS_TOOLS_MENU_FOLLOW,
+                                               base::SysNSStringToUTF16(@""));
+      UIImage* symbolImage =
+          [DefaultSymbolWithConfiguration(kPlusSymbol, configuration)
+              imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
 
-    self.unfollowAction = CreateOverflowMenuAction(
-        IDS_IOS_TOOLS_MENU_UNFOLLOW, kXMarkSymbol, YES, kToolsMenuUnfollow, ^{
-          [weakSelf updateFollowStatus:NO];
-        });
+      OverflowMenuAction* action =
+          [[OverflowMenuAction alloc] initWithName:name
+                                           uiImage:symbolImage
+                           accessibilityIdentifier:kToolsMenuFollow
+                                enterpriseDisabled:NO
+                                           handler:^{
+                                           }];
+      action.enabled = NO;
+      self.followAction = action;
+    }
 
     self.addBookmarkAction = CreateOverflowMenuAction(
         IDS_IOS_TOOLS_MENU_ADD_TO_BOOKMARKS, kAddBookmarkActionSymbol, YES,
@@ -645,17 +665,22 @@
           [weakSelf openNewWindow];
         });
 
-    self.followAction = CreateOverflowMenuAction(
-        IDS_IOS_TOOLS_MENU_FOLLOW, @"overflow_menu_action_follow",
-        kToolsMenuFollow, ^{
-          [weakSelf updateFollowStatus:YES];
-        });
+    if (!self.isIncognito && IsWebChannelsEnabled() &&
+        GetFollowActionState(self.webState) != FollowActionStateHidden) {
+      NSString* name = l10n_util::GetNSStringF(IDS_IOS_TOOLS_MENU_FOLLOW,
+                                               base::SysNSStringToUTF16(@""));
 
-    self.unfollowAction = CreateOverflowMenuAction(
-        IDS_IOS_TOOLS_MENU_UNFOLLOW, @"overflow_menu_action_unfollow",
-        kToolsMenuUnfollow, ^{
-          [weakSelf updateFollowStatus:NO];
-        });
+      OverflowMenuAction* action = [[OverflowMenuAction alloc]
+                     initWithName:name
+                          uiImage:[UIImage
+                                      imageNamed:@"overflow_menu_action_follow"]
+          accessibilityIdentifier:kToolsMenuFollow
+               enterpriseDisabled:NO
+                          handler:^{
+                          }];
+      action.enabled = NO;
+      self.followAction = action;
+    }
 
     self.addBookmarkAction = CreateOverflowMenuAction(
         IDS_IOS_TOOLS_MENU_ADD_TO_BOOKMARKS, @"overflow_menu_action_bookmark",
@@ -813,14 +838,30 @@
   BOOL pageIsBookmarked =
       self.webState && self.bookmarkModel &&
       self.bookmarkModel->IsBookmarked(self.webState->GetVisibleURL());
-  NSArray<OverflowMenuAction*>* basePageActions = @[
-    (pageIsBookmarked) ? self.editBookmarkAction : self.addBookmarkAction,
-    self.readLaterAction, self.translateAction,
-    ([self userAgentType] != web::UserAgentType::DESKTOP)
-        ? self.requestDesktopAction
-        : self.requestMobileAction,
-    self.findInPageAction, self.textZoomAction
-  ];
+
+  NSArray<OverflowMenuAction*>* basePageActions;
+  if (self.followAction &&
+      GetFollowActionState(self.webState) != FollowActionStateHidden) {
+    DCHECK(IsWebChannelsEnabled());
+    basePageActions = @[
+      self.followAction,
+      (pageIsBookmarked) ? self.editBookmarkAction : self.addBookmarkAction,
+      self.readLaterAction, self.translateAction,
+      ([self userAgentType] != web::UserAgentType::DESKTOP)
+          ? self.requestDesktopAction
+          : self.requestMobileAction,
+      self.findInPageAction, self.textZoomAction
+    ];
+  } else {
+    basePageActions = @[
+      (pageIsBookmarked) ? self.editBookmarkAction : self.addBookmarkAction,
+      self.readLaterAction, self.translateAction,
+      ([self userAgentType] != web::UserAgentType::DESKTOP)
+          ? self.requestDesktopAction
+          : self.requestMobileAction,
+      self.findInPageAction, self.textZoomAction
+    ];
+  }
 
   if (IsNewOverflowMenuCBDActionEnabled()) {
     self.pageActionsGroup.actions = [@[ self.clearBrowsingDataAction ]
@@ -829,44 +870,6 @@
     self.pageActionsGroup.actions = basePageActions;
   }
 
-  // Add the follow/unfollow action.
-  if (self.followActionState != FollowActionStateHidden) {
-    DCHECK(IsWebChannelsEnabled());
-    __weak __typeof(self) weakSelf = self;
-    FollowJavaScriptFeature::GetInstance()->GetFollowWebPageURLs(
-        self.webState, base::BindOnce(^(FollowWebPageURLs* webPageURLs) {
-          if (webPageURLs) {
-            OverflowMenuMediator* strongSelf = weakSelf;
-            if (!strongSelf) {
-              return;
-            }
-
-            strongSelf.webPageURLs = webPageURLs;
-            BOOL webChannelFollowed = ios::GetChromeBrowserProvider()
-                                          .GetFollowProvider()
-                                          ->GetFollowStatus(webPageURLs);
-
-            std::string domainName =
-                web::GetMainFrame(self.webState)->GetSecurityOrigin().host();
-            if (!webChannelFollowed) {
-              strongSelf.followAction.name = l10n_util::GetNSStringF(
-                  IDS_IOS_TOOLS_MENU_FOLLOW, base::UTF8ToUTF16(domainName));
-              strongSelf.pageActionsGroup.actions =
-                  [@[ strongSelf.followAction ]
-                      arrayByAddingObjectsFromArray:strongSelf.pageActionsGroup
-                                                        .actions];
-            } else {
-              strongSelf.unfollowAction.name = l10n_util::GetNSStringF(
-                  IDS_IOS_TOOLS_MENU_UNFOLLOW, base::UTF8ToUTF16(domainName));
-              strongSelf.pageActionsGroup.actions =
-                  [@[ strongSelf.unfollowAction ]
-                      arrayByAddingObjectsFromArray:strongSelf.pageActionsGroup
-                                                        .actions];
-            }
-          }
-        }));
-  }
-
   NSMutableArray<OverflowMenuAction*>* helpActions =
       [[NSMutableArray alloc] init];
 
@@ -904,9 +907,6 @@
   self.readLaterAction.enabled =
       !self.webContentAreaShowingOverlay && [self isCurrentURLWebURL];
 
-  BOOL followEnabled = self.followActionState == FollowActionStateEnabled;
-  self.followAction.enabled = followEnabled;
-  self.unfollowAction.enabled = followEnabled;
   BOOL bookmarkEnabled =
       [self isCurrentURLWebURL] && [self isEditBookmarksEnabled];
   self.addBookmarkAction.enabled = bookmarkEnabled;
@@ -1107,6 +1107,9 @@
                     atIndex:(int)atIndex
                      reason:(ActiveWebStateChangeReason)reason {
   self.webState = newWebState;
+  if (self.webState && self.followAction) {
+    FollowTabHelper::FromWebState(self.webState)->set_follow_menu_updater(self);
+  }
 }
 
 #pragma mark - BookmarkModelBridgeObserver
@@ -1142,6 +1145,24 @@
   [self updateModel];
 }
 
+#pragma mark - FollowMenuUpdater
+
+- (void)updateFollowMenuItemWithFollowWebPageURLs:
+            (FollowWebPageURLs*)webPageURLs
+                                           status:(BOOL)status
+                                            title:(NSString*)title
+                                          enabled:(BOOL)enable {
+  DCHECK(IsWebChannelsEnabled());
+  self.followAction.enabled = enable;
+  self.followAction.name = title;
+  self.followAction.uiImage = [UIImage
+      imageNamed:status ? @"popup_menu_unfollow" : @"popup_menu_follow"];
+  __weak __typeof(self) weakSelf = self;
+  self.followAction.handler = ^{
+    [weakSelf updateFollowStatus:!status withFollowWebPageURLs:webPageURLs];
+  };
+}
+
 #pragma mark - BrowserContainerConsumer
 
 - (void)setContentBlocked:(BOOL)contentBlocked {
@@ -1238,13 +1259,12 @@
   [self.dispatcher showClearBrowsingDataSettings];
 }
 
-// Updates the follow status of the web channel corresponding to |webPageURLs|
-// to |followStatus|, and dismisses the menu.
-- (void)updateFollowStatus:(BOOL)followStatus {
+// Updates the follow status of the website corresponding to |webPageURLs|
+// with |followStatus|, and dismisses the menu.
+- (void)updateFollowStatus:(BOOL)followStatus
+     withFollowWebPageURLs:(FollowWebPageURLs*)webPageURLs {
   ios::GetChromeBrowserProvider().GetFollowProvider()->UpdateFollowStatus(
-      self.webPageURLs, followStatus);
-  // TODO(crbug.com/1323764): This will need to be called on the
-  // PopupMenuCommands handler.
+      webPageURLs, followStatus);
   [self.dispatcher dismissPopupMenuAnimated:YES];
 }
 
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_follow.imageset/overflow_menu_action_follow@2x.png b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_follow.imageset/overflow_menu_action_follow@2x.png
index 46cacaf..89531d6 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_follow.imageset/overflow_menu_action_follow@2x.png
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_follow.imageset/overflow_menu_action_follow@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_follow.imageset/overflow_menu_action_follow@3x.png b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_follow.imageset/overflow_menu_action_follow@3x.png
index 7fecd12c..1c629098 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_follow.imageset/overflow_menu_action_follow@3x.png
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_follow.imageset/overflow_menu_action_follow@3x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unfollow.imageset/overflow_menu_action_unfollow@2x.png b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unfollow.imageset/overflow_menu_action_unfollow@2x.png
index d7fdca67..1c7c7ad 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unfollow.imageset/overflow_menu_action_unfollow@2x.png
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unfollow.imageset/overflow_menu_action_unfollow@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unfollow.imageset/overflow_menu_action_unfollow@3x.png b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unfollow.imageset/overflow_menu_action_unfollow@3x.png
index c6cceaf..10579b7 100644
--- a/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unfollow.imageset/overflow_menu_action_unfollow@3x.png
+++ b/ios/chrome/browser/ui/popup_menu/overflow_menu/resources/overflow_menu_action_unfollow.imageset/overflow_menu_action_unfollow@3x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm b/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
index 1777bf2..66e4942 100644
--- a/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
+++ b/ios/chrome/browser/ui/popup_menu/popup_menu_coordinator.mm
@@ -14,7 +14,6 @@
 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
 #include "ios/chrome/browser/feature_engagement/tracker_factory.h"
 #import "ios/chrome/browser/follow/follow_action_state.h"
-#import "ios/chrome/browser/follow/follow_util.h"
 #import "ios/chrome/browser/main/browser.h"
 #import "ios/chrome/browser/ntp/features.h"
 #import "ios/chrome/browser/overlays/public/overlay_presenter.h"
@@ -368,13 +367,9 @@
             GetApplicationContext()->GetBrowserPolicyConnector();
 
         if (IsWebChannelsEnabled()) {
-          self.overflowMenuMediator.followActionState = GetFollowActionState(
-              self.browser->GetWebStateList()->GetActiveWebState());
           ios::GetChromeBrowserProvider()
               .GetFollowProvider()
               ->SetFollowEventDelegate(self.browser);
-        } else {
-          self.overflowMenuMediator.followActionState = FollowActionStateHidden;
         }
 
         self.contentBlockerMediator.consumer = self.overflowMenuMediator;
diff --git a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_follow.imageset/popup_menu_follow.png b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_follow.imageset/popup_menu_follow.png
index d5404fe0..5de150e 100644
--- a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_follow.imageset/popup_menu_follow.png
+++ b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_follow.imageset/popup_menu_follow.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_follow.imageset/popup_menu_follow@2x.png b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_follow.imageset/popup_menu_follow@2x.png
index f39d20ac..77ad1c20 100644
--- a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_follow.imageset/popup_menu_follow@2x.png
+++ b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_follow.imageset/popup_menu_follow@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_follow.imageset/popup_menu_follow@3x.png b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_follow.imageset/popup_menu_follow@3x.png
index 44812f7..407dbe54 100644
--- a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_follow.imageset/popup_menu_follow@3x.png
+++ b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_follow.imageset/popup_menu_follow@3x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_unfollow.imageset/popup_menu_unfollow.png b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_unfollow.imageset/popup_menu_unfollow.png
index de99e2b5..d851e345 100644
--- a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_unfollow.imageset/popup_menu_unfollow.png
+++ b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_unfollow.imageset/popup_menu_unfollow.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_unfollow.imageset/popup_menu_unfollow@2x.png b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_unfollow.imageset/popup_menu_unfollow@2x.png
index 39e3983..f7f8d8bd 100644
--- a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_unfollow.imageset/popup_menu_unfollow@2x.png
+++ b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_unfollow.imageset/popup_menu_unfollow@2x.png
Binary files differ
diff --git a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_unfollow.imageset/popup_menu_unfollow@3x.png b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_unfollow.imageset/popup_menu_unfollow@3x.png
index 2a90c6b..6eb8c4d 100644
--- a/ios/chrome/browser/ui/popup_menu/resources/popup_menu_unfollow.imageset/popup_menu_unfollow@3x.png
+++ b/ios/chrome/browser/ui/popup_menu/resources/popup_menu_unfollow.imageset/popup_menu_unfollow@3x.png
Binary files differ
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 8e61eee..6b9f534 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 @@
-03b7fa8ea2fbad81f2639121a81d65109b60d09b
\ No newline at end of file
+e7722b01ec2fb58c39a417e7ddbb2194aa3e4d2e
\ 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 c2d73fa5..f3e20c1 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 @@
-8f5fa7edad2c8a7b54e081a52f4d2fb7131fef47
\ No newline at end of file
+b180d1c47893f901f48a8fd3a5f64817496f5489
\ 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 17ef57e5..ddb2e95d 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 @@
-b3620d25e36c6e6c17f1a44122ea1cb20909f379
\ No newline at end of file
+eeddfb582a95346355dfe69f0a55ba6f92688d86
\ 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 28d20e5..1ba9652 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 @@
-df674bc71debff95df8d19ac552ddbd182f46c32
\ No newline at end of file
+60d708fd483717e5953db180d3e843696d2e6126
\ 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 80d4f23..6d1d5ee 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 @@
-ab228fadeef1e16d3fa0c09d893a9406b9e36710
\ No newline at end of file
+9528311f832549b366a5c24f53a32fca7faa7e1b
\ 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 711c356..18fba93 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 @@
-3f38a0ea172cb5a790f556e66a7cf52afef68785
\ No newline at end of file
+c77416982e05bac1015ce4d470f97c68b06de442
\ 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 1ade71c6..418ff977 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 @@
-43cebe3407a61ddeba9df4ed3e690352546dfa80
\ No newline at end of file
+37e3f825c7ab646ac9c2b3ad84b1cd27010420a4
\ 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 b8c5265d..a3e2835 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 @@
-14c49f8aba562f546d0070afac3515eccd116d5a
\ No newline at end of file
+5388dd256b2e58603d15ac73f0ba6efab3d8e9d9
\ 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 cf6844e..5d11f87 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 @@
-bf4158a0ef22bc9516f51b438e0582c87d3f4e4a
\ No newline at end of file
+fd2b87cd364d0a475ac27c8eea509f6ffb79ad29
\ 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 90d053aa..33503b8 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 @@
-6da5d34e8be72fe45b1d2e237960a0002b102f94
\ No newline at end of file
+34cf55dfc0bd04d6c1c8c8a2e416afe4b8cd8e07
\ No newline at end of file
diff --git a/ipc/SECURITY_OWNERS b/ipc/SECURITY_OWNERS
index 964a4879..b2b27ad 100644
--- a/ipc/SECURITY_OWNERS
+++ b/ipc/SECURITY_OWNERS
@@ -13,7 +13,6 @@
 estark@chromium.org
 joenotcharles@chromium.org
 kenrb@chromium.org
-kerrnel@chromium.org
 kinuko@chromium.org
 meacer@chromium.org
 mkwst@chromium.org
diff --git a/media/base/BUILD.gn b/media/base/BUILD.gn
index 1e8b1707..1b14b44 100644
--- a/media/base/BUILD.gn
+++ b/media/base/BUILD.gn
@@ -313,8 +313,6 @@
     "timestamp_constants.h",
     "tuneable.cc",
     "tuneable.h",
-    "unaligned_shared_memory.cc",
-    "unaligned_shared_memory.h",
     "use_after_free_checker.h",
     "user_input_monitor.cc",
     "user_input_monitor.h",
@@ -618,7 +616,6 @@
     "text_renderer_unittest.cc",
     "time_delta_interpolator_unittest.cc",
     "tuneable_unittest.cc",
-    "unaligned_shared_memory_unittest.cc",
     "user_input_monitor_unittest.cc",
     "vector_math_unittest.cc",
     "video_aspect_ratio_unittest.cc",
diff --git a/media/base/bitstream_buffer.cc b/media/base/bitstream_buffer.cc
index 97cb6be..202e97460 100644
--- a/media/base/bitstream_buffer.cc
+++ b/media/base/bitstream_buffer.cc
@@ -10,28 +10,15 @@
 namespace media {
 
 BitstreamBuffer::BitstreamBuffer()
-    : BitstreamBuffer(-1, base::subtle::PlatformSharedMemoryRegion(), 0) {}
-
-BitstreamBuffer::BitstreamBuffer(
-    int32_t id,
-    base::subtle::PlatformSharedMemoryRegion region,
-    size_t size,
-    off_t offset,
-    base::TimeDelta presentation_timestamp)
-    : id_(id),
-      region_(std::move(region)),
-      size_(size),
-      offset_(offset),
-      presentation_timestamp_(presentation_timestamp) {}
+    : BitstreamBuffer(-1, base::UnsafeSharedMemoryRegion(), 0) {}
 
 BitstreamBuffer::BitstreamBuffer(int32_t id,
                                  base::UnsafeSharedMemoryRegion region,
                                  size_t size,
-                                 off_t offset,
+                                 uint64_t offset,
                                  base::TimeDelta presentation_timestamp)
     : id_(id),
-      region_(base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-          std::move(region))),
+      region_(std::move(region)),
       size_(size),
       offset_(offset),
       presentation_timestamp_(presentation_timestamp) {}
diff --git a/media/base/bitstream_buffer.h b/media/base/bitstream_buffer.h
index dfff2d3..52dadb0 100644
--- a/media/base/bitstream_buffer.h
+++ b/media/base/bitstream_buffer.h
@@ -8,7 +8,6 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#include "base/memory/platform_shared_memory_region.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/unsafe_shared_memory_region.h"
 #include "base/time/time.h"
@@ -37,16 +36,9 @@
   // When not provided, |presentation_timestamp| will be
   // |media::kNoTimestamp|.
   BitstreamBuffer(int32_t id,
-                  base::subtle::PlatformSharedMemoryRegion region,
-                  size_t size,
-                  off_t offset = 0,
-                  base::TimeDelta presentation_timestamp = kNoTimestamp);
-
-  // As above, creating by unwrapping a base::UnsafeSharedMemoryRegion.
-  BitstreamBuffer(int32_t id,
                   base::UnsafeSharedMemoryRegion region,
                   size_t size,
-                  off_t offset = 0,
+                  uint64_t offset = 0,
                   base::TimeDelta presentation_timestamp = kNoTimestamp);
 
   // Move operations are allowed.
@@ -79,22 +71,18 @@
                              const std::string& iv,
                              const std::vector<SubsampleEntry>& subsamples);
 
-  // Taking the region invalides the one in this BitstreamBuffer.
-  base::subtle::PlatformSharedMemoryRegion TakeRegion() {
-    return std::move(region_);
-  }
+  // Taking the region invalidates the one in this BitstreamBuffer.
+  base::UnsafeSharedMemoryRegion TakeRegion() { return std::move(region_); }
 
   // If a region needs to be taken from a const BitstreamBuffer, it must be
   // duplicated. This function makes that explicit.
   // TODO(crbug.com/793446): this is probably only needed by legacy IPC, and can
   // be removed once that is converted to the new shared memory API.
-  base::subtle::PlatformSharedMemoryRegion DuplicateRegion() const {
+  base::UnsafeSharedMemoryRegion DuplicateRegion() const {
     return region_.Duplicate();
   }
 
-  const base::subtle::PlatformSharedMemoryRegion& region() const {
-    return region_;
-  }
+  const base::UnsafeSharedMemoryRegion& region() const { return region_; }
 
   int32_t id() const { return id_; }
 
@@ -103,17 +91,13 @@
   size_t size() const { return size_; }
 
   // The offset to the start of actual bitstream data in the shared memory.
-  off_t offset() const { return offset_; }
+  uint64_t offset() const { return offset_; }
 
   // The timestamp is only valid if it's not equal to |media::kNoTimestamp|.
   base::TimeDelta presentation_timestamp() const {
     return presentation_timestamp_;
   }
 
-  void set_region(base::subtle::PlatformSharedMemoryRegion region) {
-    region_ = std::move(region);
-  }
-
   // The following methods come from SetDecryptionSettings().
   const std::string& key_id() const { return key_id_; }
   const std::string& iv() const { return iv_; }
@@ -121,9 +105,9 @@
 
  private:
   int32_t id_;
-  base::subtle::PlatformSharedMemoryRegion region_;
+  base::UnsafeSharedMemoryRegion region_;
   size_t size_;
-  off_t offset_;
+  uint64_t offset_;
 
   // Note: Not set by all clients.
   base::TimeDelta presentation_timestamp_;
diff --git a/media/base/decoder_buffer.cc b/media/base/decoder_buffer.cc
index 4d98e2c..620fc447 100644
--- a/media/base/decoder_buffer.cc
+++ b/media/base/decoder_buffer.cc
@@ -46,25 +46,15 @@
 }
 
 DecoderBuffer::DecoderBuffer(std::unique_ptr<uint8_t[]> data, size_t size)
-    : data_(std::move(data)),
-      size_(size),
-      side_data_size_(0),
-      is_key_frame_(false) {}
+    : data_(std::move(data)), size_(size) {}
 
-DecoderBuffer::DecoderBuffer(std::unique_ptr<UnalignedSharedMemory> shm,
+DecoderBuffer::DecoderBuffer(base::ReadOnlySharedMemoryMapping mapping,
                              size_t size)
-    : size_(size),
-      side_data_size_(0),
-      shm_(std::move(shm)),
-      is_key_frame_(false) {}
+    : size_(size), read_only_mapping_(std::move(mapping)) {}
 
-DecoderBuffer::DecoderBuffer(
-    std::unique_ptr<ReadOnlyUnalignedMapping> shared_mem_mapping,
-    size_t size)
-    : size_(size),
-      side_data_size_(0),
-      shared_mem_mapping_(std::move(shared_mem_mapping)),
-      is_key_frame_(false) {}
+DecoderBuffer::DecoderBuffer(base::WritableSharedMemoryMapping mapping,
+                             size_t size)
+    : size_(size), writable_mapping_(std::move(mapping)) {}
 
 DecoderBuffer::~DecoderBuffer() {
   data_.reset();
@@ -107,34 +97,33 @@
 
 // static
 scoped_refptr<DecoderBuffer> DecoderBuffer::FromSharedMemoryRegion(
-    base::subtle::PlatformSharedMemoryRegion region,
-    off_t offset,
+    base::UnsafeSharedMemoryRegion region,
+    uint64_t offset,
     size_t size) {
-  // TODO(crbug.com/795291): when clients have converted to using
-  // base::ReadOnlySharedMemoryRegion the ugly mode check below will no longer
-  // be necessary.
-  auto shm = std::make_unique<UnalignedSharedMemory>(
-      std::move(region), size,
-      region.GetMode() ==
-              base::subtle::PlatformSharedMemoryRegion::Mode::kReadOnly
-          ? true
-          : false);
-  if (size == 0 || !shm->MapAt(offset, size))
+  if (size == 0) {
     return nullptr;
-  return base::WrapRefCounted(new DecoderBuffer(std::move(shm), size));
+  }
+
+  auto mapping = region.MapAt(offset, size);
+  if (!mapping.IsValid()) {
+    return nullptr;
+  }
+  return base::WrapRefCounted(new DecoderBuffer(std::move(mapping), size));
 }
 
 // static
 scoped_refptr<DecoderBuffer> DecoderBuffer::FromSharedMemoryRegion(
     base::ReadOnlySharedMemoryRegion region,
-    off_t offset,
+    uint64_t offset,
     size_t size) {
-  std::unique_ptr<ReadOnlyUnalignedMapping> unaligned_mapping =
-      std::make_unique<ReadOnlyUnalignedMapping>(region, size, offset);
-  if (!unaligned_mapping->IsValid())
+  if (size == 0) {
     return nullptr;
-  return base::WrapRefCounted(
-      new DecoderBuffer(std::move(unaligned_mapping), size));
+  }
+  auto mapping = region.MapAt(offset, size);
+  if (!mapping.IsValid()) {
+    return nullptr;
+  }
+  return base::WrapRefCounted(new DecoderBuffer(std::move(mapping), size));
 }
 
 // static
diff --git a/media/base/decoder_buffer.h b/media/base/decoder_buffer.h
index f44b879..28befa6 100644
--- a/media/base/decoder_buffer.h
+++ b/media/base/decoder_buffer.h
@@ -15,12 +15,13 @@
 #include "base/check.h"
 #include "base/memory/read_only_shared_memory_region.h"
 #include "base/memory/ref_counted.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
 #include "media/base/decrypt_config.h"
 #include "media/base/media_export.h"
 #include "media/base/timestamp_constants.h"
-#include "media/base/unaligned_shared_memory.h"
 
 namespace media {
 
@@ -97,8 +98,8 @@
   //
   // If mapping fails, nullptr will be returned.
   static scoped_refptr<DecoderBuffer> FromSharedMemoryRegion(
-      base::subtle::PlatformSharedMemoryRegion region,
-      off_t offset,
+      base::UnsafeSharedMemoryRegion region,
+      uint64_t offset,
       size_t size);
 
   // Create a DecoderBuffer where data() of |size| bytes resides within the
@@ -108,7 +109,7 @@
   // Ownership of |region| is transferred to the buffer.
   static scoped_refptr<DecoderBuffer> FromSharedMemoryRegion(
       base::ReadOnlySharedMemoryRegion region,
-      off_t offset,
+      uint64_t offset,
       size_t size);
 
   // Create a DecoderBuffer indicating we've reached end of stream.
@@ -146,18 +147,18 @@
 
   const uint8_t* data() const {
     DCHECK(!end_of_stream());
-    if (shared_mem_mapping_ && shared_mem_mapping_->IsValid())
-      return static_cast<const uint8_t*>(shared_mem_mapping_->memory());
-    if (shm_)
-      return static_cast<uint8_t*>(shm_->memory());
+    if (read_only_mapping_.IsValid())
+      return read_only_mapping_.GetMemoryAs<const uint8_t>();
+    if (writable_mapping_.IsValid())
+      return writable_mapping_.GetMemoryAs<const uint8_t>();
     return data_.get();
   }
 
   // TODO(sandersd): Remove writable_data(). https://crbug.com/834088
   uint8_t* writable_data() const {
     DCHECK(!end_of_stream());
-    DCHECK(!shm_);
-    DCHECK(!shared_mem_mapping_);
+    DCHECK(!read_only_mapping_.IsValid());
+    DCHECK(!writable_mapping_.IsValid());
     return data_.get();
   }
 
@@ -199,7 +200,10 @@
   }
 
   // If there's no data in this buffer, it represents end of stream.
-  bool end_of_stream() const { return !shared_mem_mapping_ && !shm_ && !data_; }
+  bool end_of_stream() const {
+    return !read_only_mapping_.IsValid() && !writable_mapping_.IsValid() &&
+           !data_;
+  }
 
   bool is_key_frame() const {
     DCHECK(!end_of_stream());
@@ -237,10 +241,9 @@
 
   DecoderBuffer(std::unique_ptr<uint8_t[]> data, size_t size);
 
-  DecoderBuffer(std::unique_ptr<UnalignedSharedMemory> shm, size_t size);
+  DecoderBuffer(base::ReadOnlySharedMemoryMapping mapping, size_t size);
 
-  DecoderBuffer(std::unique_ptr<ReadOnlyUnalignedMapping> shared_mem_mapping,
-                size_t size);
+  DecoderBuffer(base::WritableSharedMemoryMapping mapping, size_t size);
 
   virtual ~DecoderBuffer();
 
@@ -254,20 +257,20 @@
   size_t size_;
 
   // Side data. Used for alpha channel in VPx, and for text cues.
-  size_t side_data_size_;
+  size_t side_data_size_ = 0;
   std::unique_ptr<uint8_t[]> side_data_;
 
-  // Encoded data, if it is stored in a shared memory mapping.
-  std::unique_ptr<ReadOnlyUnalignedMapping> shared_mem_mapping_;
+  // Encoded data, if it is stored in a read-only shared memory mapping.
+  base::ReadOnlySharedMemoryMapping read_only_mapping_;
 
-  // Encoded data, if it is stored in SHM.
-  std::unique_ptr<UnalignedSharedMemory> shm_;
+  // Encoded data, if it is stored in a writable shared memory mapping.
+  base::WritableSharedMemoryMapping writable_mapping_;
 
   // Encryption parameters for the encoded data.
   std::unique_ptr<DecryptConfig> decrypt_config_;
 
   // Whether the frame was marked as a keyframe in the container.
-  bool is_key_frame_;
+  bool is_key_frame_ = false;
 
   // Constructor helper method for memory allocations.
   void Initialize();
diff --git a/media/base/decoder_buffer_unittest.cc b/media/base/decoder_buffer_unittest.cc
index 2f80d5e9..91a871a0 100644
--- a/media/base/decoder_buffer_unittest.cc
+++ b/media/base/decoder_buffer_unittest.cc
@@ -86,10 +86,8 @@
   ASSERT_TRUE(mapping.IsValid());
   memcpy(mapping.GetMemoryAs<uint8_t>(), kData, kDataSize);
 
-  scoped_refptr<DecoderBuffer> buffer(DecoderBuffer::FromSharedMemoryRegion(
-      base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-          std::move(region)),
-      0, kDataSize));
+  scoped_refptr<DecoderBuffer> buffer(
+      DecoderBuffer::FromSharedMemoryRegion(std::move(region), 0, kDataSize));
   ASSERT_TRUE(buffer.get());
   EXPECT_EQ(buffer->data_size(), kDataSize);
   EXPECT_EQ(0, memcmp(buffer->data(), kData, kDataSize));
@@ -108,9 +106,7 @@
   memcpy(mapping.GetMemoryAs<uint8_t>(), kData, kDataSize);
 
   scoped_refptr<DecoderBuffer> buffer(DecoderBuffer::FromSharedMemoryRegion(
-      base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-          std::move(region)),
-      kDataOffset, kDataSize - kDataOffset));
+      std::move(region), kDataOffset, kDataSize - kDataOffset));
   ASSERT_TRUE(buffer.get());
   EXPECT_EQ(buffer->data_size(), kDataSize - kDataOffset);
   EXPECT_EQ(
@@ -128,10 +124,8 @@
   ASSERT_TRUE(mapping.IsValid());
   memcpy(mapping.memory(), kData, kDataSize);
 
-  scoped_refptr<DecoderBuffer> buffer(DecoderBuffer::FromSharedMemoryRegion(
-      base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-          std::move(region)),
-      0, 0));
+  scoped_refptr<DecoderBuffer> buffer(
+      DecoderBuffer::FromSharedMemoryRegion(std::move(region), 0, 0));
   ASSERT_FALSE(buffer.get());
 }
 
diff --git a/media/base/unaligned_shared_memory.cc b/media/base/unaligned_shared_memory.cc
deleted file mode 100644
index 542225f..0000000
--- a/media/base/unaligned_shared_memory.cc
+++ /dev/null
@@ -1,186 +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 "media/base/unaligned_shared_memory.h"
-
-#include <limits>
-
-#include "base/logging.h"
-#include "base/memory/read_only_shared_memory_region.h"
-#include "base/system/sys_info.h"
-#include "mojo/public/cpp/system/platform_handle.h"
-
-namespace media {
-
-namespace {
-
-bool CalculateMisalignmentAndOffset(size_t size,
-                                    off_t offset,
-                                    size_t* misalignment,
-                                    off_t* adjusted_offset) {
-  /* |   |   |   |   |   |  shm pages
-   *       |                offset (may exceed max size_t)
-   *       |-----------|    size
-   *     |-|                misalignment
-   *     |                  adjusted offset
-   *     |-------------|    requested mapping
-   */
-
-  // Note: result of % computation may be off_t or size_t, depending on the
-  // relative ranks of those types. In any case we assume that
-  // VMAllocationGranularity() fits in both types, so the final result does too.
-  DCHECK_GE(offset, 0);
-  *misalignment = offset % base::SysInfo::VMAllocationGranularity();
-
-  // Above this |max_size|, |size| + |*misalignment| overflows.
-  size_t max_size = std::numeric_limits<size_t>::max() - *misalignment;
-  if (size > max_size) {
-    DLOG(ERROR) << "Invalid size";
-    return false;
-  }
-
-  *adjusted_offset = offset - static_cast<off_t>(*misalignment);
-
-  return true;
-}
-
-}  // namespace
-
-UnalignedSharedMemory::UnalignedSharedMemory(
-    base::subtle::PlatformSharedMemoryRegion region,
-    size_t size,
-    bool read_only)
-    : region_(std::move(region)), read_only_(read_only), size_(size) {}
-
-UnalignedSharedMemory::~UnalignedSharedMemory() = default;
-
-bool UnalignedSharedMemory::MapAt(off_t offset, size_t size) {
-  if (offset < 0) {
-    DLOG(ERROR) << "Invalid offset";
-    return false;
-  }
-
-  size_t misalignment;
-  off_t adjusted_offset;
-
-  if (!CalculateMisalignmentAndOffset(size, offset, &misalignment,
-                                      &adjusted_offset)) {
-    return false;
-  }
-
-  if (read_only_) {
-    auto shm =
-        base::ReadOnlySharedMemoryRegion::Deserialize(std::move(region_));
-    read_only_mapping_ = shm.MapAt(adjusted_offset, size + misalignment);
-    if (!read_only_mapping_.IsValid()) {
-      DLOG(ERROR) << "Failed to map shared memory";
-      return false;
-    }
-    // TODO(crbug.com/849207): this ugly const cast will go away when uses of
-    // UnalignedSharedMemory are converted to
-    // {Writable,ReadOnly}UnalignedMapping.
-    mapping_ptr_ = const_cast<uint8_t*>(
-        static_cast<const uint8_t*>(read_only_mapping_.memory()));
-  } else {
-    auto shm = base::UnsafeSharedMemoryRegion::Deserialize(std::move(region_));
-    writable_mapping_ = shm.MapAt(adjusted_offset, size + misalignment);
-    if (!writable_mapping_.IsValid()) {
-      DLOG(ERROR) << "Failed to map shared memory";
-      return false;
-    }
-    mapping_ptr_ = static_cast<uint8_t*>(writable_mapping_.memory());
-  }
-
-  DCHECK(mapping_ptr_);
-  // There should be no way for the IsValid() checks above to succeed and yet
-  // |mapping_ptr_| remain null. However, since an invalid but non-null pointer
-  // could be disastrous an extra-careful check is done.
-  if (mapping_ptr_)
-    mapping_ptr_ += misalignment;
-  return true;
-}
-
-WritableUnalignedMapping::WritableUnalignedMapping(
-    const base::UnsafeSharedMemoryRegion& region,
-    size_t size,
-    off_t offset)
-    : size_(size), misalignment_(0) {
-  if (!region.IsValid()) {
-    DLOG(ERROR) << "Invalid region";
-    return;
-  }
-
-  if (offset < 0) {
-    DLOG(ERROR) << "Invalid offset";
-    return;
-  }
-
-  off_t adjusted_offset;
-  if (!CalculateMisalignmentAndOffset(size_, offset, &misalignment_,
-                                      &adjusted_offset)) {
-    return;
-  }
-
-  mapping_ = region.MapAt(adjusted_offset, size_ + misalignment_);
-  if (!mapping_.IsValid()) {
-    DLOG(ERROR) << "Failed to map shared memory " << adjusted_offset << "("
-                << offset << ")"
-                << "@" << size << "/\\" << misalignment_ << " on "
-                << region.GetSize();
-
-    return;
-  }
-}
-
-WritableUnalignedMapping::~WritableUnalignedMapping() = default;
-
-void* WritableUnalignedMapping::memory() const {
-  if (!IsValid()) {
-    return nullptr;
-  }
-  return mapping_.GetMemoryAs<uint8_t>() + misalignment_;
-}
-
-ReadOnlyUnalignedMapping::ReadOnlyUnalignedMapping(
-    const base::ReadOnlySharedMemoryRegion& region,
-    size_t size,
-    off_t offset)
-    : size_(size), misalignment_(0) {
-  if (!region.IsValid()) {
-    DLOG(ERROR) << "Invalid region";
-    return;
-  }
-
-  if (offset < 0) {
-    DLOG(ERROR) << "Invalid offset";
-    return;
-  }
-
-  off_t adjusted_offset;
-  if (!CalculateMisalignmentAndOffset(size_, offset, &misalignment_,
-                                      &adjusted_offset)) {
-    return;
-  }
-
-  mapping_ = region.MapAt(adjusted_offset, size_ + misalignment_);
-  if (!mapping_.IsValid()) {
-    DLOG(ERROR) << "Failed to map shared memory " << adjusted_offset << "("
-                << offset << ")"
-                << "@" << size << "/\\" << misalignment_ << " on "
-                << region.GetSize();
-
-    return;
-  }
-}
-
-ReadOnlyUnalignedMapping::~ReadOnlyUnalignedMapping() = default;
-
-const void* ReadOnlyUnalignedMapping::memory() const {
-  if (!IsValid()) {
-    return nullptr;
-  }
-  return mapping_.GetMemoryAs<uint8_t>() + misalignment_;
-}
-
-}  // namespace media
diff --git a/media/base/unaligned_shared_memory.h b/media/base/unaligned_shared_memory.h
deleted file mode 100644
index 481590cd..0000000
--- a/media/base/unaligned_shared_memory.h
+++ /dev/null
@@ -1,132 +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 MEDIA_BASE_UNALIGNED_SHARED_MEMORY_H_
-#define MEDIA_BASE_UNALIGNED_SHARED_MEMORY_H_
-
-#include <stdint.h>
-
-#include "base/memory/platform_shared_memory_region.h"
-#include "base/memory/raw_ptr.h"
-#include "base/memory/read_only_shared_memory_region.h"
-#include "base/memory/shared_memory_mapping.h"
-#include "base/memory/unsafe_shared_memory_region.h"
-#include "media/base/media_export.h"
-
-namespace media {
-
-// Wrapper over base::PlatformSharedMemoryRegion that can be mapped at unaligned
-// offsets.
-// DEPRECATED! See https://crbug.com/795291.
-class MEDIA_EXPORT UnalignedSharedMemory {
- public:
-  // Creates an |UnalignedSharedMemory| instance from a
-  // |PlatformSharedMemoryRegion|. |size| sets the maximum size that may be
-  // mapped. This instance will own the handle.
-  UnalignedSharedMemory(base::subtle::PlatformSharedMemoryRegion region,
-                        size_t size,
-                        bool read_only);
-
-  UnalignedSharedMemory(const UnalignedSharedMemory&) = delete;
-  UnalignedSharedMemory& operator=(const UnalignedSharedMemory&) = delete;
-
-  ~UnalignedSharedMemory();
-
-  // Map the shared memory region. Note that the passed |size| parameter should
-  // be less than or equal to |size()|.
-  bool MapAt(off_t offset, size_t size);
-  size_t size() const { return size_; }
-  void* memory() const { return mapping_ptr_; }
-
- private:
-  // Only one of the mappings is active, depending on the value of |read_only_|.
-  // These variables are held to keep the shared memory mapping valid for the
-  // lifetime of this instance.
-  base::subtle::PlatformSharedMemoryRegion region_;
-  base::WritableSharedMemoryMapping writable_mapping_;
-  base::ReadOnlySharedMemoryMapping read_only_mapping_;
-
-  // If the mapping should be made read-only.
-  bool read_only_;
-
-  // The size of the region associated with |region_|.
-  size_t size_;
-
-  // Pointer to the unaligned data in the shared memory mapping.
-  raw_ptr<uint8_t> mapping_ptr_ = nullptr;
-};
-
-// Wrapper over base::WritableSharedMemoryMapping that is mapped at unaligned
-// offsets.
-class MEDIA_EXPORT WritableUnalignedMapping {
- public:
-  // Creates an |WritableUnalignedMapping| instance from a
-  // |UnsafeSharedMemoryRegion|. |size| sets the maximum size that may be mapped
-  // within |region| and |offset| is the offset that will be mapped. |region| is
-  // not retained and is used only in the constructor.
-  WritableUnalignedMapping(const base::UnsafeSharedMemoryRegion& region,
-                           size_t size,
-                           off_t offset);
-
-  WritableUnalignedMapping(const WritableUnalignedMapping&) = delete;
-  WritableUnalignedMapping& operator=(const WritableUnalignedMapping&) = delete;
-
-  ~WritableUnalignedMapping();
-
-  size_t size() const { return size_; }
-  void* memory() const;
-
-  // True if the mapping backing the memory is valid.
-  bool IsValid() const { return mapping_.IsValid(); }
-
- private:
-  base::WritableSharedMemoryMapping mapping_;
-
-  // The size of the region associated with |mapping_|.
-  size_t size_;
-
-  // Difference between actual offset within |mapping_| where data has been
-  // mapped and requested offset; strictly less than
-  // base::SysInfo::VMAllocationGranularity().
-  size_t misalignment_;
-};
-
-// Wrapper over base::ReadOnlySharedMemoryMapping that is mapped at unaligned
-// offsets.
-class MEDIA_EXPORT ReadOnlyUnalignedMapping {
- public:
-  // Creates an |WritableUnalignedMapping| instance from a
-  // |ReadOnlySharedMemoryRegion|. |size| sets the maximum size that may be
-  // mapped within |region| and |offset| is the offset that will be mapped.
-  // |region| is not retained and is used only in the constructor.
-  ReadOnlyUnalignedMapping(const base::ReadOnlySharedMemoryRegion& region,
-                           size_t size,
-                           off_t offset);
-
-  ReadOnlyUnalignedMapping(const ReadOnlyUnalignedMapping&) = delete;
-  ReadOnlyUnalignedMapping& operator=(const ReadOnlyUnalignedMapping&) = delete;
-
-  ~ReadOnlyUnalignedMapping();
-
-  size_t size() const { return size_; }
-  const void* memory() const;
-
-  // True if the mapping backing the memory is valid.
-  bool IsValid() const { return mapping_.IsValid(); }
-
- private:
-  base::ReadOnlySharedMemoryMapping mapping_;
-
-  // The size of the region associated with |mapping_|.
-  size_t size_;
-
-  // Difference between actual offset within |mapping_| where data has been
-  // mapped and requested offset; strictly less than
-  // base::SysInfo::VMAllocationGranularity().
-  size_t misalignment_;
-};
-
-}  // namespace media
-
-#endif  // MEDIA_BASE_UNALIGNED_SHARED_MEMORY_H_
diff --git a/media/base/unaligned_shared_memory_unittest.cc b/media/base/unaligned_shared_memory_unittest.cc
deleted file mode 100644
index 9d553cb..0000000
--- a/media/base/unaligned_shared_memory_unittest.cc
+++ /dev/null
@@ -1,254 +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 "media/base/unaligned_shared_memory.h"
-
-#include <stdint.h>
-#include <string.h>
-
-#include <limits>
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace media {
-
-namespace {
-
-const uint8_t kUnalignedData[] = "XXXhello";
-const size_t kUnalignedDataSize = std::size(kUnalignedData);
-const off_t kUnalignedOffset = 3;
-
-const uint8_t kData[] = "hello";
-const size_t kDataSize = std::size(kData);
-
-base::UnsafeSharedMemoryRegion CreateRegion(const uint8_t* data, size_t size) {
-  auto region = base::UnsafeSharedMemoryRegion::Create(size);
-  auto mapping = region.Map();
-  EXPECT_TRUE(mapping.IsValid());
-  memcpy(mapping.memory(), data, size);
-  return region;
-}
-
-base::ReadOnlySharedMemoryRegion CreateReadOnlyRegion(const uint8_t* data,
-                                                      size_t size) {
-  auto mapped_region = base::ReadOnlySharedMemoryRegion::Create(size);
-  EXPECT_TRUE(mapped_region.IsValid());
-  memcpy(mapped_region.mapping.memory(), data, size);
-  return std::move(mapped_region.region);
-}
-}  // namespace
-
-TEST(UnalignedSharedMemoryTest, CreateAndDestroyRegion) {
-  auto region = CreateRegion(kData, kDataSize);
-  UnalignedSharedMemory shm(
-      base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-          std::move(region)),
-      kDataSize, false);
-}
-
-TEST(UnalignedSharedMemoryTest, CreateAndDestroyReadOnlyRegion) {
-  auto region = CreateReadOnlyRegion(kData, kDataSize);
-  UnalignedSharedMemory shm(
-      base::ReadOnlySharedMemoryRegion::TakeHandleForSerialization(
-          std::move(region)),
-      kDataSize, true);
-}
-
-TEST(UnalignedSharedMemoryTest, CreateAndDestroy_InvalidRegion) {
-  UnalignedSharedMemory shm(base::subtle::PlatformSharedMemoryRegion(),
-                            kDataSize, false);
-}
-
-TEST(UnalignedSharedMemoryTest, MapRegion) {
-  auto region = CreateRegion(kData, kDataSize);
-  UnalignedSharedMemory shm(
-      base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-          std::move(region)),
-      kDataSize, false);
-  ASSERT_TRUE(shm.MapAt(0, kDataSize));
-  EXPECT_EQ(0, memcmp(shm.memory(), kData, kDataSize));
-}
-
-TEST(UnalignedSharedMemoryTest, MapReadOnlyRegion) {
-  auto region = CreateReadOnlyRegion(kData, kDataSize);
-  UnalignedSharedMemory shm(
-      base::ReadOnlySharedMemoryRegion::TakeHandleForSerialization(
-          std::move(region)),
-      kDataSize, true);
-  ASSERT_TRUE(shm.MapAt(0, kDataSize));
-  EXPECT_EQ(0, memcmp(shm.memory(), kData, kDataSize));
-}
-
-TEST(UnalignedSharedMemoryTest, Map_UnalignedRegion) {
-  auto region = CreateRegion(kUnalignedData, kUnalignedDataSize);
-  UnalignedSharedMemory shm(
-      base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-          std::move(region)),
-      kUnalignedDataSize, false);
-  ASSERT_TRUE(shm.MapAt(kUnalignedOffset, kDataSize));
-  EXPECT_EQ(0, memcmp(shm.memory(), kData, kDataSize));
-}
-
-TEST(UnalignedSharedMemoryTest, Map_UnalignedReadOnlyRegion) {
-  auto region = CreateReadOnlyRegion(kUnalignedData, kUnalignedDataSize);
-  UnalignedSharedMemory shm(
-      base::ReadOnlySharedMemoryRegion::TakeHandleForSerialization(
-          std::move(region)),
-      kUnalignedDataSize, true);
-  ASSERT_TRUE(shm.MapAt(kUnalignedOffset, kDataSize));
-  EXPECT_EQ(0, memcmp(shm.memory(), kData, kDataSize));
-}
-
-TEST(UnalignedSharedMemoryTest, Map_InvalidRegion) {
-  UnalignedSharedMemory shm(base::subtle::PlatformSharedMemoryRegion(),
-                            kDataSize, true);
-  ASSERT_FALSE(shm.MapAt(1, kDataSize));
-  EXPECT_EQ(shm.memory(), nullptr);
-}
-
-TEST(UnalignedSharedMemoryTest, Map_NegativeOffsetRegion) {
-  auto region = CreateRegion(kData, kDataSize);
-  UnalignedSharedMemory shm(
-      base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-          std::move(region)),
-      kDataSize, false);
-  ASSERT_FALSE(shm.MapAt(-1, kDataSize));
-}
-
-TEST(UnalignedSharedMemoryTest, Map_NegativeOffsetReadOnlyRegion) {
-  auto region = CreateReadOnlyRegion(kData, kDataSize);
-  UnalignedSharedMemory shm(
-      base::ReadOnlySharedMemoryRegion::TakeHandleForSerialization(
-          std::move(region)),
-      kDataSize, true);
-  ASSERT_FALSE(shm.MapAt(-1, kDataSize));
-}
-
-TEST(UnalignedSharedMemoryTest, Map_SizeOverflowRegion) {
-  auto region = CreateRegion(kData, kDataSize);
-  UnalignedSharedMemory shm(
-      base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-          std::move(region)),
-      kDataSize, false);
-  ASSERT_FALSE(shm.MapAt(1, std::numeric_limits<size_t>::max()));
-}
-
-TEST(UnalignedSharedMemoryTest, Map_SizeOverflowReadOnlyRegion) {
-  auto region = CreateReadOnlyRegion(kData, kDataSize);
-  UnalignedSharedMemory shm(
-      base::ReadOnlySharedMemoryRegion::TakeHandleForSerialization(
-          std::move(region)),
-      kDataSize, true);
-  ASSERT_FALSE(shm.MapAt(1, std::numeric_limits<size_t>::max()));
-}
-
-TEST(UnalignedSharedMemoryTest, UnmappedRegionIsNullptr) {
-  auto region = CreateRegion(kData, kDataSize);
-  UnalignedSharedMemory shm(
-      base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-          std::move(region)),
-      kDataSize, false);
-  ASSERT_EQ(shm.memory(), nullptr);
-}
-
-TEST(UnalignedSharedMemoryTest, UnmappedReadOnlyRegionIsNullptr) {
-  auto region = CreateReadOnlyRegion(kData, kDataSize);
-  UnalignedSharedMemory shm(
-      base::ReadOnlySharedMemoryRegion::TakeHandleForSerialization(
-          std::move(region)),
-      kDataSize, true);
-  ASSERT_EQ(shm.memory(), nullptr);
-}
-
-TEST(WritableUnalignedMappingTest, CreateAndDestroy) {
-  auto region = CreateRegion(kData, kDataSize);
-  WritableUnalignedMapping shm(region, kDataSize, 0);
-  EXPECT_TRUE(shm.IsValid());
-}
-
-TEST(WritableUnalignedMappingTest, CreateAndDestroy_InvalidRegion) {
-  base::UnsafeSharedMemoryRegion region;
-  WritableUnalignedMapping shm(region, kDataSize, 0);
-  EXPECT_FALSE(shm.IsValid());
-}
-
-TEST(WritableUnalignedMappingTest, Map) {
-  auto region = CreateRegion(kData, kDataSize);
-  WritableUnalignedMapping shm(region, kDataSize, 0);
-  ASSERT_TRUE(shm.IsValid());
-  EXPECT_EQ(0, memcmp(shm.memory(), kData, kDataSize));
-}
-
-TEST(WritableUnalignedMappingTest, Map_Unaligned) {
-  auto region = CreateRegion(kUnalignedData, kUnalignedDataSize);
-  WritableUnalignedMapping shm(region, kDataSize, kUnalignedOffset);
-  ASSERT_TRUE(shm.IsValid());
-  EXPECT_EQ(0, memcmp(shm.memory(), kData, kDataSize));
-}
-
-TEST(WritableUnalignedMappingTest, Map_InvalidRegion) {
-  base::UnsafeSharedMemoryRegion region;
-  WritableUnalignedMapping shm(region, kDataSize, 0);
-  ASSERT_FALSE(shm.IsValid());
-  EXPECT_EQ(shm.memory(), nullptr);
-}
-
-TEST(WritableUnalignedMappingTest, Map_NegativeOffset) {
-  auto region = CreateRegion(kData, kDataSize);
-  WritableUnalignedMapping shm(region, kDataSize, -1);
-  ASSERT_FALSE(shm.IsValid());
-}
-
-TEST(WritableUnalignedMappingTest, Map_SizeOverflow) {
-  auto region = CreateRegion(kData, kDataSize);
-  WritableUnalignedMapping shm(region, std::numeric_limits<size_t>::max(), 1);
-  ASSERT_FALSE(shm.IsValid());
-}
-
-TEST(ReadOnlyUnalignedMappingTest, CreateAndDestroy) {
-  auto region = CreateReadOnlyRegion(kData, kDataSize);
-  ReadOnlyUnalignedMapping shm(region, kDataSize, 0);
-  EXPECT_TRUE(shm.IsValid());
-}
-
-TEST(ReadOnlyUnalignedMappingTest, CreateAndDestroy_InvalidRegion) {
-  base::ReadOnlySharedMemoryRegion region;
-  ReadOnlyUnalignedMapping shm(region, kDataSize, 0);
-  EXPECT_FALSE(shm.IsValid());
-}
-
-TEST(ReadOnlyUnalignedMappingTest, Map) {
-  auto region = CreateReadOnlyRegion(kData, kDataSize);
-  ReadOnlyUnalignedMapping shm(region, kDataSize, 0);
-  ASSERT_TRUE(shm.IsValid());
-  EXPECT_EQ(0, memcmp(shm.memory(), kData, kDataSize));
-}
-
-TEST(ReadOnlyUnalignedMappingTest, Map_Unaligned) {
-  auto region = CreateReadOnlyRegion(kUnalignedData, kUnalignedDataSize);
-  ReadOnlyUnalignedMapping shm(region, kDataSize, kUnalignedOffset);
-  ASSERT_TRUE(shm.IsValid());
-  EXPECT_EQ(0, memcmp(shm.memory(), kData, kDataSize));
-}
-
-TEST(ReadOnlyUnalignedMappingTest, Map_InvalidRegion) {
-  base::ReadOnlySharedMemoryRegion region;
-  ReadOnlyUnalignedMapping shm(region, kDataSize, 0);
-  ASSERT_FALSE(shm.IsValid());
-  EXPECT_EQ(shm.memory(), nullptr);
-}
-
-TEST(ReadOnlyUnalignedMappingTest, Map_NegativeOffset) {
-  auto region = CreateReadOnlyRegion(kData, kDataSize);
-  ReadOnlyUnalignedMapping shm(region, kDataSize, -1);
-  ASSERT_FALSE(shm.IsValid());
-}
-
-TEST(ReadOnlyUnalignedMappingTest, Map_SizeOverflow) {
-  auto region = CreateReadOnlyRegion(kData, kDataSize);
-  ReadOnlyUnalignedMapping shm(region, std::numeric_limits<size_t>::max(), 1);
-  ASSERT_FALSE(shm.IsValid());
-}
-
-}  // namespace media
diff --git a/media/gpu/android/android_video_encode_accelerator.cc b/media/gpu/android/android_video_encode_accelerator.cc
index f566c2b7..80c152e 100644
--- a/media/gpu/android/android_video_encode_accelerator.cc
+++ b/media/gpu/android/android_video_encode_accelerator.cc
@@ -11,6 +11,8 @@
 #include "base/bind.h"
 #include "base/location.h"
 #include "base/logging.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
@@ -20,7 +22,6 @@
 #include "media/base/bitstream_buffer.h"
 #include "media/base/limits.h"
 #include "media/base/media_log.h"
-#include "media/base/unaligned_shared_memory.h"
 #include "media/video/picture.h"
 #include "third_party/libyuv/include/libyuv/convert_from.h"
 #include "ui/gl/android/scoped_java_surface.h"
@@ -467,18 +468,18 @@
   BitstreamBuffer bitstream_buffer =
       std::move(available_bitstream_buffers_.back());
   available_bitstream_buffers_.pop_back();
-  auto shm = std::make_unique<UnalignedSharedMemory>(
-      bitstream_buffer.TakeRegion(), bitstream_buffer.size(), false);
-  RETURN_ON_FAILURE(
-      shm->MapAt(bitstream_buffer.offset(), bitstream_buffer.size()),
-      "Failed to map SHM", kPlatformFailureError);
+  base::UnsafeSharedMemoryRegion region = bitstream_buffer.TakeRegion();
+  auto mapping =
+      region.MapAt(bitstream_buffer.offset(), bitstream_buffer.size());
+  RETURN_ON_FAILURE(mapping.IsValid(), "Failed to map SHM",
+                    kPlatformFailureError);
   RETURN_ON_FAILURE(
       size <= bitstream_buffer.size(),
       "Encoded buffer too large: " << size << ">" << bitstream_buffer.size(),
       kPlatformFailureError);
 
-  status = media_codec_->CopyFromOutputBuffer(buf_index, offset, shm->memory(),
-                                              size);
+  status = media_codec_->CopyFromOutputBuffer(buf_index, offset,
+                                              mapping.memory(), size);
   RETURN_ON_FAILURE(status == MEDIA_CODEC_OK, "CopyFromOutputBuffer failed",
                     kPlatformFailureError);
   media_codec_->ReleaseOutputBuffer(buf_index, false);
diff --git a/media/gpu/android/ndk_video_encode_accelerator.cc b/media/gpu/android/ndk_video_encode_accelerator.cc
index ee72985..991cede 100644
--- a/media/gpu/android/ndk_video_encode_accelerator.cc
+++ b/media/gpu/android/ndk_video_encode_accelerator.cc
@@ -8,6 +8,8 @@
 #include "base/feature_list.h"
 #include "base/files/file_path.h"
 #include "base/logging.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
 #include "media/base/android/media_codec_util.h"
@@ -676,14 +678,15 @@
     return;
   }
 
-  UnalignedSharedMemory shm(bitstream_buffer.TakeRegion(),
-                            bitstream_buffer.size(), false);
-  if (!shm.MapAt(bitstream_buffer.offset(), bitstream_buffer.size())) {
+  base::UnsafeSharedMemoryRegion region = bitstream_buffer.TakeRegion();
+  auto mapping =
+      region.MapAt(bitstream_buffer.offset(), bitstream_buffer.size());
+  if (!mapping.IsValid()) {
     NotifyError("Failed to map SHM", kPlatformFailureError);
     return;
   }
 
-  uint8_t* output_dst = static_cast<uint8_t*>(shm.memory());
+  uint8_t* output_dst = mapping.GetMemoryAs<uint8_t>();
   if (config_size > 0) {
     memcpy(output_dst, config_data_.data(), config_size);
     output_dst += config_size;
@@ -702,4 +705,4 @@
                                   output_buffer.buffer_index, false);
 }
 
-}  // namespace media
\ No newline at end of file
+}  // namespace media
diff --git a/media/gpu/chromeos/vd_video_decode_accelerator.cc b/media/gpu/chromeos/vd_video_decode_accelerator.cc
index f777cad..5ba2262 100644
--- a/media/gpu/chromeos/vd_video_decode_accelerator.cc
+++ b/media/gpu/chromeos/vd_video_decode_accelerator.cc
@@ -10,6 +10,7 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/location.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "gpu/ipc/common/gpu_memory_buffer_support.h"
 #include "media/base/format_utils.h"
 #include "media/base/media_util.h"
@@ -81,8 +82,7 @@
     BitstreamBuffer bitstream_buffer) {
   // Check to see if we have our secure buffer tag and then extract the
   // decrypt parameters.
-  auto mem_region = base::UnsafeSharedMemoryRegion::Deserialize(
-      bitstream_buffer.DuplicateRegion());
+  auto mem_region = bitstream_buffer.DuplicateRegion();
   if (!mem_region.IsValid()) {
     DVLOG(2) << "Invalid shared memory region";
     return nullptr;
diff --git a/media/gpu/ipc/common/media_param_traits.cc b/media/gpu/ipc/common/media_param_traits.cc
index 334d9d45..e7f057ac 100644
--- a/media/gpu/ipc/common/media_param_traits.cc
+++ b/media/gpu/ipc/common/media_param_traits.cc
@@ -16,8 +16,7 @@
                                                 const param_type& p) {
   WriteParam(m, p.id());
   WriteParam(m, static_cast<uint64_t>(p.size()));
-  DCHECK_GE(p.offset(), 0);
-  WriteParam(m, static_cast<uint64_t>(p.offset()));
+  WriteParam(m, p.offset());
   WriteParam(m, p.presentation_timestamp());
   WriteParam(m, p.key_id());
   if (!p.key_id().empty()) {
@@ -32,9 +31,8 @@
                                                param_type* r) {
   DCHECK(r);
   uint64_t size = 0;
-  uint64_t offset = 0;
   if (!(ReadParam(m, iter, &r->id_) && ReadParam(m, iter, &size) &&
-        ReadParam(m, iter, &offset) &&
+        ReadParam(m, iter, &r->offset_) &&
         ReadParam(m, iter, &r->presentation_timestamp_) &&
         ReadParam(m, iter, &r->key_id_)))
     return false;
@@ -46,13 +44,6 @@
   }
   r->size_ = checked_size.ValueOrDie();
 
-  base::CheckedNumeric<off_t> checked_offset(offset);
-  if (!checked_offset.IsValid()) {
-    DLOG(ERROR) << "Invalid offset: " << offset;
-    return false;
-  }
-  r->offset_ = checked_offset.ValueOrDie();
-
   if (!r->key_id_.empty()) {
     if (!(ReadParam(m, iter, &r->iv_) && ReadParam(m, iter, &r->subsamples_)))
       return false;
diff --git a/media/gpu/mac/vt_video_encode_accelerator_mac.cc b/media/gpu/mac/vt_video_encode_accelerator_mac.cc
index 6a3a7771..0b622f1 100644
--- a/media/gpu/mac/vt_video_encode_accelerator_mac.cc
+++ b/media/gpu/mac/vt_video_encode_accelerator_mac.cc
@@ -257,8 +257,7 @@
     return;
   }
 
-  auto mapping =
-      base::UnsafeSharedMemoryRegion::Deserialize(buffer.TakeRegion()).Map();
+  auto mapping = buffer.TakeRegion().Map();
   if (!mapping.IsValid()) {
     DLOG(ERROR) << "Failed mapping shared memory.";
     client_->NotifyError(kPlatformFailureError);
diff --git a/media/gpu/test/video_encoder/video_encoder_client.cc b/media/gpu/test/video_encoder/video_encoder_client.cc
index 40e74083..b190d0b 100644
--- a/media/gpu/test/video_encoder/video_encoder_client.cc
+++ b/media/gpu/test/video_encoder/video_encoder_client.cc
@@ -352,9 +352,7 @@
   auto it = bitstream_buffers_.find(bitstream_buffer_id);
   LOG_ASSERT(it != bitstream_buffers_.end());
   auto decoder_buffer = DecoderBuffer::FromSharedMemoryRegion(
-      base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-          it->second.Duplicate()),
-      0u /* offset */, metadata.payload_size_bytes);
+      it->second.Duplicate(), 0u /* offset */, metadata.payload_size_bytes);
   if (!decoder_buffer)
     return nullptr;
   decoder_buffer->set_timestamp(base::Microseconds(frame_index_));
diff --git a/media/gpu/v4l2/v4l2_jpeg_encode_accelerator.cc b/media/gpu/v4l2/v4l2_jpeg_encode_accelerator.cc
index fd67d728..1ef8f3f 100644
--- a/media/gpu/v4l2/v4l2_jpeg_encode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_jpeg_encode_accelerator.cc
@@ -69,37 +69,24 @@
     scoped_refptr<VideoFrame> output_frame,
     int quality,
     int32_t task_id,
-    BitstreamBuffer* exif_buffer)
+    base::WritableSharedMemoryMapping exif_mapping)
     : input_frame(input_frame),
       output_frame(output_frame),
       quality(quality),
       task_id(task_id),
-      output_shm(base::subtle::PlatformSharedMemoryRegion(), 0, true),  // dummy
-      exif_shm(nullptr) {
-  if (exif_buffer) {
-    exif_shm.reset(new UnalignedSharedMemory(exif_buffer->TakeRegion(),
-                                             exif_buffer->size(), false));
-    exif_offset = exif_buffer->offset();
-  }
-}
+      exif_mapping(std::move(exif_mapping)) {}
 
 V4L2JpegEncodeAccelerator::JobRecord::JobRecord(
     scoped_refptr<VideoFrame> input_frame,
     int quality,
-    BitstreamBuffer* exif_buffer,
-    BitstreamBuffer output_buffer)
+    int32_t task_id,
+    base::WritableSharedMemoryMapping exif_mapping,
+    base::WritableSharedMemoryMapping output_mapping)
     : input_frame(input_frame),
       quality(quality),
-      task_id(output_buffer.id()),
-      output_shm(output_buffer.TakeRegion(), output_buffer.size(), false),
-      output_offset(output_buffer.offset()),
-      exif_shm(nullptr) {
-  if (exif_buffer) {
-    exif_shm.reset(new UnalignedSharedMemory(exif_buffer->TakeRegion(),
-                                             exif_buffer->size(), false));
-    exif_offset = exif_buffer->offset();
-  }
-}
+      task_id(task_id),
+      output_mapping(std::move(output_mapping)),
+      exif_mapping(std::move(exif_mapping)) {}
 
 V4L2JpegEncodeAccelerator::JobRecord::~JobRecord() {}
 
@@ -769,7 +756,7 @@
     uint8_t* dst_ptr,
     const JpegBufferRecord& output_buffer,
     size_t buffer_size,
-    std::unique_ptr<UnalignedSharedMemory> exif_shm) {
+    base::WritableSharedMemoryMapping exif_mapping) {
   DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
   size_t idx;
 
@@ -778,9 +765,9 @@
   dst_ptr[1] = JPEG_SOI;
   idx = 2;
 
-  if (exif_shm) {
-    uint8_t* exif_buffer = static_cast<uint8_t*>(exif_shm->memory());
-    size_t exif_buffer_size = exif_shm->size();
+  if (exif_mapping.IsValid()) {
+    uint8_t* exif_buffer = exif_mapping.GetMemoryAs<uint8_t>();
+    size_t exif_buffer_size = exif_mapping.size();
     // Application Segment for Exif data.
     uint16_t exif_segment_size = static_cast<uint16_t>(exif_buffer_size + 2);
     const uint8_t kAppSegment[] = {
@@ -914,8 +901,8 @@
     }
 
     size_t jpeg_size = FinalizeJpegImage(
-        static_cast<uint8_t*>(job_record->output_shm.memory()), output_record,
-        planes[0].bytesused, std::move(job_record->exif_shm));
+        job_record->output_mapping.GetMemoryAs<uint8_t>(), output_record,
+        planes[0].bytesused, std::move(job_record->exif_mapping));
     if (!jpeg_size) {
       NotifyError(job_record->task_id, PLATFORM_FAILURE);
       return;
@@ -1575,7 +1562,7 @@
 size_t V4L2JpegEncodeAccelerator::EncodedInstanceDmaBuf::FinalizeJpegImage(
     scoped_refptr<VideoFrame> output_frame,
     size_t buffer_size,
-    std::unique_ptr<UnalignedSharedMemory> exif_shm) {
+    base::WritableSharedMemoryMapping exif_mapping) {
   DCHECK(parent_->encoder_task_runner_->BelongsToCurrentThread());
   size_t idx = 0;
 
@@ -1605,9 +1592,9 @@
   // Fill SOI and EXIF markers.
   static const uint8_t kJpegStart[] = {0xFF, JPEG_SOI};
 
-  if (exif_shm) {
-    uint8_t* exif_buffer = static_cast<uint8_t*>(exif_shm->memory());
-    size_t exif_buffer_size = exif_shm->size();
+  if (exif_mapping.IsValid()) {
+    uint8_t* exif_buffer = exif_mapping.GetMemoryAs<uint8_t>();
+    size_t exif_buffer_size = exif_mapping.size();
     // Application Segment for Exif data.
     uint16_t exif_segment_size = static_cast<uint16_t>(exif_buffer_size + 2);
     const uint8_t kAppSegment[] = {
@@ -1795,7 +1782,7 @@
 
     size_t jpeg_size =
         FinalizeJpegImage(job_record->output_frame, planes[0].bytesused,
-                          std::move(job_record->exif_shm));
+                          std::move(job_record->exif_mapping));
 
     if (!jpeg_size) {
       NotifyError(job_record->task_id, PLATFORM_FAILURE);
@@ -1942,16 +1929,36 @@
     return;
   }
 
+  base::WritableSharedMemoryMapping exif_mapping;
   if (exif_buffer) {
     VLOGF(4) << "EXIF size " << exif_buffer->size();
     if (exif_buffer->size() > kMaxMarkerSizeAllowed) {
       NotifyError(output_buffer.id(), INVALID_ARGUMENT);
       return;
     }
+
+    base::UnsafeSharedMemoryRegion exif_region = exif_buffer->TakeRegion();
+    exif_mapping =
+        exif_region.MapAt(exif_buffer->offset(), exif_buffer->size());
+    if (!exif_mapping.IsValid()) {
+      VPLOGF(1) << "could not map exif bitstream_buffer";
+      NotifyError(output_buffer.id(), PLATFORM_FAILURE);
+      return;
+    }
   }
 
-  std::unique_ptr<JobRecord> job_record(new JobRecord(
-      video_frame, quality, exif_buffer, std::move(output_buffer)));
+  base::UnsafeSharedMemoryRegion output_region = output_buffer.TakeRegion();
+  base::WritableSharedMemoryMapping output_mapping =
+      output_region.MapAt(output_buffer.offset(), output_buffer.size());
+  if (!output_mapping.IsValid()) {
+    VPLOGF(1) << "could not map I420 bitstream_buffer";
+    NotifyError(output_buffer.id(), PLATFORM_FAILURE);
+    return;
+  }
+
+  std::unique_ptr<JobRecord> job_record(
+      new JobRecord(video_frame, quality, output_buffer.id(),
+                    std::move(exif_mapping), std::move(output_mapping)));
 
   encoder_task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&V4L2JpegEncodeAccelerator::EncodeTaskLegacy,
@@ -1978,16 +1985,26 @@
     return;
   }
 
+  base::WritableSharedMemoryMapping exif_mapping;
   if (exif_buffer) {
     VLOGF(4) << "EXIF size " << exif_buffer->size();
     if (exif_buffer->size() > kMaxMarkerSizeAllowed) {
       NotifyError(task_id, INVALID_ARGUMENT);
       return;
     }
+
+    base::UnsafeSharedMemoryRegion exif_region = exif_buffer->TakeRegion();
+    exif_mapping =
+        exif_region.MapAt(exif_buffer->offset(), exif_buffer->size());
+    if (!exif_mapping.IsValid()) {
+      VPLOGF(1) << "could not map exif bitstream_buffer";
+      NotifyError(task_id, PLATFORM_FAILURE);
+      return;
+    }
   }
 
-  std::unique_ptr<JobRecord> job_record(
-      new JobRecord(input_frame, output_frame, quality, task_id, exif_buffer));
+  std::unique_ptr<JobRecord> job_record(new JobRecord(
+      input_frame, output_frame, quality, task_id, std::move(exif_mapping)));
 
   encoder_task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&V4L2JpegEncodeAccelerator::EncodeTask,
@@ -1997,19 +2014,6 @@
 void V4L2JpegEncodeAccelerator::EncodeTaskLegacy(
     std::unique_ptr<JobRecord> job_record) {
   DCHECK(encoder_task_runner_->BelongsToCurrentThread());
-  if (!job_record->output_shm.MapAt(job_record->output_offset,
-                                    job_record->output_shm.size())) {
-    VPLOGF(1) << "could not map I420 bitstream_buffer";
-    NotifyError(job_record->task_id, PLATFORM_FAILURE);
-    return;
-  }
-  if (job_record->exif_shm &&
-      !job_record->exif_shm->MapAt(job_record->exif_offset,
-                                   job_record->exif_shm->size())) {
-    VPLOGF(1) << "could not map exif bitstream_buffer";
-    NotifyError(job_record->task_id, PLATFORM_FAILURE);
-    return;
-  }
 
   // Check if the parameters of input frame changes.
   // If it changes, we open a new device and put the job in it.
@@ -2034,7 +2038,7 @@
     }
 
     if (!encoded_device->CreateBuffers(coded_size,
-                                       job_record->output_shm.size())) {
+                                       job_record->output_mapping.size())) {
       VLOGF(1) << "Create buffers failed.";
       NotifyError(job_record->task_id, PLATFORM_FAILURE);
       return;
@@ -2055,13 +2059,6 @@
 void V4L2JpegEncodeAccelerator::EncodeTask(
     std::unique_ptr<JobRecord> job_record) {
   DCHECK(encoder_task_runner_->BelongsToCurrentThread());
-  if (job_record->exif_shm &&
-      !job_record->exif_shm->MapAt(job_record->exif_offset,
-                                   job_record->exif_shm->size())) {
-    VPLOGF(1) << "could not map exif bitstream_buffer";
-    NotifyError(job_record->task_id, PLATFORM_FAILURE);
-    return;
-  }
 
   // Check if the parameters of input frame changes.
   // If it changes, we open a new device and put the job in it.
diff --git a/media/gpu/v4l2/v4l2_jpeg_encode_accelerator.h b/media/gpu/v4l2/v4l2_jpeg_encode_accelerator.h
index 8a94b61..b31ef0e2 100644
--- a/media/gpu/v4l2/v4l2_jpeg_encode_accelerator.h
+++ b/media/gpu/v4l2/v4l2_jpeg_encode_accelerator.h
@@ -13,13 +13,13 @@
 
 #include "base/containers/queue.h"
 #include "base/memory/ref_counted.h"
+#include "base/memory/shared_memory_mapping.h"
 #include "base/memory/weak_ptr.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread.h"
 #include "components/chromeos_camera/jpeg_encode_accelerator.h"
 #include "gpu/ipc/common/gpu_memory_buffer_support.h"
 #include "media/base/bitstream_buffer.h"
-#include "media/base/unaligned_shared_memory.h"
 #include "media/base/video_frame.h"
 #include "media/gpu/media_gpu_export.h"
 #include "media/gpu/v4l2/v4l2_device.h"
@@ -113,11 +113,12 @@
               scoped_refptr<VideoFrame> output_frame,
               int32_t task_id,
               int quality,
-              BitstreamBuffer* exif_buffer);
+              base::WritableSharedMemoryMapping exif_mapping);
     JobRecord(scoped_refptr<VideoFrame> input_frame,
               int quality,
-              BitstreamBuffer* exif_buffer,
-              BitstreamBuffer output_buffer);
+              int32_t task_id,
+              base::WritableSharedMemoryMapping exif_mapping,
+              base::WritableSharedMemoryMapping output_mapping);
     ~JobRecord();
 
     // Input frame buffer.
@@ -132,16 +133,12 @@
     // Encode task ID.
     int32_t task_id;
     // Memory mapped from |output_buffer|.
-    UnalignedSharedMemory output_shm;
-    // Offset used for |output_shm|.
-    off_t output_offset;
+    base::WritableSharedMemoryMapping output_mapping;
 
     // Memory mapped from |exif_buffer|.
-    // It contains EXIF data to be inserted into JPEG image. If it's nullptr,
-    // the JFIF APP0 segment will be inserted.
-    std::unique_ptr<UnalignedSharedMemory> exif_shm;
-    // Offset used for |exif_shm|.
-    off_t exif_offset;
+    // It contains EXIF data to be inserted into JPEG image. If `IsValid()` is
+    // false, the JFIF APP0 segment will be inserted.
+    base::WritableSharedMemoryMapping exif_mapping;
   };
 
   // TODO(wtlee): To be deprecated. (crbug.com/944705)
@@ -183,7 +180,7 @@
     size_t FinalizeJpegImage(uint8_t* dst_ptr,
                              const JpegBufferRecord& output_buffer,
                              size_t buffer_size,
-                             std::unique_ptr<UnalignedSharedMemory> exif_shm);
+                             base::WritableSharedMemoryMapping exif_mapping);
 
     bool SetInputBufferFormat(gfx::Size coded_size);
     bool SetOutputBufferFormat(gfx::Size coded_size, size_t buffer_size);
@@ -300,7 +297,7 @@
     // Add JPEG Marks if needed. Add EXIF section by |exif_shm|.
     size_t FinalizeJpegImage(scoped_refptr<VideoFrame> output_frame,
                              size_t buffer_size,
-                             std::unique_ptr<UnalignedSharedMemory> exif_shm);
+                             base::WritableSharedMemoryMapping exif_mapping);
 
     bool SetInputBufferFormat(gfx::Size coded_size,
                               const VideoFrameLayout& input_layout);
diff --git a/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.cc b/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.cc
index 1fe2660..d6624d6 100644
--- a/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.cc
@@ -18,11 +18,12 @@
 #include "base/callback_helpers.h"
 #include "base/files/scoped_file.h"
 #include "base/memory/page_size.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "media/base/bind_to_current_loop.h"
 #include "media/base/bitstream_buffer.h"
-#include "media/base/unaligned_shared_memory.h"
 #include "media/base/video_frame.h"
 #include "media/base/video_types.h"
 #include "media/gpu/chromeos/fourcc.h"
@@ -145,7 +146,7 @@
   // Input buffer size.
   virtual size_t size() const = 0;
   // Input buffer offset.
-  virtual off_t offset() const = 0;
+  virtual uint64_t offset() const = 0;
   // Maps input buffer.
   virtual bool map() = 0;
   // Pointer to the input content. Only valid if map() is already called.
@@ -164,9 +165,7 @@
   JobRecordBitstreamBuffer(BitstreamBuffer bitstream_buffer,
                            scoped_refptr<VideoFrame> video_frame)
       : task_id_(bitstream_buffer.id()),
-        shm_(bitstream_buffer.TakeRegion(),
-             bitstream_buffer.size(),
-             false /* read_only */),
+        shm_region_(bitstream_buffer.TakeRegion()),
         offset_(bitstream_buffer.offset()),
         out_frame_(video_frame) {}
 
@@ -174,17 +173,21 @@
   JobRecordBitstreamBuffer& operator=(const JobRecordBitstreamBuffer&) = delete;
 
   int32_t task_id() const override { return task_id_; }
-  size_t size() const override { return shm_.size(); }
-  off_t offset() const override { return offset_; }
-  bool map() override { return shm_.MapAt(offset(), size()); }
-  const void* memory() const override { return shm_.memory(); }
+  size_t size() const override { return shm_region_.GetSize(); }
+  uint64_t offset() const override { return offset_; }
+  bool map() override {
+    shm_mapping_ = shm_region_.MapAt(offset(), size());
+    return shm_mapping_.IsValid();
+  }
+  const void* memory() const override { return shm_mapping_.memory(); }
 
   const scoped_refptr<VideoFrame>& out_frame() override { return out_frame_; }
 
  private:
   int32_t task_id_;
-  UnalignedSharedMemory shm_;
-  off_t offset_;
+  base::UnsafeSharedMemoryRegion shm_region_;
+  uint64_t offset_;
+  base::WritableSharedMemoryMapping shm_mapping_;
   scoped_refptr<VideoFrame> out_frame_;
 };
 
@@ -215,7 +218,7 @@
 
   int32_t task_id() const override { return task_id_; }
   size_t size() const override { return size_; }
-  off_t offset() const override { return offset_; }
+  uint64_t offset() const override { return offset_; }
 
   bool map() override {
     if (mapped_addr_)
@@ -225,7 +228,7 @@
     DCHECK(dmabuf_fd_.is_valid());
     DCHECK_GT(size(), 0u);
     void* addr = mmap(nullptr, size(), PROT_READ, MAP_SHARED, dmabuf_fd_.get(),
-                      offset());
+                      base::checked_cast<off_t>(offset()));
     if (addr == MAP_FAILED)
       return false;
     mapped_addr_ = addr;
@@ -243,7 +246,7 @@
   int32_t task_id_;
   base::ScopedFD dmabuf_fd_;
   size_t size_;
-  off_t offset_;
+  uint64_t offset_;
   void* mapped_addr_;
   scoped_refptr<VideoFrame> out_frame_;
 };
diff --git a/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc b/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc
index 7c5f91a..e1f4879 100644
--- a/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_slice_video_decode_accelerator.cc
@@ -32,7 +32,6 @@
 #include "media/base/bind_to_current_loop.h"
 #include "media/base/media_switches.h"
 #include "media/base/scopedfd_helper.h"
-#include "media/base/unaligned_shared_memory.h"
 #include "media/base/video_types.h"
 #include "media/base/video_util.h"
 #include "media/gpu/chromeos/fourcc.h"
diff --git a/media/gpu/v4l2/v4l2_video_decode_accelerator.cc b/media/gpu/v4l2/v4l2_video_decode_accelerator.cc
index c920940..f39cc56 100644
--- a/media/gpu/v4l2/v4l2_video_decode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_video_decode_accelerator.cc
@@ -28,7 +28,6 @@
 #include "build/build_config.h"
 #include "media/base/media_switches.h"
 #include "media/base/scopedfd_helper.h"
-#include "media/base/unaligned_shared_memory.h"
 #include "media/base/video_frame_layout.h"
 #include "media/base/video_types.h"
 #include "media/gpu/chromeos/fourcc.h"
diff --git a/media/gpu/v4l2/v4l2_video_encode_accelerator.cc b/media/gpu/v4l2/v4l2_video_encode_accelerator.cc
index d602c030..de980ae 100644
--- a/media/gpu/v4l2/v4l2_video_encode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_video_encode_accelerator.cc
@@ -21,6 +21,8 @@
 #include "base/callback.h"
 #include "base/callback_helpers.h"
 #include "base/command_line.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/task/task_traits.h"
@@ -31,7 +33,6 @@
 #include "media/base/color_plane_layout.h"
 #include "media/base/media_log.h"
 #include "media/base/scopedfd_helper.h"
-#include "media/base/unaligned_shared_memory.h"
 #include "media/base/video_frame_layout.h"
 #include "media/base/video_types.h"
 #include "media/gpu/chromeos/fourcc.h"
@@ -128,10 +129,10 @@
 }  // namespace
 
 struct V4L2VideoEncodeAccelerator::BitstreamBufferRef {
-  BitstreamBufferRef(int32_t id, std::unique_ptr<UnalignedSharedMemory> shm)
-      : id(id), shm(std::move(shm)) {}
+  BitstreamBufferRef(int32_t id, base::WritableSharedMemoryMapping shm_mapping)
+      : id(id), shm_mapping(std::move(shm_mapping)) {}
   const int32_t id;
-  const std::unique_ptr<UnalignedSharedMemory> shm;
+  base::WritableSharedMemoryMapping shm_mapping;
 };
 
 V4L2VideoEncodeAccelerator::InputRecord::InputRecord() = default;
@@ -648,8 +649,8 @@
     std::unique_ptr<BitstreamBufferRef> buffer_ref) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
 
-  uint8_t* dst_ptr = static_cast<uint8_t*>(buffer_ref->shm->memory());
-  size_t remaining_dst_size = buffer_ref->shm->size();
+  uint8_t* dst_ptr = buffer_ref->shm_mapping.GetMemoryAs<uint8_t>();
+  size_t remaining_dst_size = buffer_ref->shm_mapping.size();
 
   if (!inject_sps_and_pps_) {
     if (bitstream_size <= remaining_dst_size) {
@@ -726,7 +727,7 @@
                                 &remaining_dst_size);
   }
 
-  return buffer_ref->shm->size() - remaining_dst_size;
+  return buffer_ref->shm_mapping.size() - remaining_dst_size;
 }
 
 void V4L2VideoEncodeAccelerator::EncodeTask(scoped_refptr<VideoFrame> frame,
@@ -926,15 +927,17 @@
     NOTIFY_ERROR(kInvalidArgumentError);
     return;
   }
-  auto shm = std::make_unique<UnalignedSharedMemory>(buffer.TakeRegion(),
-                                                     buffer.size(), false);
-  if (!shm->MapAt(buffer.offset(), buffer.size())) {
+
+  base::UnsafeSharedMemoryRegion shm_region = buffer.TakeRegion();
+  base::WritableSharedMemoryMapping shm_mapping =
+      shm_region.MapAt(buffer.offset(), buffer.size());
+  if (!shm_mapping.IsValid()) {
     NOTIFY_ERROR(kPlatformFailureError);
     return;
   }
 
-  bitstream_buffer_pool_.push_back(
-      std::make_unique<BitstreamBufferRef>(buffer.id(), std::move(shm)));
+  bitstream_buffer_pool_.push_back(std::make_unique<BitstreamBufferRef>(
+      buffer.id(), std::move(shm_mapping)));
   PumpBitstreamBuffers();
 
   if (encoder_state_ == kInitialized) {
diff --git a/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.cc b/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.cc
index 8c2788d..72993a3a 100644
--- a/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.cc
+++ b/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.cc
@@ -12,7 +12,8 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/logging.h"
-#include "base/memory/writable_shared_memory_region.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/numerics/safe_conversions.h"
@@ -51,13 +52,13 @@
 VaapiJpegEncodeAccelerator::EncodeRequest::EncodeRequest(
     int32_t task_id,
     scoped_refptr<VideoFrame> video_frame,
-    std::unique_ptr<UnalignedSharedMemory> exif_shm,
-    std::unique_ptr<UnalignedSharedMemory> output_shm,
+    base::WritableSharedMemoryMapping exif_mapping,
+    base::WritableSharedMemoryMapping output_mapping,
     int quality)
     : task_id(task_id),
       video_frame(std::move(video_frame)),
-      exif_shm(std::move(exif_shm)),
-      output_shm(std::move(output_shm)),
+      exif_mapping(std::move(exif_mapping)),
+      output_mapping(std::move(output_mapping)),
       quality(quality) {}
 
 VaapiJpegEncodeAccelerator::EncodeRequest::~EncodeRequest() {}
@@ -75,12 +76,11 @@
   ~Encoder();
 
   // Processes one encode task with DMA-buf.
-  void EncodeWithDmaBufTask(
-      scoped_refptr<VideoFrame> input_frame,
-      scoped_refptr<VideoFrame> output_frame,
-      int32_t task_id,
-      int quality,
-      std::unique_ptr<WritableUnalignedMapping> exif_mapping);
+  void EncodeWithDmaBufTask(scoped_refptr<VideoFrame> input_frame,
+                            scoped_refptr<VideoFrame> output_frame,
+                            int32_t task_id,
+                            int quality,
+                            base::WritableSharedMemoryMapping exif_mapping);
 
   // Processes one encode |request|.
   void EncodeTask(std::unique_ptr<EncodeRequest> request);
@@ -137,7 +137,7 @@
     scoped_refptr<VideoFrame> output_frame,
     int32_t task_id,
     int quality,
-    std::unique_ptr<WritableUnalignedMapping> exif_mapping) {
+    base::WritableSharedMemoryMapping exif_mapping) {
   DVLOGF(4);
   TRACE_EVENT0("jpeg", "EncodeWithDmaBufTask");
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -222,9 +222,9 @@
   // Prepare exif.
   const uint8_t* exif_buffer = nullptr;
   size_t exif_buffer_size = 0;
-  if (exif_mapping) {
-    exif_buffer = static_cast<const uint8_t*>(exif_mapping->memory());
-    exif_buffer_size = exif_mapping->size();
+  if (exif_mapping.IsValid()) {
+    exif_buffer = exif_mapping.GetMemoryAs<uint8_t>();
+    exif_buffer_size = exif_mapping.size();
   }
 
   if (!jpeg_encoder_->Encode(input_size, /*exif_buffer=*/nullptr,
@@ -410,9 +410,9 @@
 
   uint8_t* exif_buffer = nullptr;
   size_t exif_buffer_size = 0;
-  if (request->exif_shm) {
-    exif_buffer = static_cast<uint8_t*>(request->exif_shm->memory());
-    exif_buffer_size = request->exif_shm->size();
+  if (request->exif_mapping.IsValid()) {
+    exif_buffer = request->exif_mapping.GetMemoryAs<uint8_t>();
+    exif_buffer_size = request->exif_mapping.size();
   }
 
   // When the exif buffer contains a thumbnail, the VAAPI encoder would
@@ -435,15 +435,15 @@
   size_t encoded_size = 0;
   if (!vaapi_wrapper_->DownloadFromVABuffer(
           cached_output_buffer_->id(), va_surface_id_,
-          static_cast<uint8_t*>(request->output_shm->memory()),
-          request->output_shm->size(), &encoded_size)) {
+          request->output_mapping.GetMemoryAs<uint8_t>(),
+          request->output_mapping.size(), &encoded_size)) {
     VLOGF(1) << "Failed to retrieve output image from VA coded buffer";
     notify_error_cb_.Run(task_id, PLATFORM_FAILURE);
     return;
   }
 
   // Copy the real exif buffer into preserved space.
-  memcpy(static_cast<uint8_t*>(request->output_shm->memory()) + exif_offset,
+  memcpy(request->output_mapping.GetMemoryAs<uint8_t>() + exif_offset,
          exif_buffer, exif_buffer_size);
 
   video_frame_ready_cb_.Run(task_id, encoded_size);
@@ -613,20 +613,20 @@
     return;
   }
 
-  std::unique_ptr<UnalignedSharedMemory> exif_shm;
+  base::WritableSharedMemoryMapping exif_mapping;
   if (exif_buffer) {
-    // |exif_shm| will take ownership of the |exif_buffer->region()|.
-    exif_shm = std::make_unique<UnalignedSharedMemory>(
-        exif_buffer->TakeRegion(), exif_buffer->size(), false);
-    if (!exif_shm->MapAt(exif_buffer->offset(), exif_buffer->size())) {
+    base::UnsafeSharedMemoryRegion exif_region = exif_buffer->TakeRegion();
+    exif_mapping =
+        exif_region.MapAt(exif_buffer->offset(), exif_buffer->size());
+    if (!exif_mapping.IsValid()) {
       VLOGF(1) << "Failed to map exif buffer";
       task_runner_->PostTask(
           FROM_HERE, base::BindOnce(&VaapiJpegEncodeAccelerator::NotifyError,
                                     weak_this_, task_id, PLATFORM_FAILURE));
       return;
     }
-    if (exif_shm->size() > kMaxMarkerSizeAllowed) {
-      VLOGF(1) << "Exif buffer too big: " << exif_shm->size();
+    if (exif_mapping.size() > kMaxMarkerSizeAllowed) {
+      VLOGF(1) << "Exif buffer too big: " << exif_mapping.size();
       task_runner_->PostTask(
           FROM_HERE, base::BindOnce(&VaapiJpegEncodeAccelerator::NotifyError,
                                     weak_this_, task_id, INVALID_ARGUMENT));
@@ -634,10 +634,10 @@
     }
   }
 
-  // |output_shm| will take ownership of the |output_buffer.handle()|.
-  auto output_shm = std::make_unique<UnalignedSharedMemory>(
-      output_buffer.TakeRegion(), output_buffer.size(), false);
-  if (!output_shm->MapAt(output_buffer.offset(), output_buffer.size())) {
+  base::UnsafeSharedMemoryRegion output_region = output_buffer.TakeRegion();
+  base::WritableSharedMemoryMapping output_mapping =
+      output_region.MapAt(output_buffer.offset(), output_buffer.size());
+  if (!output_mapping.IsValid()) {
     VLOGF(1) << "Failed to map output buffer";
     task_runner_->PostTask(
         FROM_HERE,
@@ -647,8 +647,8 @@
   }
 
   auto request = std::make_unique<EncodeRequest>(
-      task_id, std::move(video_frame), std::move(exif_shm),
-      std::move(output_shm), quality);
+      task_id, std::move(video_frame), std::move(exif_mapping),
+      std::move(output_mapping), quality);
 
   encoder_task_runner_->PostTask(
       FROM_HERE,
@@ -682,21 +682,20 @@
     return;
   }
 
-  std::unique_ptr<WritableUnalignedMapping> exif_mapping;
+  base::WritableSharedMemoryMapping exif_mapping;
   if (exif_buffer) {
-    // |exif_mapping| will take ownership of the |exif_buffer->region()|.
-    exif_mapping = std::make_unique<WritableUnalignedMapping>(
-        base::UnsafeSharedMemoryRegion::Deserialize(exif_buffer->TakeRegion()),
-        exif_buffer->size(), exif_buffer->offset());
-    if (!exif_mapping->IsValid()) {
+    base::UnsafeSharedMemoryRegion exif_region = exif_buffer->TakeRegion();
+    exif_mapping =
+        exif_region.MapAt(exif_buffer->offset(), exif_buffer->size());
+    if (!exif_mapping.IsValid()) {
       LOG(ERROR) << "Failed to map exif buffer";
       task_runner_->PostTask(
           FROM_HERE, base::BindOnce(&VaapiJpegEncodeAccelerator::NotifyError,
                                     weak_this_, task_id, PLATFORM_FAILURE));
       return;
     }
-    if (exif_mapping->size() > kMaxMarkerSizeAllowed) {
-      LOG(ERROR) << "Exif buffer too big: " << exif_mapping->size();
+    if (exif_mapping.size() > kMaxMarkerSizeAllowed) {
+      LOG(ERROR) << "Exif buffer too big: " << exif_mapping.size();
       task_runner_->PostTask(
           FROM_HERE, base::BindOnce(&VaapiJpegEncodeAccelerator::NotifyError,
                                     weak_this_, task_id, INVALID_ARGUMENT));
diff --git a/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.h b/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.h
index 94c2af1..ab8932c 100644
--- a/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.h
+++ b/media/gpu/vaapi/vaapi_jpeg_encode_accelerator.h
@@ -7,12 +7,12 @@
 
 #include <memory>
 
+#include "base/memory/shared_memory_mapping.h"
 #include "base/memory/weak_ptr.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread.h"
 #include "components/chromeos_camera/jpeg_encode_accelerator.h"
 #include "media/base/bitstream_buffer.h"
-#include "media/base/unaligned_shared_memory.h"
 #include "media/gpu/media_gpu_export.h"
 #include "media/gpu/vaapi/vaapi_wrapper.h"
 
@@ -63,8 +63,8 @@
   struct EncodeRequest {
     EncodeRequest(int32_t task_id,
                   scoped_refptr<VideoFrame> video_frame,
-                  std::unique_ptr<UnalignedSharedMemory> exif_shm,
-                  std::unique_ptr<UnalignedSharedMemory> output_shm,
+                  base::WritableSharedMemoryMapping exif_mapping,
+                  base::WritableSharedMemoryMapping output_mapping,
                   int quality);
 
     EncodeRequest(const EncodeRequest&) = delete;
@@ -74,8 +74,8 @@
 
     int32_t task_id;
     scoped_refptr<VideoFrame> video_frame;
-    std::unique_ptr<UnalignedSharedMemory> exif_shm;
-    std::unique_ptr<UnalignedSharedMemory> output_shm;
+    base::WritableSharedMemoryMapping exif_mapping;
+    base::WritableSharedMemoryMapping output_mapping;
     int quality;
   };
 
diff --git a/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.cc b/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.cc
index 4a8d771..e56098a 100644
--- a/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.cc
+++ b/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.cc
@@ -17,6 +17,8 @@
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/memory/page_size.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/numerics/checked_math.h"
 #include "base/numerics/safe_conversions.h"
@@ -27,7 +29,6 @@
 #include "media/base/bind_to_current_loop.h"
 #include "media/base/bitstream_buffer.h"
 #include "media/base/format_utils.h"
-#include "media/base/unaligned_shared_memory.h"
 #include "media/base/video_frame.h"
 #include "media/base/video_frame_layout.h"
 #include "media/base/video_util.h"
@@ -404,14 +405,13 @@
 
 void VaapiMjpegDecodeAccelerator::DecodeFromShmTask(
     int32_t task_id,
-    std::unique_ptr<UnalignedSharedMemory> shm,
+    base::WritableSharedMemoryMapping mapping,
     scoped_refptr<VideoFrame> dst_frame) {
   DVLOGF(4);
   DCHECK(decoder_task_runner_->BelongsToCurrentThread());
   TRACE_EVENT0("jpeg", __func__);
 
-  auto src_image =
-      base::make_span(static_cast<const uint8_t*>(shm->memory()), shm->size());
+  auto src_image = mapping.GetMemoryAsSpan<uint8_t>();
   DecodeImpl(task_id, src_image, std::move(dst_frame));
 }
 
@@ -577,12 +577,10 @@
     return;
   }
 
-  // UnalignedSharedMemory will take over the |bitstream_buffer.handle()|.
-  auto shm = std::make_unique<UnalignedSharedMemory>(
-      bitstream_buffer.TakeRegion(), bitstream_buffer.size(),
-      false /* read_only */);
-
-  if (!shm->MapAt(bitstream_buffer.offset(), bitstream_buffer.size())) {
+  auto region = bitstream_buffer.TakeRegion();
+  auto mapping =
+      region.MapAt(bitstream_buffer.offset(), bitstream_buffer.size());
+  if (!mapping.IsValid()) {
     VLOGF(1) << "Failed to map input buffer";
     NotifyError(bitstream_buffer.id(), UNREADABLE_INPUT);
     return;
@@ -593,7 +591,7 @@
   decoder_task_runner_->PostTask(
       FROM_HERE, base::BindOnce(&VaapiMjpegDecodeAccelerator::DecodeFromShmTask,
                                 base::Unretained(this), bitstream_buffer.id(),
-                                std::move(shm), std::move(video_frame)));
+                                std::move(mapping), std::move(video_frame)));
 }
 
 void VaapiMjpegDecodeAccelerator::Decode(int32_t task_id,
diff --git a/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.h b/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.h
index 0821d5c..9d420769 100644
--- a/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.h
+++ b/media/gpu/vaapi/vaapi_mjpeg_decode_accelerator.h
@@ -11,6 +11,7 @@
 
 #include "base/containers/span.h"
 #include "base/memory/scoped_refptr.h"
+#include "base/memory/shared_memory_mapping.h"
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread.h"
 #include "components/chromeos_camera/mjpeg_decode_accelerator.h"
@@ -26,7 +27,6 @@
 
 class BitstreamBuffer;
 class ScopedVAImage;
-class UnalignedSharedMemory;
 class VaapiWrapper;
 class VideoFrame;
 
@@ -75,7 +75,7 @@
 
   // Processes one decode request.
   void DecodeFromShmTask(int32_t task_id,
-                         std::unique_ptr<UnalignedSharedMemory> shm,
+                         base::WritableSharedMemoryMapping mapping,
                          scoped_refptr<VideoFrame> dst_frame);
   void DecodeFromDmaBufTask(int32_t task_id,
                             base::ScopedFD src_dmabuf_fd,
diff --git a/media/gpu/vaapi/vaapi_video_decode_accelerator.cc b/media/gpu/vaapi/vaapi_video_decode_accelerator.cc
index baaf2ae..3a07fa21 100644
--- a/media/gpu/vaapi/vaapi_video_decode_accelerator.cc
+++ b/media/gpu/vaapi/vaapi_video_decode_accelerator.cc
@@ -31,7 +31,6 @@
 #include "media/base/bind_to_current_loop.h"
 #include "media/base/format_utils.h"
 #include "media/base/media_log.h"
-#include "media/base/unaligned_shared_memory.h"
 #include "media/base/video_util.h"
 #include "media/gpu/accelerated_video_decoder.h"
 #include "media/gpu/h264_decoder.h"
diff --git a/media/gpu/vaapi/vaapi_video_decode_accelerator_unittest.cc b/media/gpu/vaapi/vaapi_video_decode_accelerator_unittest.cc
index 4d229d6f..0cd77859 100644
--- a/media/gpu/vaapi/vaapi_video_decode_accelerator_unittest.cc
+++ b/media/gpu/vaapi/vaapi_video_decode_accelerator_unittest.cc
@@ -278,9 +278,7 @@
                                       1, picture_size, _))
         .WillOnce(RunClosure(run_loop.QuitClosure()));
 
-    auto region = base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-        in_shm_.Duplicate());
-    BitstreamBuffer bitstream_buffer(bitstream_id, std::move(region),
+    BitstreamBuffer bitstream_buffer(bitstream_id, in_shm_.Duplicate(),
                                      kInputSize);
 
     QueueInputBuffer(std::move(bitstream_buffer));
@@ -372,10 +370,8 @@
     EXPECT_CALL(*this, NotifyEndOfBitstreamBuffer(bitstream_id))
         .WillOnce(RunClosure(run_loop.QuitClosure()));
 
-    auto region = base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-        in_shm_.Duplicate());
     QueueInputBuffer(
-        BitstreamBuffer(bitstream_id, std::move(region), kInputSize));
+        BitstreamBuffer(bitstream_id, in_shm_.Duplicate(), kInputSize));
 
     run_loop.Run();
   }
@@ -441,9 +437,8 @@
        QueueInputBufferAndErrorWhenVDAUninitialized) {
   SetVdaStateToUnitialized();
 
-  auto region = base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-      in_shm_.Duplicate());
-  BitstreamBuffer bitstream_buffer(kBitstreamId, std::move(region), kInputSize);
+  BitstreamBuffer bitstream_buffer(kBitstreamId, in_shm_.Duplicate(),
+                                   kInputSize);
 
   EXPECT_CALL(*this,
               NotifyError(VaapiVideoDecodeAccelerator::PLATFORM_FAILURE));
@@ -452,9 +447,8 @@
 
 // Verifies that Decode() returning kDecodeError ends up pinging NotifyError().
 TEST_P(VaapiVideoDecodeAcceleratorTest, QueueInputBufferAndDecodeError) {
-  auto region = base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-      in_shm_.Duplicate());
-  BitstreamBuffer bitstream_buffer(kBitstreamId, std::move(region), kInputSize);
+  BitstreamBuffer bitstream_buffer(kBitstreamId, in_shm_.Duplicate(),
+                                   kInputSize);
 
   base::RunLoop run_loop;
   EXPECT_CALL(*mock_decoder_,
@@ -473,9 +467,8 @@
   if (GetParam().video_codec != VP9PROFILE_PROFILE2)
     GTEST_SKIP() << "The test parameter is not vp9 profile 2";
 
-  auto region = base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-      in_shm_.Duplicate());
-  BitstreamBuffer bitstream_buffer(kBitstreamId, std::move(region), kInputSize);
+  BitstreamBuffer bitstream_buffer(kBitstreamId, in_shm_.Duplicate(),
+                                   kInputSize);
   base::RunLoop run_loop;
   EXPECT_CALL(*mock_decoder_,
               SetStream(_, IsExpectedDecoderBuffer(kInputSize, nullptr)))
diff --git a/media/gpu/vaapi/vaapi_video_encode_accelerator.cc b/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
index 5c0bdbc7..83df960 100644
--- a/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
+++ b/media/gpu/vaapi/vaapi_video_encode_accelerator.cc
@@ -22,6 +22,8 @@
 #include "base/cxx17_backports.h"
 #include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
+#include "base/memory/shared_memory_mapping.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/numerics/safe_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/task/task_traits.h"
@@ -34,7 +36,6 @@
 #include "media/base/format_utils.h"
 #include "media/base/media_log.h"
 #include "media/base/media_switches.h"
-#include "media/base/unaligned_shared_memory.h"
 #include "media/base/video_bitrate_allocation.h"
 #include "media/gpu/chromeos/platform_video_frame_utils.h"
 #include "media/gpu/gpu_video_encode_accelerator_helpers.h"
@@ -129,13 +130,10 @@
 
 struct VaapiVideoEncodeAccelerator::BitstreamBufferRef {
   BitstreamBufferRef(int32_t id, BitstreamBuffer buffer)
-      : id(id),
-        shm(std::make_unique<UnalignedSharedMemory>(buffer.TakeRegion(),
-                                                    buffer.size(),
-                                                    false)),
-        offset(buffer.offset()) {}
+      : id(id), shm_region(buffer.TakeRegion()), offset(buffer.offset()) {}
   const int32_t id;
-  const std::unique_ptr<UnalignedSharedMemory> shm;
+  base::UnsafeSharedMemoryRegion shm_region;
+  base::WritableSharedMemoryMapping shm_mapping;
   const off_t offset;
 };
 
@@ -512,13 +510,13 @@
     std::unique_ptr<EncodeResult> encode_result,
     std::unique_ptr<BitstreamBufferRef> buffer) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
-  uint8_t* target_data = static_cast<uint8_t*>(buffer->shm->memory());
+  uint8_t* target_data = buffer->shm_mapping.GetMemoryAs<uint8_t>();
   size_t data_size = 0;
   // vaSyncSurface() is not necessary because GetEncodedChunkSize() has been
   // called in VaapiVideoEncoderDelegate::Encode().
   if (!vaapi_wrapper_->DownloadFromVABuffer(
           encode_result->coded_buffer_id(), /*sync_surface_id=*/absl::nullopt,
-          target_data, buffer->shm->size(), &data_size)) {
+          target_data, buffer->shm_region.GetSize(), &data_size)) {
     NOTIFY_ERROR(kPlatformFailureError, "Failed downloading coded buffer");
     return;
   }
@@ -954,7 +952,9 @@
   DCHECK_CALLED_ON_VALID_SEQUENCE(encoder_sequence_checker_);
   DCHECK_NE(state_, kUninitialized);
 
-  if (!buffer_ref->shm->MapAt(buffer_ref->offset, buffer_ref->shm->size())) {
+  buffer_ref->shm_mapping = buffer_ref->shm_region.MapAt(
+      buffer_ref->offset, buffer_ref->shm_region.GetSize());
+  if (!buffer_ref->shm_mapping.IsValid()) {
     NOTIFY_ERROR(kPlatformFailureError, "Failed mapping shared memory.");
     return;
   }
diff --git a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
index 0bad02e5..f7b7aaf 100644
--- a/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
+++ b/media/gpu/windows/media_foundation_video_encode_accelerator_win.cc
@@ -224,8 +224,9 @@
       bitrate_(Bitrate::ConstantBitrate(kDefaultTargetBitrate)),
       input_required_(false),
       main_client_task_runner_(base::SequencedTaskRunnerHandle::Get()),
-      encoder_thread_task_runner_(
-          base::ThreadPool::CreateCOMSTATaskRunner({})) {
+      encoder_thread_task_runner_(base::ThreadPool::CreateCOMSTATaskRunner(
+          {},
+          base::SingleThreadTaskRunnerThreadMode::DEDICATED)) {
   encoder_weak_ptr_ = encoder_task_weak_factory_.GetWeakPtr();
 }
 
@@ -547,10 +548,9 @@
     return;
   }
 
-  auto region =
-      base::UnsafeSharedMemoryRegion::Deserialize(buffer.TakeRegion());
+  auto region = buffer.TakeRegion();
   auto mapping = region.Map();
-  if (!region.IsValid() || !mapping.IsValid()) {
+  if (!mapping.IsValid()) {
     DLOG(ERROR) << "Failed mapping shared memory.";
     NotifyError(kPlatformFailureError);
     return;
diff --git a/media/mojo/clients/mojo_video_encode_accelerator.cc b/media/mojo/clients/mojo_video_encode_accelerator.cc
index 2b2e46b..2e418be 100644
--- a/media/mojo/clients/mojo_video_encode_accelerator.cc
+++ b/media/mojo/clients/mojo_video_encode_accelerator.cc
@@ -22,7 +22,6 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
-#include "mojo/public/cpp/system/platform_handle.h"
 
 namespace media {
 
@@ -201,10 +200,7 @@
 
   DCHECK(buffer.region().IsValid());
 
-  auto buffer_handle =
-      mojo::WrapPlatformSharedMemoryRegion(buffer.TakeRegion());
-
-  vea_->UseOutputBitstreamBuffer(buffer.id(), std::move(buffer_handle));
+  vea_->UseOutputBitstreamBuffer(buffer.id(), buffer.TakeRegion());
 }
 
 void MojoVideoEncodeAccelerator::RequestEncodingParametersChange(
diff --git a/media/mojo/clients/mojo_video_encode_accelerator_unittest.cc b/media/mojo/clients/mojo_video_encode_accelerator_unittest.cc
index 0d75a04..2dd1b5be 100644
--- a/media/mojo/clients/mojo_video_encode_accelerator_unittest.cc
+++ b/media/mojo/clients/mojo_video_encode_accelerator_unittest.cc
@@ -93,14 +93,14 @@
 
   void UseOutputBitstreamBuffer(
       int32_t bitstream_buffer_id,
-      mojo::ScopedSharedBufferHandle buffer) override {
+      base::UnsafeSharedMemoryRegion region) override {
     EXPECT_EQ(-1, configured_bitstream_buffer_id_);
     configured_bitstream_buffer_id_ = bitstream_buffer_id;
 
-    DoUseOutputBitstreamBuffer(bitstream_buffer_id, &buffer);
+    DoUseOutputBitstreamBuffer(bitstream_buffer_id, &region);
   }
   MOCK_METHOD2(DoUseOutputBitstreamBuffer,
-               void(int32_t, mojo::ScopedSharedBufferHandle*));
+               void(int32_t, base::UnsafeSharedMemoryRegion*));
 
   MOCK_METHOD2(RequestEncodingParametersChangeWithLayers,
                void(const media::VideoBitrateAllocation&, uint32_t));
@@ -244,11 +244,9 @@
     auto shmem = base::UnsafeSharedMemoryRegion::Create(kShMemSize);
     EXPECT_CALL(*mock_mojo_vea(),
                 DoUseOutputBitstreamBuffer(kBitstreamBufferId, _));
-    mojo_vea()->UseOutputBitstreamBuffer(BitstreamBuffer(
-        kBitstreamBufferId,
-        base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
-            std::move(shmem)),
-        kShMemSize, 0 /* offset */, base::TimeDelta()));
+    mojo_vea()->UseOutputBitstreamBuffer(
+        BitstreamBuffer(kBitstreamBufferId, std::move(shmem), kShMemSize,
+                        0 /* offset */, base::TimeDelta()));
     base::RunLoop().RunUntilIdle();
   }
 
diff --git a/media/mojo/mojom/video_encode_accelerator.mojom b/media/mojo/mojom/video_encode_accelerator.mojom
index deadae6..b76407a 100644
--- a/media/mojo/mojom/video_encode_accelerator.mojom
+++ b/media/mojo/mojom/video_encode_accelerator.mojom
@@ -6,6 +6,7 @@
 
 import "media/mojo/mojom/media_log.mojom";
 import "media/mojo/mojom/media_types.mojom";
+import "mojo/public/mojom/base/shared_memory.mojom";
 import "mojo/public/mojom/base/time.mojom";
 import "ui/gfx/geometry/mojom/geometry.mojom";
 import "media/mojo/mojom/video_encoder_info.mojom";
@@ -159,7 +160,7 @@
   Encode(VideoFrame frame, bool force_keyframe) => ();
 
   UseOutputBitstreamBuffer(int32 bitstream_buffer_id,
-                           handle<shared_buffer> buffer);
+                           mojo_base.mojom.UnsafeSharedMemoryRegion region);
 
   // Request a change to the encoding parameters. This is only a request,
   // fulfilled on a best-effort basis. This method is intended for use with
diff --git a/media/mojo/services/mojo_video_encode_accelerator_service.cc b/media/mojo/services/mojo_video_encode_accelerator_service.cc
index e26aa459..34b3e252 100644
--- a/media/mojo/services/mojo_video_encode_accelerator_service.cc
+++ b/media/mojo/services/mojo_video_encode_accelerator_service.cc
@@ -158,14 +158,14 @@
 
 void MojoVideoEncodeAcceleratorService::UseOutputBitstreamBuffer(
     int32_t bitstream_buffer_id,
-    mojo::ScopedSharedBufferHandle buffer) {
+    base::UnsafeSharedMemoryRegion region) {
   DVLOG(2) << __func__ << " bitstream_buffer_id=" << bitstream_buffer_id;
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
   if (!encoder_)
     return;
-  if (!buffer.is_valid()) {
-    DLOG(ERROR) << __func__ << " invalid |buffer|.";
+  if (!region.IsValid()) {
+    DLOG(ERROR) << __func__ << " invalid |region|.";
     NotifyError(::media::VideoEncodeAccelerator::kInvalidArgumentError);
     return;
   }
@@ -176,9 +176,6 @@
     return;
   }
 
-  base::subtle::PlatformSharedMemoryRegion region =
-      mojo::UnwrapPlatformSharedMemoryRegion(std::move(buffer));
-
   auto memory_size = region.GetSize();
   if (memory_size < output_buffer_size_) {
     DLOG(ERROR) << __func__ << " bitstream_buffer_id=" << bitstream_buffer_id
diff --git a/media/mojo/services/mojo_video_encode_accelerator_service.h b/media/mojo/services/mojo_video_encode_accelerator_service.h
index 77474a9..978552d8 100644
--- a/media/mojo/services/mojo_video_encode_accelerator_service.h
+++ b/media/mojo/services/mojo_video_encode_accelerator_service.h
@@ -12,6 +12,7 @@
 #include <vector>
 
 #include "base/compiler_specific.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/memory/weak_ptr.h"
 #include "base/sequence_checker.h"
 #include "gpu/config/gpu_driver_bug_workarounds.h"
@@ -73,7 +74,7 @@
               bool force_keyframe,
               EncodeCallback callback) override;
   void UseOutputBitstreamBuffer(int32_t bitstream_buffer_id,
-                                mojo::ScopedSharedBufferHandle buffer) override;
+                                base::UnsafeSharedMemoryRegion region) override;
   void RequestEncodingParametersChangeWithBitrate(
       const media::Bitrate& bitrate_allocation,
       uint32_t framerate) override;
diff --git a/media/mojo/services/mojo_video_encode_accelerator_service_unittest.cc b/media/mojo/services/mojo_video_encode_accelerator_service_unittest.cc
index e3c32a1..2c5ed85 100644
--- a/media/mojo/services/mojo_video_encode_accelerator_service_unittest.cc
+++ b/media/mojo/services/mojo_video_encode_accelerator_service_unittest.cc
@@ -7,6 +7,7 @@
 #include "base/bind.h"
 #include "base/callback_helpers.h"
 #include "base/memory/ptr_util.h"
+#include "base/memory/unsafe_shared_memory_region.h"
 #include "base/run_loop.h"
 #include "base/test/task_environment.h"
 #include "gpu/config/gpu_driver_bug_workarounds.h"
@@ -174,10 +175,10 @@
   const int32_t kBitstreamBufferId = 17;
   {
     const uint64_t kShMemSize = fake_vea()->minimum_output_buffer_size();
-    auto handle = mojo::SharedBufferHandle::Create(kShMemSize);
+    auto region = base::UnsafeSharedMemoryRegion::Create(kShMemSize);
 
     mojo_vea_service()->UseOutputBitstreamBuffer(kBitstreamBufferId,
-                                                 std::move(handle));
+                                                 std::move(region));
     base::RunLoop().RunUntilIdle();
   }
 
@@ -303,13 +304,13 @@
 
   const int32_t kBitstreamBufferId = 17;
   const uint64_t wrong_size = fake_vea()->minimum_output_buffer_size() / 2;
-  auto handle = mojo::SharedBufferHandle::Create(wrong_size);
+  auto region = base::UnsafeSharedMemoryRegion::Create(wrong_size);
 
   EXPECT_CALL(*mock_mojo_vea_client(),
               NotifyError(VideoEncodeAccelerator::kInvalidArgumentError));
 
   mojo_vea_service()->UseOutputBitstreamBuffer(kBitstreamBufferId,
-                                               std::move(handle));
+                                               std::move(region));
   base::RunLoop().RunUntilIdle();
 }
 
@@ -347,9 +348,9 @@
   {
     const int32_t kBitstreamBufferId = 17;
     const uint64_t kShMemSize = 10;
-    auto handle = mojo::SharedBufferHandle::Create(kShMemSize);
+    auto region = base::UnsafeSharedMemoryRegion::Create(kShMemSize);
     mojo_vea_service()->UseOutputBitstreamBuffer(kBitstreamBufferId,
-                                                 std::move(handle));
+                                                 std::move(region));
     base::RunLoop().RunUntilIdle();
   }
   {
diff --git a/net/base/features.cc b/net/base/features.cc
index 20b3b04f..957ac8c5 100644
--- a/net/base/features.cc
+++ b/net/base/features.cc
@@ -293,5 +293,15 @@
     "ClampCookieExpiryTo400Days",
     base::FEATURE_DISABLED_BY_DEFAULT);
 
+#if BUILDFLAG(IS_ANDROID)
+const base::Feature kStaticKeyPinningEnforcement(
+    "StaticKeyPinningEnforcement",
+    base::FEATURE_DISABLED_BY_DEFAULT);
+#else
+const base::Feature kStaticKeyPinningEnforcement(
+    "StaticKeyPinningEnforcement",
+    base::FEATURE_ENABLED_BY_DEFAULT);
+#endif
+
 }  // namespace features
 }  // namespace net
diff --git a/net/base/features.h b/net/base/features.h
index ca9fe8f..c2f9b2d 100644
--- a/net/base/features.h
+++ b/net/base/features.h
@@ -430,6 +430,9 @@
 // future.
 NET_EXPORT extern const base::Feature kClampCookieExpiryTo400Days;
 
+// Controls whether static key pinning is enforced.
+NET_EXPORT extern const base::Feature kStaticKeyPinningEnforcement;
+
 }  // namespace features
 }  // namespace net
 
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index 3739a24..35f53172 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -1018,3 +1018,7 @@
 
 // All DNS requests associated with this job have been cancelled.
 NET_ERROR(DNS_REQUEST_CANCELLED, -810)
+
+// The hostname resolution of HTTPS record was expected to be resolved with
+// alpn values of supported protocols, but did not.
+NET_ERROR(DNS_NO_MACHING_SUPPORTED_ALPN, -811)
diff --git a/net/http/http_proxy_connect_job.cc b/net/http/http_proxy_connect_job.cc
index 5fadbafb..ca6bde8 100644
--- a/net/http/http_proxy_connect_job.cc
+++ b/net/http/http_proxy_connect_job.cc
@@ -660,7 +660,8 @@
       quic_version, ssl_params->privacy_mode(), kH2QuicTunnelPriority,
       socket_tag(), params_->network_isolation_key(),
       ssl_params->GetDirectConnectionParams()->secure_dns_policy(),
-      /*use_dns_aliases=*/false, ssl_params->ssl_config().GetCertVerifyFlags(),
+      /*use_dns_aliases=*/false, /*require_dns_https_alpn=*/false,
+      ssl_params->ssl_config().GetCertVerifyFlags(),
       GURL("https://" + proxy_server.ToString()), net_log(),
       &quic_net_error_details_,
       /*failed_on_default_network_callback=*/CompletionOnceCallback(),
diff --git a/net/http/http_stream_factory_job.cc b/net/http/http_stream_factory_job.cc
index 261cbd36..c60650b23 100644
--- a/net/http/http_stream_factory_job.cc
+++ b/net/http/http_stream_factory_job.cc
@@ -886,7 +886,8 @@
       std::move(destination), quic_version_, request_info_.privacy_mode,
       priority_, request_info_.socket_tag, request_info_.network_isolation_key,
       request_info_.secure_dns_policy, proxy_info_.is_direct(),
-      ssl_config->GetCertVerifyFlags(), url, net_log_, &net_error_details_,
+      /*require_dns_https_alpn=*/false, ssl_config->GetCertVerifyFlags(), url,
+      net_log_, &net_error_details_,
       base::BindOnce(&Job::OnFailedOnDefaultNetwork, ptr_factory_.GetWeakPtr()),
       io_callback_);
   if (rv == OK) {
diff --git a/net/http/http_stream_factory_job_controller.cc b/net/http/http_stream_factory_job_controller.cc
index 364a9153..05823c2 100644
--- a/net/http/http_stream_factory_job_controller.cc
+++ b/net/http/http_stream_factory_job_controller.cc
@@ -1125,7 +1125,7 @@
     QuicSessionKey session_key(
         HostPortPair::FromURL(mapped_origin), request_info.privacy_mode,
         request_info.socket_tag, request_info.network_isolation_key,
-        request_info.secure_dns_policy);
+        request_info.secure_dns_policy, /*require_dns_https_alpn=*/false);
 
     GURL destination = CreateAltSvcUrl(
         original_url, alternative_service_info.host_port_pair());
diff --git a/net/http/transport_security_state.cc b/net/http/transport_security_state.cc
index 340b2e4..f920d29 100644
--- a/net/http/transport_security_state.cc
+++ b/net/http/transport_security_state.cc
@@ -413,8 +413,7 @@
           features::kPartitionExpectCTStateByNetworkIsolationKey)) {
 // Static pinning is only enabled for official builds to make sure that
 // others don't end up with pins that cannot be easily updated.
-#if !BUILDFLAG(GOOGLE_CHROME_BRANDING) || BUILDFLAG(IS_ANDROID) || \
-    BUILDFLAG(IS_IOS)
+#if !BUILDFLAG(GOOGLE_CHROME_BRANDING) || BUILDFLAG(IS_IOS)
   enable_static_pins_ = false;
   enable_static_expect_ct_ = false;
 #endif
@@ -1179,8 +1178,10 @@
                                                PKPState* pkp_result) const {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-  if (!enable_static_pins_ || !IsStaticPKPListTimely())
+  if (!enable_static_pins_ || !IsStaticPKPListTimely() ||
+      !base::FeatureList::IsEnabled(features::kStaticKeyPinningEnforcement)) {
     return false;
+  }
 
   PreloadResult result;
   if (host_pins_.has_value()) {
@@ -1641,10 +1642,18 @@
 }
 
 bool TransportSecurityState::IsStaticPKPListTimely() const {
+  if (pins_list_always_timely_for_testing_) {
+    return true;
+  }
+
   // If the list has not been updated via component updater, freshness depends
-  // on build freshness.
+  // on the compiled-in list freshness.
   if (!host_pins_.has_value()) {
-    return IsBuildTimely();
+#if BUILDFLAG(INCLUDE_TRANSPORT_SECURITY_STATE_PRELOAD_LIST)
+    return (base::Time::Now() - kPinsListTimestamp).InDays() < 70;
+#else
+    return false;
+#endif
   }
   DCHECK(!key_pins_list_last_update_time_.is_null());
   // Else, we use the last update time.
diff --git a/net/http/transport_security_state.h b/net/http/transport_security_state.h
index e8d50db..17e8bac 100644
--- a/net/http/transport_security_state.h
+++ b/net/http/transport_security_state.h
@@ -642,6 +642,12 @@
   // The number of cached ExpectCTState entries.
   size_t num_expect_ct_entries_for_testing() const;
 
+  // Sets whether pinning list timestamp freshness should be ignored for
+  // testing.
+  void SetPinningListAlwaysTimelyForTesting(bool always_timely) {
+    pins_list_always_timely_for_testing_ = always_timely;
+  }
+
   // The number of cached STSState entries.
   size_t num_sts_entries() const;
 
@@ -813,6 +819,8 @@
   base::Time key_pins_list_last_update_time_;
   std::vector<PinSet> pinsets_;
 
+  bool pins_list_always_timely_for_testing_ = false;
+
   THREAD_CHECKER(thread_checker_);
 };
 
diff --git a/net/http/transport_security_state_static.pins b/net/http/transport_security_state_static.pins
index b7d22a05..a83b411 100644
--- a/net/http/transport_security_state_static.pins
+++ b/net/http/transport_security_state_static.pins
@@ -43,6 +43,10 @@
 #   hash function for preloaded entries again (we have already done so once).
 #
 
+# Last updated: 2022-04-25 00:00 UTC
+PinsListTimestamp
+1650844800
+
 TestSPKI
 sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
 
diff --git a/net/http/transport_security_state_static.template b/net/http/transport_security_state_static.template
index ba40b5b..58d1f8a 100644
--- a/net/http/transport_security_state_static.template
+++ b/net/http/transport_security_state_static.template
@@ -11,8 +11,12 @@
 
 #include <iterator>
 
+#include "base/time/time.h"
 #include "net/http/transport_security_state_source.h"
 
+// This is the time at which the key pins list was last updated.
+const base::Time kPinsListTimestamp = base::Time::FromTimeT([[PINS_LIST_TIMESTAMP]]);
+
 // These are SubjectPublicKeyInfo hashes for public key pinning. The
 // hashes are SHA256 digests.
 [[SPKI_HASHES]]
diff --git a/net/http/transport_security_state_static_unittest.pins b/net/http/transport_security_state_static_unittest.pins
index 36ef2f1..fc583a7 100644
--- a/net/http/transport_security_state_static_unittest.pins
+++ b/net/http/transport_security_state_static_unittest.pins
@@ -5,6 +5,12 @@
 # This is a HSTS pins file used by the unittests. For more information on the
 # content and format see the comments in transport_security_state_static.pins.
 
+# Having a timestamp is required by the parser, but this timestamp is not
+# otherwise used by tests (since they override the behavior by setting
+# pins_list_always_timely_for_testing_
+PinsListTimestamp
+0
+
 TestSPKI1
 sha256/AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=
 
diff --git a/net/http/transport_security_state_static_unittest_default.pins b/net/http/transport_security_state_static_unittest_default.pins
index 900b7fd..486dc8bb9 100644
--- a/net/http/transport_security_state_static_unittest_default.pins
+++ b/net/http/transport_security_state_static_unittest_default.pins
@@ -5,6 +5,12 @@
 # For use with transport_security_state_static_unittest_default.json.
 # The format of this file is identical to transport_security_state_static.pins.
 
+# Having a timestamp is required by the parser, but this timestamp is not
+# otherwise used by tests (since they override the behavior by setting
+# pins_list_always_timely_for_testing_
+PinsListTimestamp
+0
+
 TestSPKI1
 sha256/w3y7Yg3RzkAyhCeBoLHm71YRnuuUW87AAR/DVpLMTw4=
 
diff --git a/net/http/transport_security_state_unittest.cc b/net/http/transport_security_state_unittest.cc
index ba3b4e3..b30df42b2 100644
--- a/net/http/transport_security_state_unittest.cc
+++ b/net/http/transport_security_state_unittest.cc
@@ -381,6 +381,7 @@
 
   static void EnableStaticPins(TransportSecurityState* state) {
     state->enable_static_pins_ = true;
+    state->SetPinningListAlwaysTimelyForTesting(true);
   }
 
   static void EnableStaticExpectCT(TransportSecurityState* state) {
@@ -884,6 +885,7 @@
 
 TEST_F(TransportSecurityStateTest, LongNames) {
   TransportSecurityState state;
+  state.SetPinningListAlwaysTimelyForTesting(true);
   const char kLongName[] =
       "lookupByWaveIdHashAndWaveIdIdAndWaveIdDomainAndWaveletIdIdAnd"
       "WaveletIdDomainAndBlipBlipid";
@@ -906,6 +908,9 @@
 }
 
 TEST_F(TransportSecurityStateTest, PinValidationWithoutRejectedCerts) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   HashValueVector good_hashes, bad_hashes;
 
   for (size_t i = 0; kGoodPath[i]; i++) {
@@ -916,6 +921,7 @@
   }
 
   TransportSecurityState state;
+  state.SetPinningListAlwaysTimelyForTesting(true);
   EnableStaticPins(&state);
 
   TransportSecurityState::PKPState pkp_state;
@@ -931,12 +937,16 @@
 // Tests that pinning violations on preloaded pins trigger reports when
 // the preloaded pin contains a report URI.
 TEST_F(TransportSecurityStateTest, PreloadedPKPReportUri) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   const char kPreloadedPinDomain[] = "with-report-uri-pkp.preloaded.test";
   HostPortPair host_port_pair(kPreloadedPinDomain, kPort);
   net::NetworkIsolationKey network_isolation_key =
       NetworkIsolationKey::CreateTransient();
 
   TransportSecurityState state;
+  state.SetPinningListAlwaysTimelyForTesting(true);
   MockCertificateReportSender mock_report_sender;
   state.SetReportSender(&mock_report_sender);
 
@@ -1372,6 +1382,9 @@
 // the lookup methods can find the entry and correctly decode the different
 // preloaded states (HSTS, HPKP, and Expect-CT).
 TEST_F(TransportSecurityStateTest, DecodePreloadedSingle) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   SetTransportSecurityStateSourceForTesting(&test1::kHSTSSource);
 
   TransportSecurityState state;
@@ -1402,6 +1415,9 @@
 // entries and correctly decode the different preloaded states (HSTS, HPKP,
 // and Expect-CT) for each entry.
 TEST_F(TransportSecurityStateTest, DecodePreloadedMultiplePrefix) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   SetTransportSecurityStateSourceForTesting(&test2::kHSTSSource);
 
   TransportSecurityState state;
@@ -1471,6 +1487,9 @@
 // all entries and correctly decode the different preloaded states (HSTS, HPKP,
 // and Expect-CT) for each entry.
 TEST_F(TransportSecurityStateTest, DecodePreloadedMultipleMix) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   SetTransportSecurityStateSourceForTesting(&test3::kHSTSSource);
 
   TransportSecurityState state;
@@ -2703,6 +2722,7 @@
 
 static bool HasStaticState(const char* hostname) {
   TransportSecurityState state;
+  state.SetPinningListAlwaysTimelyForTesting(true);
   TransportSecurityState::STSState sts_state;
   TransportSecurityState::PKPState pkp_state;
   return state.GetStaticSTSState(hostname, &sts_state) ||
@@ -2711,6 +2731,7 @@
 
 static bool HasStaticPublicKeyPins(const char* hostname) {
   TransportSecurityState state;
+  state.SetPinningListAlwaysTimelyForTesting(true);
   TransportSecurityStateTest::EnableStaticPins(&state);
   TransportSecurityState::PKPState pkp_state;
   if (!state.GetStaticPKPState(hostname, &pkp_state))
@@ -2728,7 +2749,11 @@
 }
 
 TEST_F(TransportSecurityStateStaticTest, EnableStaticPins) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   TransportSecurityState state;
+  state.SetPinningListAlwaysTimelyForTesting(true);
   TransportSecurityState::PKPState pkp_state;
 
   EnableStaticPins(&state);
@@ -2739,6 +2764,7 @@
 
 TEST_F(TransportSecurityStateStaticTest, DisableStaticPins) {
   TransportSecurityState state;
+  state.SetPinningListAlwaysTimelyForTesting(true);
   TransportSecurityState::PKPState pkp_state;
 
   DisableStaticPins(&state);
@@ -2783,6 +2809,9 @@
 }
 
 TEST_F(TransportSecurityStateStaticTest, PreloadedDomainSet) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   TransportSecurityState state;
   EnableStaticPins(&state);
   TransportSecurityState::STSState sts_state;
@@ -2801,6 +2830,9 @@
 }
 
 TEST_F(TransportSecurityStateStaticTest, Preloaded) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   TransportSecurityState state;
   EnableStaticPins(&state);
   TransportSecurityState::STSState sts_state;
@@ -3017,6 +3049,9 @@
 }
 
 TEST_F(TransportSecurityStateStaticTest, PreloadedPins) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   TransportSecurityState state;
   EnableStaticPins(&state);
   TransportSecurityState::STSState sts_state;
@@ -3083,6 +3118,9 @@
 }
 
 TEST_F(TransportSecurityStateStaticTest, BuiltinCertPins) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   TransportSecurityState state;
   EnableStaticPins(&state);
   TransportSecurityState::PKPState pkp_state;
@@ -3139,6 +3177,9 @@
 }
 
 TEST_F(TransportSecurityStateStaticTest, OptionalHSTSCertPins) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   TransportSecurityState state;
   EnableStaticPins(&state);
 
@@ -3162,11 +3203,16 @@
 }
 
 TEST_F(TransportSecurityStateStaticTest, OverrideBuiltins) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   EXPECT_TRUE(HasStaticPublicKeyPins("google.com"));
   EXPECT_FALSE(StaticShouldRedirect("google.com"));
   EXPECT_FALSE(StaticShouldRedirect("www.google.com"));
 
   TransportSecurityState state;
+  state.SetPinningListAlwaysTimelyForTesting(true);
+
   const base::Time current_time(base::Time::Now());
   const base::Time expiry = current_time + base::Seconds(1000);
   state.AddHSTS("www.google.com", expiry, true);
@@ -3176,6 +3222,9 @@
 
 // Tests that redundant reports are rate-limited.
 TEST_F(TransportSecurityStateStaticTest, HPKPReportRateLimiting) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   HostPortPair host_port_pair(kHost, kPort);
   HostPortPair subdomain_host_port_pair(kSubdomain, kPort);
   GURL report_uri(kReportUri);
@@ -3238,6 +3287,9 @@
 }
 
 TEST_F(TransportSecurityStateStaticTest, HPKPReporting) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   HostPortPair host_port_pair(kHost, kPort);
   HostPortPair subdomain_host_port_pair(kSubdomain, kPort);
   GURL report_uri(kReportUri);
@@ -3882,6 +3934,9 @@
 }
 
 TEST_F(TransportSecurityStateTest, UpdateKeyPinsListValidPin) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   HostPortPair host_port_pair(kHost, kPort);
   GURL report_uri(kReportUri);
   NetworkIsolationKey network_isolation_key =
@@ -3938,6 +3993,9 @@
 }
 
 TEST_F(TransportSecurityStateTest, UpdateKeyPinsListNotValidPin) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   HostPortPair host_port_pair(kHost, kPort);
   GURL report_uri(kReportUri);
   NetworkIsolationKey network_isolation_key =
@@ -3994,6 +4052,9 @@
 }
 
 TEST_F(TransportSecurityStateTest, UpdateKeyPinsEmptyList) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   HostPortPair host_port_pair(kHost, kPort);
   GURL report_uri(kReportUri);
   NetworkIsolationKey network_isolation_key =
@@ -4035,6 +4096,9 @@
 }
 
 TEST_F(TransportSecurityStateTest, UpdateKeyPinsListTimestamp) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   HostPortPair host_port_pair(kHost, kPort);
   GURL report_uri(kReportUri);
   NetworkIsolationKey network_isolation_key =
@@ -4055,6 +4119,7 @@
 
   TransportSecurityState state;
   EnableStaticPins(&state);
+  state.SetPinningListAlwaysTimelyForTesting(false);
   std::string unused_failure_log;
 
   // Prior to updating the list, bad_hashes should be rejected.
@@ -4102,4 +4167,48 @@
                 network_isolation_key, &unused_failure_log));
 }
 
+class TransportSecurityStatePinningKillswitchTest
+    : public TransportSecurityStateTest {
+ public:
+  void SetUp() override {
+    scoped_feature_list_.InitAndDisableFeature(
+        features::kStaticKeyPinningEnforcement);
+    TransportSecurityStateTest::SetUp();
+  }
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+TEST_F(TransportSecurityStatePinningKillswitchTest, PinningKillswitchSet) {
+  HostPortPair host_port_pair(kHost, kPort);
+  GURL report_uri(kReportUri);
+  NetworkIsolationKey network_isolation_key =
+      NetworkIsolationKey::CreateTransient();
+  // Two dummy certs to use as the server-sent and validated chains. The
+  // contents don't matter.
+  scoped_refptr<X509Certificate> cert1 =
+      ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
+  ASSERT_TRUE(cert1);
+  scoped_refptr<X509Certificate> cert2 =
+      ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem");
+  ASSERT_TRUE(cert2);
+
+  HashValueVector bad_hashes;
+
+  for (size_t i = 0; kBadPath[i]; i++)
+    EXPECT_TRUE(AddHash(kBadPath[i], &bad_hashes));
+
+  TransportSecurityState state;
+  EnableStaticPins(&state);
+  std::string unused_failure_log;
+
+  // Hashes should be accepted since pinning enforcement is disabled.
+  EXPECT_EQ(TransportSecurityState::PKPStatus::OK,
+            state.CheckPublicKeyPins(
+                host_port_pair, true, bad_hashes, cert1.get(), cert2.get(),
+                TransportSecurityState::ENABLE_PIN_REPORTS,
+                network_isolation_key, &unused_failure_log));
+}
+
 }  // namespace net
diff --git a/net/quic/bidirectional_stream_quic_impl_unittest.cc b/net/quic/bidirectional_stream_quic_impl_unittest.cc
index eabc5fb..da630d7f 100644
--- a/net/quic/bidirectional_stream_quic_impl_unittest.cc
+++ b/net/quic/bidirectional_stream_quic_impl_unittest.cc
@@ -561,7 +561,8 @@
         base::WrapUnique(static_cast<QuicServerInfo*>(nullptr)),
         QuicSessionKey(kDefaultServerHostName, kDefaultServerPort,
                        PRIVACY_MODE_DISABLED, SocketTag(),
-                       NetworkIsolationKey(), SecureDnsPolicy::kAllow),
+                       NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                       /*require_dns_https_alpn=*/false),
         /*require_confirmation=*/false,
         /*migrate_session_early_v2=*/false,
         /*migrate_session_on_network_change_v2=*/false,
diff --git a/net/quic/crypto/proof_verifier_chromium_test.cc b/net/quic/crypto/proof_verifier_chromium_test.cc
index 3789f50..22f0351 100644
--- a/net/quic/crypto/proof_verifier_chromium_test.cc
+++ b/net/quic/crypto/proof_verifier_chromium_test.cc
@@ -11,7 +11,9 @@
 #include "base/memory/ref_counted.h"
 #include "base/strings/string_piece.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/test/scoped_feature_list.h"
 #include "net/base/completion_once_callback.h"
+#include "net/base/features.h"
 #include "net/base/net_errors.h"
 #include "net/base/network_isolation_key.h"
 #include "net/cert/cert_and_ct_verifier.h"
@@ -578,6 +580,9 @@
 
 // Test that PKP is enforced for certificates that chain up to known roots.
 TEST_F(ProofVerifierChromiumTest, PKPEnforced) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   dummy_result_.is_issued_by_known_root = true;
   dummy_result_.public_key_hashes = MakeHashValueVector(0x01);
 
@@ -585,6 +590,7 @@
   dummy_verifier.AddResultForCert(test_cert_.get(), dummy_result_, OK);
 
   transport_security_state_.EnableStaticPinsForTesting();
+  transport_security_state_.SetPinningListAlwaysTimelyForTesting(true);
   ScopedTransportSecurityStateSource scoped_security_state_source;
 
   ProofVerifierChromium proof_verifier(&dummy_verifier, &ct_policy_enforcer_,
@@ -625,6 +631,9 @@
 // Test |pkp_bypassed| is set when PKP is bypassed due to a local
 // trust anchor
 TEST_F(ProofVerifierChromiumTest, PKPBypassFlagSet) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   dummy_result_.is_issued_by_known_root = false;
   dummy_result_.public_key_hashes = MakeHashValueVector(0x01);
 
@@ -632,6 +641,7 @@
   dummy_verifier.AddResultForCert(test_cert_.get(), dummy_result_, OK);
 
   transport_security_state_.EnableStaticPinsForTesting();
+  transport_security_state_.SetPinningListAlwaysTimelyForTesting(true);
   ScopedTransportSecurityStateSource scoped_security_state_source;
 
   ProofVerifierChromium proof_verifier(&dummy_verifier, &ct_policy_enforcer_,
@@ -782,6 +792,9 @@
 
 // Test that CT is considered even when PKP fails.
 TEST_F(ProofVerifierChromiumTest, PKPAndCTBothTested) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   dummy_result_.is_issued_by_known_root = true;
   dummy_result_.public_key_hashes = MakeHashValueVector(0x01);
 
@@ -790,6 +803,7 @@
 
   // Set up PKP.
   transport_security_state_.EnableStaticPinsForTesting();
+  transport_security_state_.SetPinningListAlwaysTimelyForTesting(true);
   ScopedTransportSecurityStateSource scoped_security_state_source;
 
   // Set up CT.
diff --git a/net/quic/quic_chromium_client_session_peer.cc b/net/quic/quic_chromium_client_session_peer.cc
index 4b180ec..d04cc48 100644
--- a/net/quic/quic_chromium_client_session_peer.cc
+++ b/net/quic/quic_chromium_client_session_peer.cc
@@ -17,8 +17,9 @@
   quic::QuicServerId server_id(hostname,
                                session->session_key_.server_id().port(),
                                session->session_key_.privacy_mode());
-  session->session_key_ = QuicSessionKey(
-      server_id, SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow);
+  session->session_key_ =
+      QuicSessionKey(server_id, SocketTag(), NetworkIsolationKey(),
+                     SecureDnsPolicy::kAllow, /*require_dns_https_alpn=*/false);
 }
 
 // static
diff --git a/net/quic/quic_chromium_client_session_test.cc b/net/quic/quic_chromium_client_session_test.cc
index 58d76e1..3185f32 100644
--- a/net/quic/quic_chromium_client_session_test.cc
+++ b/net/quic/quic_chromium_client_session_test.cc
@@ -146,7 +146,8 @@
                      PRIVACY_MODE_DISABLED,
                      SocketTag(),
                      NetworkIsolationKey(),
-                     SecureDnsPolicy::kAllow),
+                     SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false),
         destination_(url::kHttpsScheme, kServerHostname, kServerPort),
         default_network_(NetworkChangeNotifier::kInvalidNetworkHandle),
         client_maker_(version_,
@@ -1642,39 +1643,47 @@
   EXPECT_TRUE(session_->CanPool(
       "www.example.org",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
-                     NetworkIsolationKey(), SecureDnsPolicy::kAllow)));
+                     NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
   EXPECT_FALSE(session_->CanPool(
       "www.example.org",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_ENABLED, SocketTag(),
-                     NetworkIsolationKey(), SecureDnsPolicy::kAllow)));
+                     NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
   EXPECT_FALSE(session_->CanPool(
       "www.example.org",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
-                     NetworkIsolationKey(), SecureDnsPolicy::kDisable)));
+                     NetworkIsolationKey(), SecureDnsPolicy::kDisable,
+                     /*require_dns_https_alpn=*/false)));
 #if BUILDFLAG(IS_ANDROID)
   SocketTag tag1(SocketTag::UNSET_UID, 0x12345678);
   SocketTag tag2(getuid(), 0x87654321);
   EXPECT_FALSE(session_->CanPool(
       "www.example.org",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, tag1,
-                     NetworkIsolationKey(), SecureDnsPolicy::kAllow)));
+                     NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
   EXPECT_FALSE(session_->CanPool(
       "www.example.org",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, tag2,
-                     NetworkIsolationKey(), SecureDnsPolicy::kAllow)));
+                     NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
 #endif
   EXPECT_TRUE(session_->CanPool(
       "mail.example.org",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
-                     NetworkIsolationKey(), SecureDnsPolicy::kAllow)));
+                     NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
   EXPECT_TRUE(session_->CanPool(
       "mail.example.com",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
-                     NetworkIsolationKey(), SecureDnsPolicy::kAllow)));
+                     NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
   EXPECT_FALSE(session_->CanPool(
       "mail.google.com",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
-                     NetworkIsolationKey(), SecureDnsPolicy::kAllow)));
+                     NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
 
   const SchemefulSite kSiteFoo(GURL("http://foo.test/"));
 
@@ -1687,7 +1696,8 @@
         "mail.example.com",
         QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
                        NetworkIsolationKey(kSiteFoo, kSiteFoo),
-                       SecureDnsPolicy::kAllow)));
+                       SecureDnsPolicy::kAllow,
+                       /*require_dns_https_alpn=*/false)));
   }
   {
     base::test::ScopedFeatureList feature_list;
@@ -1697,7 +1707,8 @@
         "mail.example.com",
         QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
                        NetworkIsolationKey(kSiteFoo, kSiteFoo),
-                       SecureDnsPolicy::kAllow)));
+                       SecureDnsPolicy::kAllow,
+                       /*require_dns_https_alpn=*/false)));
   }
 }
 
@@ -1716,9 +1727,10 @@
   // Need to create a session key after setting
   // kPartitionExpectCTStateByNetworkIsolationKey, otherwise, it will ignore the
   // NetworkIsolationKey value.
-  session_key_ = QuicSessionKey(kServerHostname, kServerPort,
-                                PRIVACY_MODE_DISABLED, SocketTag(),
-                                network_isolation_key, SecureDnsPolicy::kAllow);
+  session_key_ =
+      QuicSessionKey(kServerHostname, kServerPort, PRIVACY_MODE_DISABLED,
+                     SocketTag(), network_isolation_key,
+                     SecureDnsPolicy::kAllow, /*require_dns_https_alpn=*/false);
 
   // Need to create this after enabling
   // kPartitionExpectCTStateByNetworkIsolationKey.
@@ -1753,7 +1765,8 @@
   EXPECT_TRUE(session_->CanPool(
       "www.example.org",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
-                     network_isolation_key, SecureDnsPolicy::kAllow)));
+                     network_isolation_key, SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
 
   // Adding Expect-CT data for different NetworkIsolationKeys should have no
   // effect.
@@ -1767,7 +1780,8 @@
   EXPECT_TRUE(session_->CanPool(
       "www.example.org",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
-                     network_isolation_key, SecureDnsPolicy::kAllow)));
+                     network_isolation_key, SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
 
   // Adding Expect-CT data for the same NetworkIsolationKey should prevent
   // pooling.
@@ -1777,7 +1791,8 @@
   EXPECT_FALSE(session_->CanPool(
       "www.example.org",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
-                     network_isolation_key, SecureDnsPolicy::kAllow)));
+                     network_isolation_key, SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
 }
 
 // Much as above, but uses a non-empty NetworkIsolationKey.
@@ -1791,9 +1806,10 @@
   const NetworkIsolationKey kNetworkIsolationKey1(kSiteFoo, kSiteFoo);
   const NetworkIsolationKey kNetworkIsolationKey2(kSiteBar, kSiteBar);
 
-  session_key_ = QuicSessionKey(kServerHostname, kServerPort,
-                                PRIVACY_MODE_DISABLED, SocketTag(),
-                                kNetworkIsolationKey1, SecureDnsPolicy::kAllow);
+  session_key_ =
+      QuicSessionKey(kServerHostname, kServerPort, PRIVACY_MODE_DISABLED,
+                     SocketTag(), kNetworkIsolationKey1,
+                     SecureDnsPolicy::kAllow, /*require_dns_https_alpn=*/false);
 
   MockQuicData quic_data(version_);
   if (VersionUsesHttp3(version_.transport_version))
@@ -1818,47 +1834,59 @@
   EXPECT_TRUE(session_->CanPool(
       "www.example.org",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
-                     kNetworkIsolationKey1, SecureDnsPolicy::kAllow)));
+                     kNetworkIsolationKey1, SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
   EXPECT_FALSE(session_->CanPool(
       "www.example.org",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_ENABLED, SocketTag(),
-                     kNetworkIsolationKey1, SecureDnsPolicy::kAllow)));
+                     kNetworkIsolationKey1, SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
 #if BUILDFLAG(IS_ANDROID)
   SocketTag tag1(SocketTag::UNSET_UID, 0x12345678);
   SocketTag tag2(getuid(), 0x87654321);
   EXPECT_FALSE(session_->CanPool(
       "www.example.org",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, tag1,
-                     kNetworkIsolationKey1, SecureDnsPolicy::kAllow)));
+                     kNetworkIsolationKey1, SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
   EXPECT_FALSE(session_->CanPool(
       "www.example.org",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, tag2,
-                     kNetworkIsolationKey1, SecureDnsPolicy::kAllow)));
+                     kNetworkIsolationKey1, SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
 #endif
   EXPECT_TRUE(session_->CanPool(
       "mail.example.org",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
-                     kNetworkIsolationKey1, SecureDnsPolicy::kAllow)));
+                     kNetworkIsolationKey1, SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
   EXPECT_TRUE(session_->CanPool(
       "mail.example.com",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
-                     kNetworkIsolationKey1, SecureDnsPolicy::kAllow)));
+                     kNetworkIsolationKey1, SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
   EXPECT_FALSE(session_->CanPool(
       "mail.google.com",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
-                     kNetworkIsolationKey1, SecureDnsPolicy::kAllow)));
+                     kNetworkIsolationKey1, SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
 
   EXPECT_FALSE(session_->CanPool(
       "mail.example.com",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
-                     kNetworkIsolationKey2, SecureDnsPolicy::kAllow)));
+                     kNetworkIsolationKey2, SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
   EXPECT_FALSE(session_->CanPool(
       "mail.example.com",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
-                     NetworkIsolationKey(), SecureDnsPolicy::kAllow)));
+                     NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
 }
 
 TEST_P(QuicChromiumClientSessionTest, ConnectionNotPooledWithDifferentPin) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   // Configure the TransportSecurityStateSource so that kPreloadedPKPHost will
   // have static PKP pins set.
   ScopedTransportSecurityStateSource scoped_security_state_source;
@@ -1898,7 +1926,8 @@
   EXPECT_FALSE(session_->CanPool(
       kPreloadedPKPHost,
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
-                     NetworkIsolationKey(), SecureDnsPolicy::kAllow)));
+                     NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
 }
 
 TEST_P(QuicChromiumClientSessionTest, ConnectionPooledWithMatchingPin) {
@@ -1932,7 +1961,8 @@
   EXPECT_TRUE(session_->CanPool(
       "mail.example.org",
       QuicSessionKey("foo", 1234, PRIVACY_MODE_DISABLED, SocketTag(),
-                     NetworkIsolationKey(), SecureDnsPolicy::kAllow)));
+                     NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                     /*require_dns_https_alpn=*/false)));
 }
 
 TEST_P(QuicChromiumClientSessionTest, MigrateToSocket) {
diff --git a/net/quic/quic_http_stream_test.cc b/net/quic/quic_http_stream_test.cc
index 431197de..048fc59 100644
--- a/net/quic/quic_http_stream_test.cc
+++ b/net/quic/quic_http_stream_test.cc
@@ -405,7 +405,8 @@
         base::WrapUnique(static_cast<QuicServerInfo*>(nullptr)),
         QuicSessionKey(kDefaultServerHostName, kDefaultServerPort,
                        PRIVACY_MODE_DISABLED, SocketTag(),
-                       NetworkIsolationKey(), SecureDnsPolicy::kAllow),
+                       NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                       /*require_dns_https_alpn=*/false),
         /*require_confirmation=*/false,
         /*migrate_session_early_v2=*/false,
         /*migrate_session_on_network_change_v2=*/false,
diff --git a/net/quic/quic_proxy_client_socket_unittest.cc b/net/quic/quic_proxy_client_socket_unittest.cc
index dcc91cc..107703a 100644
--- a/net/quic/quic_proxy_client_socket_unittest.cc
+++ b/net/quic/quic_proxy_client_socket_unittest.cc
@@ -267,7 +267,8 @@
         base::WrapUnique(static_cast<QuicServerInfo*>(nullptr)),
         QuicSessionKey("mail.example.org", 80, PRIVACY_MODE_DISABLED,
                        SocketTag(), NetworkIsolationKey(),
-                       SecureDnsPolicy::kAllow),
+                       SecureDnsPolicy::kAllow,
+                       /*require_dns_https_alpn=*/false),
         /*require_confirmation=*/false,
         /*migrate_session_early_v2=*/false,
         /*migrate_session_on_network_change_v2=*/false,
diff --git a/net/quic/quic_session_key.cc b/net/quic/quic_session_key.cc
index 8a1cc0a..c67ec6d 100644
--- a/net/quic/quic_session_key.cc
+++ b/net/quic/quic_session_key.cc
@@ -16,31 +16,36 @@
                                PrivacyMode privacy_mode,
                                const SocketTag& socket_tag,
                                const NetworkIsolationKey& network_isolation_key,
-                               SecureDnsPolicy secure_dns_policy)
+                               SecureDnsPolicy secure_dns_policy,
+                               bool require_dns_https_alpn)
     : QuicSessionKey(host_port_pair.host(),
                      host_port_pair.port(),
                      privacy_mode,
                      socket_tag,
                      network_isolation_key,
-                     secure_dns_policy) {}
+                     secure_dns_policy,
+                     require_dns_https_alpn) {}
 
 QuicSessionKey::QuicSessionKey(const std::string& host,
                                uint16_t port,
                                PrivacyMode privacy_mode,
                                const SocketTag& socket_tag,
                                const NetworkIsolationKey& network_isolation_key,
-                               SecureDnsPolicy secure_dns_policy)
+                               SecureDnsPolicy secure_dns_policy,
+                               bool require_dns_https_alpn)
     : QuicSessionKey(
           // TODO(crbug.com/1103350): Handle non-boolean privacy modes.
           quic::QuicServerId(host, port, privacy_mode != PRIVACY_MODE_DISABLED),
           socket_tag,
           network_isolation_key,
-          secure_dns_policy) {}
+          secure_dns_policy,
+          require_dns_https_alpn) {}
 
 QuicSessionKey::QuicSessionKey(const quic::QuicServerId& server_id,
                                const SocketTag& socket_tag,
                                const NetworkIsolationKey& network_isolation_key,
-                               SecureDnsPolicy secure_dns_policy)
+                               SecureDnsPolicy secure_dns_policy,
+                               bool require_dns_https_alpn)
     : server_id_(server_id),
       socket_tag_(socket_tag),
       network_isolation_key_(
@@ -48,20 +53,23 @@
               features::kPartitionConnectionsByNetworkIsolationKey)
               ? network_isolation_key
               : NetworkIsolationKey()),
-      secure_dns_policy_(secure_dns_policy) {}
+      secure_dns_policy_(secure_dns_policy),
+      require_dns_https_alpn_(require_dns_https_alpn) {}
 
 QuicSessionKey::QuicSessionKey(const QuicSessionKey& other) = default;
 
 bool QuicSessionKey::operator<(const QuicSessionKey& other) const {
   return std::tie(server_id_, socket_tag_, network_isolation_key_,
-                  secure_dns_policy_) <
+                  secure_dns_policy_, require_dns_https_alpn_) <
          std::tie(other.server_id_, other.socket_tag_,
-                  other.network_isolation_key_, other.secure_dns_policy_);
+                  other.network_isolation_key_, other.secure_dns_policy_,
+                  other.require_dns_https_alpn_);
 }
 bool QuicSessionKey::operator==(const QuicSessionKey& other) const {
   return server_id_ == other.server_id_ && socket_tag_ == other.socket_tag_ &&
          network_isolation_key_ == other.network_isolation_key_ &&
-         secure_dns_policy_ == other.secure_dns_policy_;
+         secure_dns_policy_ == other.secure_dns_policy_ &&
+         require_dns_https_alpn_ == other.require_dns_https_alpn_;
 }
 
 bool QuicSessionKey::CanUseForAliasing(const QuicSessionKey& other) const {
@@ -69,7 +77,8 @@
              other.server_id_.privacy_mode_enabled() &&
          socket_tag_ == other.socket_tag_ &&
          network_isolation_key_ == other.network_isolation_key_ &&
-         secure_dns_policy_ == other.secure_dns_policy_;
+         secure_dns_policy_ == other.secure_dns_policy_ &&
+         require_dns_https_alpn_ == other.require_dns_https_alpn_;
 }
 
 }  // namespace net
diff --git a/net/quic/quic_session_key.h b/net/quic/quic_session_key.h
index 6d82c0b..09a2c86 100644
--- a/net/quic/quic_session_key.h
+++ b/net/quic/quic_session_key.h
@@ -23,17 +23,20 @@
                  PrivacyMode privacy_mode,
                  const SocketTag& socket_tag,
                  const NetworkIsolationKey& network_isolation_key,
-                 SecureDnsPolicy secure_dns_policy);
+                 SecureDnsPolicy secure_dns_policy,
+                 bool require_dns_https_alpn);
   QuicSessionKey(const std::string& host,
                  uint16_t port,
                  PrivacyMode privacy_mode,
                  const SocketTag& socket_tag,
                  const NetworkIsolationKey& network_isolation_key,
-                 SecureDnsPolicy secure_dns_policy);
+                 SecureDnsPolicy secure_dns_policy,
+                 bool require_dns_https_alpn);
   QuicSessionKey(const quic::QuicServerId& server_id,
                  const SocketTag& socket_tag,
                  const NetworkIsolationKey& network_isolation_key,
-                 SecureDnsPolicy secure_dns_policy);
+                 SecureDnsPolicy secure_dns_policy,
+                 bool require_dns_https_alpn);
   QuicSessionKey(const QuicSessionKey& other);
   ~QuicSessionKey() = default;
 
@@ -68,12 +71,15 @@
 
   SecureDnsPolicy secure_dns_policy() const { return secure_dns_policy_; }
 
+  bool require_dns_https_alpn() const { return require_dns_https_alpn_; }
+
  private:
   quic::QuicServerId server_id_;
   SocketTag socket_tag_;
   // Used to separate requests made in different contexts.
   NetworkIsolationKey network_isolation_key_;
   SecureDnsPolicy secure_dns_policy_;
+  bool require_dns_https_alpn_ = false;
 };
 
 }  // namespace net
diff --git a/net/quic/quic_stream_factory.cc b/net/quic/quic_stream_factory.cc
index 68fd9b1..3e933545 100644
--- a/net/quic/quic_stream_factory.cc
+++ b/net/quic/quic_stream_factory.cc
@@ -227,6 +227,25 @@
   return hosts;
 }
 
+quic::ParsedQuicVersion SelectQuicVersion(
+    const std::vector<HostResolverEndpointResult>& endpoint_results,
+    const quic::ParsedQuicVersionVector& supported_versions) {
+  for (const auto& result : endpoint_results) {
+    for (const auto& alpn : result.metadata.supported_protocol_alpns) {
+      quic::ParsedQuicVersion version = quic::ParseQuicVersionString(alpn);
+      if (!version.IsKnown()) {
+        continue;
+      }
+      for (const auto& supported_version : supported_versions) {
+        if (supported_version == version) {
+          return version;
+        }
+      }
+    }
+  }
+  return quic::UnsupportedQuicVersion();
+}
+
 }  // namespace
 
 // Refcounted class that owns quic::QuicCryptoClientConfig and tracks how many
@@ -358,6 +377,7 @@
       bool race_stale_dns_on_connection,
       RequestPriority priority,
       bool use_dns_aliases,
+      bool require_dns_https_alpn,
       int cert_verify_flags,
       const NetLogWithSource& net_log);
 
@@ -497,6 +517,7 @@
   const std::unique_ptr<CryptoClientConfigHandle> client_config_handle_;
   RequestPriority priority_;
   const bool use_dns_aliases_;
+  const bool require_dns_https_alpn_;
   const int cert_verify_flags_;
   const bool was_alternative_service_recently_broken_;
   const bool retry_on_alternate_network_before_handshake_;
@@ -532,6 +553,7 @@
     bool race_stale_dns_on_connection,
     RequestPriority priority,
     bool use_dns_aliases,
+    bool require_dns_https_alpn,
     int cert_verify_flags,
     const NetLogWithSource& net_log)
     : io_state_(STATE_RESOLVE_HOST),
@@ -542,6 +564,7 @@
       client_config_handle_(std::move(client_config_handle)),
       priority_(priority),
       use_dns_aliases_(use_dns_aliases),
+      require_dns_https_alpn_(require_dns_https_alpn),
       cert_verify_flags_(cert_verify_flags),
       was_alternative_service_recently_broken_(
           was_alternative_service_recently_broken),
@@ -555,6 +578,7 @@
       connection_retried_(false),
       session_(nullptr),
       network_(NetworkChangeNotifier::kInvalidNetworkHandle) {
+  DCHECK_EQ(quic_version.IsKnown(), !require_dns_https_alpn);
   net_log_.BeginEvent(NetLogEventType::QUIC_STREAM_FACTORY_JOB,
                       [&] { return NetLogQuicStreamFactoryJobParams(&key_); });
   // Associate |net_log_| with |net_log|.
@@ -798,6 +822,15 @@
   DCHECK(!fresh_resolve_host_request_);
   DCHECK(!factory_->HasActiveSession(key_.session_key()));
 
+  if (require_dns_https_alpn_) {
+    quic_version_ =
+        SelectQuicVersion(*resolve_host_request_->GetEndpointResults(),
+                          factory_->supported_versions());
+    if (quic_version_ == quic::ParsedQuicVersion::Unsupported()) {
+      return ERR_DNS_NO_MACHING_SUPPORTED_ALPN;
+    }
+  }
+
   // Inform the factory of this resolution, which will set up
   // a session alias, if possible.
   if (factory_->HasMatchingIpSession(
@@ -1016,13 +1049,14 @@
     const NetworkIsolationKey& network_isolation_key,
     SecureDnsPolicy secure_dns_policy,
     bool use_dns_aliases,
+    bool require_dns_https_alpn,
     int cert_verify_flags,
     const GURL& url,
     const NetLogWithSource& net_log,
     NetErrorDetails* net_error_details,
     CompletionOnceCallback failed_on_default_network_callback,
     CompletionOnceCallback callback) {
-  DCHECK_NE(quic_version, quic::ParsedQuicVersion::Unsupported());
+  DCHECK_EQ(quic_version.IsKnown(), !require_dns_https_alpn);
   DCHECK(net_error_details);
   DCHECK(callback_.is_null());
   DCHECK(host_resolution_callback_.is_null());
@@ -1031,9 +1065,10 @@
   net_error_details_ = net_error_details;
   failed_on_default_network_callback_ =
       std::move(failed_on_default_network_callback);
-  session_key_ =
-      QuicSessionKey(HostPortPair::FromURL(url), privacy_mode, socket_tag,
-                     network_isolation_key, secure_dns_policy);
+
+  session_key_ = QuicSessionKey(HostPortPair::FromURL(url), privacy_mode,
+                                socket_tag, network_isolation_key,
+                                secure_dns_policy, require_dns_https_alpn);
 
   int rv = factory_->Create(session_key_, std::move(destination), quic_version,
                             priority, use_dns_aliases, cert_verify_flags, url,
@@ -1303,7 +1338,7 @@
       WasQuicRecentlyBroken(session_key),
       params_.retry_on_alternate_network_before_handshake,
       params_.race_stale_dns_on_connection, priority, use_dns_aliases,
-      cert_verify_flags, net_log);
+      session_key.require_dns_https_alpn(), cert_verify_flags, net_log);
   int rv = job->Run(base::BindOnce(&QuicStreamFactory::OnJobComplete,
                                    base::Unretained(this), job.get()));
   if (rv == ERR_IO_PENDING) {
diff --git a/net/quic/quic_stream_factory.h b/net/quic/quic_stream_factory.h
index 8d263c0..d175b15 100644
--- a/net/quic/quic_stream_factory.h
+++ b/net/quic/quic_stream_factory.h
@@ -131,6 +131,7 @@
               const NetworkIsolationKey& network_isolation_key,
               SecureDnsPolicy secure_dns_policy,
               bool use_dns_aliases,
+              bool require_dns_https_alpn,
               int cert_verify_flags,
               const GURL& url,
               const NetLogWithSource& net_log,
@@ -506,6 +507,10 @@
       const quic::QuicServerId& server_id,
       const NetworkIsolationKey& network_isolation_key);
 
+  const quic::ParsedQuicVersionVector& supported_versions() const {
+    return params_.supported_versions;
+  }
+
   // Whether QUIC is known to work on current network. This is true when QUIC is
   // expected to work in general, rather than whether QUIC was broken / recently
   // broken when used with a particular server. That information is stored in
diff --git a/net/quic/quic_stream_factory_fuzzer.cc b/net/quic/quic_stream_factory_fuzzer.cc
index e7c0fe5..c5c56bd 100644
--- a/net/quic/quic_stream_factory_fuzzer.cc
+++ b/net/quic/quic_stream_factory_fuzzer.cc
@@ -146,8 +146,8 @@
   request.Request(
       env->scheme_host_port, version, PRIVACY_MODE_DISABLED, DEFAULT_PRIORITY,
       SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-      true /* use_dns_aliases */, kCertVerifyFlags, GURL(kUrl), env->net_log,
-      &net_error_details,
+      true /* use_dns_aliases */, false /* require_dns_https_alpn */,
+      kCertVerifyFlags, GURL(kUrl), env->net_log, &net_error_details,
       /*failed_on_default_network_callback=*/CompletionOnceCallback(),
       callback.callback());
 
diff --git a/net/quic/quic_stream_factory_peer.cc b/net/quic/quic_stream_factory_peer.cc
index 4a5c9065..2d078b43 100644
--- a/net/quic/quic_stream_factory_peer.cc
+++ b/net/quic/quic_stream_factory_peer.cc
@@ -41,13 +41,15 @@
     const quic::QuicServerId& server_id,
     const NetworkIsolationKey& network_isolation_key) {
   return factory->HasActiveSession(QuicSessionKey(
-      server_id, SocketTag(), network_isolation_key, SecureDnsPolicy::kAllow));
+      server_id, SocketTag(), network_isolation_key, SecureDnsPolicy::kAllow,
+      /*require_dns_https_alpn=*/false));
 }
 
 bool QuicStreamFactoryPeer::HasActiveJob(QuicStreamFactory* factory,
                                          const quic::QuicServerId& server_id) {
   return factory->HasActiveJob(QuicSessionKey(
-      server_id, SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow));
+      server_id, SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+      /*require_dns_https_alpn=*/false));
 }
 
 // static
@@ -56,7 +58,8 @@
     const quic::QuicServerId& server_id,
     url::SchemeHostPort destination) {
   QuicSessionKey session_key(server_id, SocketTag(), NetworkIsolationKey(),
-                             SecureDnsPolicy::kAllow);
+                             SecureDnsPolicy::kAllow,
+                             /*require_dns_https_alpn=*/false);
   QuicStreamFactory::QuicSessionAliasKey key(std::move(destination),
                                              session_key);
   DCHECK(factory->HasActiveJob(session_key));
@@ -70,7 +73,8 @@
     const quic::QuicServerId& server_id,
     const NetworkIsolationKey& network_isolation_key) {
   QuicSessionKey session_key(server_id, SocketTag(), network_isolation_key,
-                             SecureDnsPolicy::kAllow);
+                             SecureDnsPolicy::kAllow,
+                             /*require_dns_https_alpn=*/false);
   DCHECK(factory->HasActiveSession(session_key));
   return factory->active_sessions_[session_key];
 }
@@ -79,8 +83,9 @@
     QuicStreamFactory* factory,
     url::SchemeHostPort destination,
     const quic::QuicServerId& server_id) {
-  QuicSessionKey session_key = QuicSessionKey(
-      server_id, SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow);
+  QuicSessionKey session_key =
+      QuicSessionKey(server_id, SocketTag(), NetworkIsolationKey(),
+                     SecureDnsPolicy::kAllow, /*require_dns_https_alpn=*/false);
   QuicStreamFactory::QuicSessionAliasKey alias_key(std::move(destination),
                                                    session_key);
   for (auto it = factory->all_sessions_.begin();
diff --git a/net/quic/quic_stream_factory_test.cc b/net/quic/quic_stream_factory_test.cc
index 015cdc6..1151c34 100644
--- a/net/quic/quic_stream_factory_test.cc
+++ b/net/quic/quic_stream_factory_test.cc
@@ -386,7 +386,7 @@
               request.Request(
                   destination, version_, privacy_mode_, DEFAULT_PRIORITY,
                   SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                  true /* use_dns_aliases */,
+                  /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                   /*cert_verify_flags=*/0, url, net_log_, &net_error_details_,
                   failed_on_default_network_callback_, callback_.callback()));
 
@@ -552,7 +552,7 @@
               request.Request(
                   scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                   SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                  true /* use_dns_aliases */,
+                  /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                   /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                   failed_on_default_network_callback_, callback_.callback()));
 
@@ -769,8 +769,8 @@
                                       quic_server_id1.port()),
                   version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
                   network_isolation_key1, SecureDnsPolicy::kAllow,
-                  true /* use_dns_aliases */, /*cert_verify_flags=*/0, url_,
-                  net_log_, &net_error_details_,
+                  /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+                  /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                   failed_on_default_network_callback_, callback_.callback()));
     EXPECT_THAT(callback_.WaitForResult(), IsOk());
 
@@ -814,7 +814,8 @@
                                 quic_server_id2.port()),
             version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
             network_isolation_key2, SecureDnsPolicy::kAllow,
-            true /* use_dns_aliases */, /*cert_verify_flags=*/0,
+            /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+            /*cert_verify_flags=*/0,
             vary_network_isolation_key ? url_
                                        : GURL("https://mail.example.org/"),
             net_log_, &net_error_details_, failed_on_default_network_callback_,
@@ -937,6 +938,11 @@
   // Port migrations.
   void TestSimplePortMigrationOnPathDegrading();
 
+  // Tests for DNS HTTPS record with alpn.
+  void TestRequireDnsHttpsAlpn(
+      std::vector<HostResolverEndpointResult> endpoints,
+      bool expect_success);
+
   QuicFlagSaver flags_;  // Save/restore all QUIC flag values.
   std::unique_ptr<MockHostResolverBase> host_resolver_;
   std::unique_ptr<SSLConfigService> ssl_config_service_;
@@ -1000,7 +1006,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -1015,7 +1021,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   // Will reset stream 3.
@@ -1030,7 +1036,7 @@
             request3.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   stream = CreateStream(&request3);  // Will reset stream 5.
@@ -1064,7 +1070,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -1103,7 +1109,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_FALSE(HasActiveSession(scheme_host_port_));
@@ -1136,7 +1142,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -1166,7 +1172,7 @@
             request->Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   request.reset();
@@ -1199,7 +1205,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -1243,7 +1249,7 @@
   EXPECT_THAT(request.Request(
                   scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                   SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                  true /* use_dns_aliases */,
+                  /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                   /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                   failed_on_default_network_callback_, callback_.callback()),
               IsOk());
@@ -1284,7 +1290,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -1352,7 +1358,7 @@
               request.Request(
                   scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                   SocketTag(), network_isolation_key, SecureDnsPolicy::kAllow,
-                  true /* use_dns_aliases */,
+                  /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                   /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                   failed_on_default_network_callback_, callback_.callback()));
 
@@ -1395,7 +1401,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -1430,7 +1436,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -1460,7 +1466,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -1503,7 +1509,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -1579,13 +1585,14 @@
     socket_data.AddSocketDataToFactory(socket_factory_.get());
 
     QuicStreamRequest request(factory_.get());
-    EXPECT_EQ(ERR_IO_PENDING,
-              request.Request(
-                  scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
-                  SocketTag(), kNetworkIsolationKeys[i],
-                  SecureDnsPolicy::kAllow, true /* use_dns_aliases */,
-                  /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
-                  failed_on_default_network_callback_, callback_.callback()));
+    EXPECT_EQ(
+        ERR_IO_PENDING,
+        request.Request(
+            scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
+            SocketTag(), kNetworkIsolationKeys[i], SecureDnsPolicy::kAllow,
+            /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+            /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
+            failed_on_default_network_callback_, callback_.callback()));
 
     EXPECT_THAT(callback_.WaitForResult(), IsOk());
     std::unique_ptr<HttpStream> stream = CreateStream(&request);
@@ -1636,13 +1643,14 @@
     socket_data.AddSocketDataToFactory(socket_factory_.get());
 
     QuicStreamRequest request(factory_.get());
-    EXPECT_EQ(ERR_IO_PENDING,
-              request.Request(
-                  scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
-                  SocketTag(), kNetworkIsolationKeys[i],
-                  SecureDnsPolicy::kAllow, true /* use_dns_aliases */,
-                  /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
-                  failed_on_default_network_callback_, callback_.callback()));
+    EXPECT_EQ(
+        ERR_IO_PENDING,
+        request.Request(
+            scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
+            SocketTag(), kNetworkIsolationKeys[i], SecureDnsPolicy::kAllow,
+            /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+            /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
+            failed_on_default_network_callback_, callback_.callback()));
 
     EXPECT_THAT(callback_.WaitForResult(), IsError(ERR_QUIC_HANDSHAKE_FAILED));
 
@@ -1685,7 +1693,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   std::unique_ptr<HttpStream> stream = CreateStream(&request);
@@ -1693,12 +1701,13 @@
 
   TestCompletionCallback callback;
   QuicStreamRequest request2(factory_.get());
-  EXPECT_EQ(OK, request2.Request(
-                    server2, version_, privacy_mode_, DEFAULT_PRIORITY,
-                    SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                    true /* use_dns_aliases */, /*cert_verify_flags=*/0, url2_,
-                    net_log_, &net_error_details_,
-                    failed_on_default_network_callback_, callback.callback()));
+  EXPECT_EQ(OK,
+            request2.Request(
+                server2, version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
+                NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+                /*cert_verify_flags=*/0, url2_, net_log_, &net_error_details_,
+                failed_on_default_network_callback_, callback.callback()));
   std::unique_ptr<HttpStream> stream2 = CreateStream(&request2);
   EXPECT_TRUE(stream2.get());
 
@@ -1760,8 +1769,8 @@
             request2.Request(
                 server2, version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
                 NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */, /*cert_verify_flags=*/0, url2_,
-                net_log_, &net_error_details_,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+                /*cert_verify_flags=*/0, url2_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback.callback()));
   EXPECT_EQ(OK, callback.WaitForResult());
   std::unique_ptr<HttpStream> stream2 = CreateStream(&request2);
@@ -1806,7 +1815,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   std::unique_ptr<HttpStream> stream = CreateStream(&request);
@@ -1814,12 +1823,13 @@
 
   TestCompletionCallback callback;
   QuicStreamRequest request2(factory_.get());
-  EXPECT_EQ(OK, request2.Request(
-                    server2, version_, privacy_mode_, DEFAULT_PRIORITY,
-                    SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                    true /* use_dns_aliases */, /*cert_verify_flags=*/0, url2_,
-                    net_log_, &net_error_details_,
-                    failed_on_default_network_callback_, callback.callback()));
+  EXPECT_EQ(OK,
+            request2.Request(
+                server2, version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
+                NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+                /*cert_verify_flags=*/0, url2_, net_log_, &net_error_details_,
+                failed_on_default_network_callback_, callback.callback()));
   std::unique_ptr<HttpStream> stream2 = CreateStream(&request2);
   EXPECT_TRUE(stream2.get());
 
@@ -1829,12 +1839,13 @@
 
   TestCompletionCallback callback3;
   QuicStreamRequest request3(factory_.get());
-  EXPECT_EQ(OK, request3.Request(
-                    server2, version_, privacy_mode_, DEFAULT_PRIORITY,
-                    SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                    true /* use_dns_aliases */, /*cert_verify_flags=*/0, url2_,
-                    net_log_, &net_error_details_,
-                    failed_on_default_network_callback_, callback3.callback()));
+  EXPECT_EQ(OK,
+            request3.Request(
+                server2, version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
+                NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+                /*cert_verify_flags=*/0, url2_, net_log_, &net_error_details_,
+                failed_on_default_network_callback_, callback3.callback()));
   std::unique_ptr<HttpStream> stream3 = CreateStream(&request3);
   EXPECT_TRUE(stream3.get());
 
@@ -1866,23 +1877,25 @@
   host_resolver_->rules()->AddIPLiteralRule(server2.host(), "192.168.0.1", "");
 
   QuicStreamRequest request(factory_.get());
-  EXPECT_EQ(OK, request.Request(
-                    server1, version_, privacy_mode_, DEFAULT_PRIORITY,
-                    SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                    true /* use_dns_aliases */, /*cert_verify_flags=*/0, url_,
-                    net_log_, &net_error_details_,
-                    failed_on_default_network_callback_, callback_.callback()));
+  EXPECT_EQ(OK,
+            request.Request(
+                server1, version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
+                NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+                /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
+                failed_on_default_network_callback_, callback_.callback()));
   std::unique_ptr<HttpStream> stream = CreateStream(&request);
   EXPECT_TRUE(stream.get());
 
   TestCompletionCallback callback;
   QuicStreamRequest request2(factory_.get());
-  EXPECT_EQ(OK, request2.Request(
-                    server2, version_, privacy_mode_, DEFAULT_PRIORITY,
-                    SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                    true /* use_dns_aliases */, /*cert_verify_flags=*/0, url2_,
-                    net_log_, &net_error_details_,
-                    failed_on_default_network_callback_, callback_.callback()));
+  EXPECT_EQ(OK,
+            request2.Request(
+                server2, version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
+                NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+                /*cert_verify_flags=*/0, url2_, net_log_, &net_error_details_,
+                failed_on_default_network_callback_, callback_.callback()));
   std::unique_ptr<HttpStream> stream2 = CreateStream(&request2);
   EXPECT_TRUE(stream2.get());
 
@@ -1917,23 +1930,25 @@
   host_resolver_->rules()->AddIPLiteralRule(server2.host(), "192.168.0.1", "");
 
   QuicStreamRequest request(factory_.get());
-  EXPECT_EQ(OK, request.Request(
-                    server1, version_, privacy_mode_, DEFAULT_PRIORITY,
-                    SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                    true /* use_dns_aliases */, /*cert_verify_flags=*/0, url_,
-                    net_log_, &net_error_details_,
-                    failed_on_default_network_callback_, callback_.callback()));
+  EXPECT_EQ(OK,
+            request.Request(
+                server1, version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
+                NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+                /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
+                failed_on_default_network_callback_, callback_.callback()));
   std::unique_ptr<HttpStream> stream = CreateStream(&request);
   EXPECT_TRUE(stream.get());
 
   TestCompletionCallback callback;
   QuicStreamRequest request2(factory_.get());
-  EXPECT_EQ(OK, request2.Request(
-                    server2, version_, privacy_mode_, DEFAULT_PRIORITY,
-                    SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                    true /* use_dns_aliases */, /*cert_verify_flags=*/0, url2_,
-                    net_log_, &net_error_details_,
-                    failed_on_default_network_callback_, callback_.callback()));
+  EXPECT_EQ(OK,
+            request2.Request(
+                server2, version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
+                NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+                /*cert_verify_flags=*/0, url2_, net_log_, &net_error_details_,
+                failed_on_default_network_callback_, callback_.callback()));
   std::unique_ptr<HttpStream> stream2 = CreateStream(&request2);
   EXPECT_TRUE(stream2.get());
 
@@ -1944,6 +1959,9 @@
 }
 
 TEST_P(QuicStreamFactoryTest, NoHttpsPoolingWithDifferentPins) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   Initialize();
 
   MockQuicData socket_data1(version_);
@@ -1981,23 +1999,25 @@
   host_resolver_->rules()->AddIPLiteralRule(server2.host(), "192.168.0.1", "");
 
   QuicStreamRequest request(factory_.get());
-  EXPECT_EQ(OK, request.Request(
-                    server1, version_, privacy_mode_, DEFAULT_PRIORITY,
-                    SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                    true /* use_dns_aliases */, /*cert_verify_flags=*/0, url_,
-                    net_log_, &net_error_details_,
-                    failed_on_default_network_callback_, callback_.callback()));
+  EXPECT_EQ(OK,
+            request.Request(
+                server1, version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
+                NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+                /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
+                failed_on_default_network_callback_, callback_.callback()));
   std::unique_ptr<HttpStream> stream = CreateStream(&request);
   EXPECT_TRUE(stream.get());
 
   TestCompletionCallback callback;
   QuicStreamRequest request2(factory_.get());
-  EXPECT_EQ(OK, request2.Request(
-                    server2, version_, privacy_mode_, DEFAULT_PRIORITY,
-                    SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                    true /* use_dns_aliases */, /*cert_verify_flags=*/0, url2_,
-                    net_log_, &net_error_details_,
-                    failed_on_default_network_callback_, callback_.callback()));
+  EXPECT_EQ(OK,
+            request2.Request(
+                server2, version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
+                NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+                /*cert_verify_flags=*/0, url2_, net_log_, &net_error_details_,
+                failed_on_default_network_callback_, callback_.callback()));
   std::unique_ptr<HttpStream> stream2 = CreateStream(&request2);
   EXPECT_TRUE(stream2.get());
 
@@ -2032,7 +2052,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -2055,7 +2075,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -2129,7 +2149,7 @@
     int rv = request.Request(
         scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
         SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-        true /* use_dns_aliases */,
+        /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
         /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
         failed_on_default_network_callback_, callback_.callback());
     if (i == 0) {
@@ -2151,7 +2171,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, CompletionOnceCallback()));
   std::unique_ptr<HttpStream> stream = CreateStream(&request);
@@ -2192,7 +2212,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -2214,7 +2234,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -2237,7 +2257,7 @@
               request.Request(
                   scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                   SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                  true /* use_dns_aliases */,
+                  /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                   /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                   failed_on_default_network_callback_, callback_.callback()));
   }
@@ -2249,7 +2269,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   std::unique_ptr<HttpStream> stream = CreateStream(&request2);
@@ -2292,7 +2312,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -2319,7 +2339,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -2356,7 +2376,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(ERR_QUIC_HANDSHAKE_FAILED, callback_.WaitForResult());
@@ -2380,7 +2400,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_FALSE(HasActiveSession(scheme_host_port_));
@@ -2426,7 +2446,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   // Check no active session, or active jobs left for this server.
@@ -2450,7 +2470,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_FALSE(HasActiveSession(scheme_host_port_));
@@ -2505,7 +2525,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -2541,7 +2561,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -2603,7 +2623,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -2651,7 +2671,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -2700,7 +2720,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -2726,7 +2746,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   stream = CreateStream(&request2);
@@ -2818,7 +2838,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -2982,7 +3002,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -3093,7 +3113,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -3225,7 +3245,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -3304,7 +3324,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -3425,7 +3445,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -3494,7 +3514,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -3596,7 +3616,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -3680,7 +3700,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -3756,7 +3776,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -3896,7 +3916,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -4090,7 +4110,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -4259,7 +4279,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -4415,7 +4435,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -4556,7 +4576,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -4685,7 +4705,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -4809,7 +4829,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -5001,7 +5021,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -5141,7 +5161,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -5368,7 +5388,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -5522,7 +5542,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -5671,7 +5691,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -5824,7 +5844,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -5952,23 +5972,25 @@
 
   // Create request and QuicHttpStream to create session1.
   QuicStreamRequest request1(factory_.get());
-  EXPECT_EQ(OK, request1.Request(
-                    server1, version_, privacy_mode_, DEFAULT_PRIORITY,
-                    SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                    true /* use_dns_aliases */, /*cert_verify_flags=*/0, url_,
-                    net_log_, &net_error_details_,
-                    failed_on_default_network_callback_, callback_.callback()));
+  EXPECT_EQ(OK,
+            request1.Request(
+                server1, version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
+                NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+                /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
+                failed_on_default_network_callback_, callback_.callback()));
   std::unique_ptr<HttpStream> stream1 = CreateStream(&request1);
   EXPECT_TRUE(stream1.get());
 
   // Create request and QuicHttpStream to create session2.
   QuicStreamRequest request2(factory_.get());
-  EXPECT_EQ(OK, request2.Request(
-                    server2, version_, privacy_mode_, DEFAULT_PRIORITY,
-                    SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                    true /* use_dns_aliases */, /*cert_verify_flags=*/0, url2_,
-                    net_log_, &net_error_details_,
-                    failed_on_default_network_callback_, callback_.callback()));
+  EXPECT_EQ(OK,
+            request2.Request(
+                server2, version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
+                NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+                /*cert_verify_flags=*/0, url2_, net_log_, &net_error_details_,
+                failed_on_default_network_callback_, callback_.callback()));
   std::unique_ptr<HttpStream> stream2 = CreateStream(&request2);
   EXPECT_TRUE(stream2.get());
 
@@ -6088,7 +6110,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -6229,7 +6251,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -6309,7 +6331,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -6446,7 +6468,7 @@
             request1.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -6469,7 +6491,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback2.callback()));
   std::unique_ptr<HttpStream> stream2 = CreateStream(&request2);
@@ -6613,7 +6635,7 @@
             request1.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -6754,7 +6776,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -6818,7 +6840,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -6959,7 +6981,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -7071,7 +7093,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(ERR_QUIC_HANDSHAKE_FAILED, callback_.WaitForResult());
@@ -7095,7 +7117,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_FALSE(HasActiveSession(scheme_host_port_));
@@ -7184,7 +7206,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   // Ensure that the session is alive but not active.
@@ -7259,7 +7281,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -7385,7 +7407,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -7557,7 +7579,7 @@
             request1.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -7580,7 +7602,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback2.callback()));
   std::unique_ptr<HttpStream> stream2 = CreateStream(&request2);
@@ -7717,7 +7739,7 @@
             request1.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -7740,7 +7762,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback2.callback()));
   std::unique_ptr<HttpStream> stream2 = CreateStream(&request2);
@@ -7885,7 +7907,7 @@
             request1.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -7908,7 +7930,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback2.callback()));
   std::unique_ptr<HttpStream> stream2 = CreateStream(&request2);
@@ -8034,7 +8056,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -8125,7 +8147,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -8235,7 +8257,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -8327,7 +8349,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   // Deliver the network notification, which should cause the connection to be
@@ -8370,7 +8392,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -8535,7 +8557,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -8706,7 +8728,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -8867,7 +8889,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -8991,7 +9013,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -9128,7 +9150,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -9336,7 +9358,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -9505,7 +9527,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -9643,7 +9665,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -9776,7 +9798,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -9909,7 +9931,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -10042,7 +10064,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -10136,7 +10158,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -10293,7 +10315,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -10411,7 +10433,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   std::unique_ptr<HttpStream> stream2 = CreateStream(&request2);
@@ -10598,7 +10620,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -10794,7 +10816,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -10884,7 +10906,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -11075,7 +11097,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -11159,7 +11181,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -11221,7 +11243,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -11245,7 +11267,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -11375,7 +11397,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -11430,7 +11452,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -11464,12 +11486,13 @@
   DVLOG(1) << "Create 2nd session and timeout with open stream";
   TestCompletionCallback callback2;
   QuicStreamRequest request2(factory_.get());
-  EXPECT_EQ(OK, request2.Request(
-                    server2, version_, privacy_mode_, DEFAULT_PRIORITY,
-                    SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                    true /* use_dns_aliases */, /*cert_verify_flags=*/0, url2_,
-                    net_log_, &net_error_details_,
-                    failed_on_default_network_callback_, callback2.callback()));
+  EXPECT_EQ(OK,
+            request2.Request(
+                server2, version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
+                NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+                /*cert_verify_flags=*/0, url2_, net_log_, &net_error_details_,
+                failed_on_default_network_callback_, callback2.callback()));
   QuicChromiumClientSession* session2 = GetActiveSession(server2);
 
   std::unique_ptr<HttpStream> stream2 = CreateStream(&request2);
@@ -11837,7 +11860,7 @@
                             kDefaultServerPort),
         version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
         network_isolation_keys[i], SecureDnsPolicy::kAllow,
-        true /* use_dns_aliases */,
+        /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
         /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
         failed_on_default_network_callback_, callback_.callback());
     EXPECT_THAT(callback_.GetResult(rv), IsOk());
@@ -11899,7 +11922,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -11952,7 +11975,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -11988,7 +12011,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -12011,7 +12034,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -12043,7 +12066,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -12067,13 +12090,14 @@
   // Sending the request should not use the push stream, since the privacy mode
   // is different.
   QuicStreamRequest request2(factory_.get());
-  EXPECT_EQ(ERR_IO_PENDING,
-            request2.Request(
-                scheme_host_port_, version_, PRIVACY_MODE_ENABLED,
-                DEFAULT_PRIORITY, SocketTag(), NetworkIsolationKey(),
-                SecureDnsPolicy::kAllow, true /* use_dns_aliases */,
-                /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
-                failed_on_default_network_callback_, callback_.callback()));
+  EXPECT_EQ(
+      ERR_IO_PENDING,
+      request2.Request(
+          scheme_host_port_, version_, PRIVACY_MODE_ENABLED, DEFAULT_PRIORITY,
+          SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+          /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+          /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
+          failed_on_default_network_callback_, callback_.callback()));
 
   EXPECT_EQ(0, QuicStreamFactoryPeer::GetNumPushStreamsCreated(factory_.get()));
   // The pushed stream should still be pending.
@@ -12124,7 +12148,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -12148,14 +12172,14 @@
   // Sending the request should not use the push stream, since the
   // NetworkIsolationKey is different.
   QuicStreamRequest request2(factory_.get());
-  EXPECT_EQ(
-      ERR_IO_PENDING,
-      request2.Request(
-          scheme_host_port_, version_, PRIVACY_MODE_DISABLED, DEFAULT_PRIORITY,
-          SocketTag(), NetworkIsolationKey::CreateTransient(),
-          SecureDnsPolicy::kAllow, true /* use_dns_aliases */,
-          /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
-          failed_on_default_network_callback_, callback_.callback()));
+  EXPECT_EQ(ERR_IO_PENDING,
+            request2.Request(
+                scheme_host_port_, version_, PRIVACY_MODE_DISABLED,
+                DEFAULT_PRIORITY, SocketTag(),
+                NetworkIsolationKey::CreateTransient(), SecureDnsPolicy::kAllow,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
+                /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
+                failed_on_default_network_callback_, callback_.callback()));
 
   EXPECT_EQ(0, QuicStreamFactoryPeer::GetNumPushStreamsCreated(factory_.get()));
   EXPECT_EQ(&promised, index->GetPromised(kDefaultUrl));
@@ -12193,7 +12217,7 @@
             request1.Request(
                 destination1, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -12208,7 +12232,7 @@
             request2.Request(
                 destination2, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback2.callback()));
   std::unique_ptr<HttpStream> stream2 = CreateStream(&request2);
@@ -12373,7 +12397,7 @@
             request.Request(
                 destination, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -12415,7 +12439,7 @@
             request1.Request(
                 destination, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url1, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -12431,7 +12455,7 @@
             request2.Request(
                 destination, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url2, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback2.callback()));
   std::unique_ptr<HttpStream> stream2 = CreateStream(&request2);
@@ -12495,7 +12519,7 @@
             request1.Request(
                 destination, version_, PRIVACY_MODE_DISABLED, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url1, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -12509,7 +12533,7 @@
             request2.Request(
                 destination, version_, PRIVACY_MODE_ENABLED, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url2, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback2.callback()));
   EXPECT_EQ(OK, callback2.WaitForResult());
@@ -12580,7 +12604,7 @@
             request1.Request(
                 destination, version_, PRIVACY_MODE_DISABLED, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url1, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(OK, callback_.WaitForResult());
@@ -12594,7 +12618,7 @@
             request2.Request(
                 destination, version_, PRIVACY_MODE_DISABLED, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kDisable,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url2, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback2.callback()));
   EXPECT_EQ(OK, callback2.WaitForResult());
@@ -12664,7 +12688,7 @@
             request1.Request(
                 destination, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url1, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -12678,7 +12702,7 @@
             request2.Request(
                 destination, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url2, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback2.callback()));
   EXPECT_THAT(callback2.WaitForResult(), IsOk());
@@ -12798,7 +12822,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, MAXIMUM_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -12828,7 +12852,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, MAXIMUM_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -12840,7 +12864,7 @@
             request2.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url2_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_EQ(DEFAULT_PRIORITY, host_resolver_->last_request_priority());
@@ -12880,7 +12904,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), kNetworkIsolationKey, SecureDnsPolicy::kDisable,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -12933,7 +12957,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -12988,7 +13012,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -13040,7 +13064,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -13079,7 +13103,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -13114,7 +13138,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -13141,7 +13165,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -13196,7 +13220,7 @@
   EXPECT_THAT(request.Request(
                   scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                   SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                  true /* use_dns_aliases */,
+                  /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                   /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                   failed_on_default_network_callback_, callback_.callback()),
               IsOk());
@@ -13234,7 +13258,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   TestCompletionCallback host_resolution_callback;
@@ -13289,7 +13313,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -13349,7 +13373,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -13415,7 +13439,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -13493,7 +13517,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -13576,7 +13600,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -13657,7 +13681,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   // Finish dns resolution, but need to wait for stale connection.
@@ -13700,7 +13724,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 }
@@ -13726,7 +13750,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -13779,7 +13803,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -13830,7 +13854,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_FALSE(HasLiveSession(scheme_host_port_));
@@ -13882,7 +13906,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -13945,7 +13969,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -14004,7 +14028,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -14062,7 +14086,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -14099,7 +14123,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -14169,7 +14193,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -14248,7 +14272,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -14317,7 +14341,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   base::RunLoop().RunUntilIdle();
@@ -14386,7 +14410,7 @@
   int rv = request1.Request(
       scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY, tag1,
       NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-      true /* use_dns_aliases */,
+      /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
       /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
       failed_on_default_network_callback_, callback_.callback());
   EXPECT_THAT(callback_.GetResult(rv), IsOk());
@@ -14403,7 +14427,7 @@
   rv = request2.Request(
       scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY, tag1,
       NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-      true /* use_dns_aliases */,
+      /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
       /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
       failed_on_default_network_callback_, callback_.callback());
   EXPECT_THAT(callback_.GetResult(rv), IsOk());
@@ -14418,7 +14442,7 @@
   rv = request3.Request(
       scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY, tag2,
       NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-      true /* use_dns_aliases */,
+      /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
       /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
       failed_on_default_network_callback_, callback_.callback());
   EXPECT_THAT(callback_.GetResult(rv), IsOk());
@@ -14456,7 +14480,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -14493,7 +14517,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -14530,7 +14554,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
@@ -14569,7 +14593,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -14607,7 +14631,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -14644,7 +14668,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                false /* use_dns_aliases */,
+                /*use_dns_aliases=*/false, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -14678,7 +14702,7 @@
             request.Request(
                 scheme_host_port_, version_, privacy_mode_, DEFAULT_PRIORITY,
                 SocketTag(), NetworkIsolationKey(), SecureDnsPolicy::kAllow,
-                true /* use_dns_aliases */,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/false,
                 /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
                 failed_on_default_network_callback_, callback_.callback()));
 
@@ -14688,6 +14712,78 @@
   EXPECT_TRUE(socket_data.AllWriteDataConsumed());
 }
 
+TEST_P(QuicStreamFactoryTest, RequireDnsHttpsAlpnNoHttpsRecord) {
+  std::vector<HostResolverEndpointResult> endpoints(1);
+  endpoints[0].ip_endpoints = {IPEndPoint(IPAddress::IPv4Localhost(), 0)};
+  TestRequireDnsHttpsAlpn(std::move(endpoints), /*expect_success=*/false);
+}
+
+TEST_P(QuicStreamFactoryTest, RequireDnsHttpsAlpnMatch) {
+  std::vector<HostResolverEndpointResult> endpoints(2);
+  endpoints[0].ip_endpoints = {IPEndPoint(IPAddress::IPv4Localhost(), 0)};
+  endpoints[0].metadata.supported_protocol_alpns = {
+      quic::QuicVersionLabelToString(quic::CreateQuicVersionLabel(version_))};
+  // Add a final non-protocol endpoint at the end.
+  endpoints[1].ip_endpoints = {IPEndPoint(IPAddress::IPv4Localhost(), 0)};
+  TestRequireDnsHttpsAlpn(std::move(endpoints), /*expect_success=*/true);
+}
+
+TEST_P(QuicStreamFactoryTest, RequireDnsHttpsAlpnUnkownAlpn) {
+  std::vector<HostResolverEndpointResult> endpoints(2);
+  endpoints[0].ip_endpoints = {IPEndPoint(IPAddress::IPv4Localhost(), 0)};
+  endpoints[0].metadata.supported_protocol_alpns = {"unkown"};
+  // Add a final non-protocol endpoint at the end.
+  endpoints[1].ip_endpoints = {IPEndPoint(IPAddress::IPv4Localhost(), 0)};
+  TestRequireDnsHttpsAlpn(std::move(endpoints), /*expect_success=*/false);
+}
+
+TEST_P(QuicStreamFactoryTest, RequireDnsHttpsAlpnUnkownAndSupportedAlpn) {
+  std::vector<HostResolverEndpointResult> endpoints(2);
+  endpoints[0].ip_endpoints = {IPEndPoint(IPAddress::IPv4Localhost(), 0)};
+  endpoints[0].metadata.supported_protocol_alpns = {
+      "unkown",
+      quic::QuicVersionLabelToString(quic::CreateQuicVersionLabel(version_))};
+  // Add a final non-protocol endpoint at the end.
+  endpoints[1].ip_endpoints = {IPEndPoint(IPAddress::IPv4Localhost(), 0)};
+  TestRequireDnsHttpsAlpn(std::move(endpoints), /*expect_success=*/true);
+}
+
+void QuicStreamFactoryTestBase::TestRequireDnsHttpsAlpn(
+    std::vector<HostResolverEndpointResult> endpoints,
+    bool expect_success) {
+  quic_params_->supported_versions = {version_};
+  host_resolver_ = std::make_unique<MockCachingHostResolver>();
+  host_resolver_->rules()->AddRule(scheme_host_port_.host(),
+                                   std::move(endpoints));
+
+  Initialize();
+  ProofVerifyDetailsChromium verify_details = DefaultProofVerifyDetails();
+  crypto_client_stream_factory_.AddProofVerifyDetails(&verify_details);
+
+  MockQuicData socket_data(version_);
+  socket_data.AddRead(SYNCHRONOUS, ERR_IO_PENDING);
+  if (VersionUsesHttp3(version_.transport_version))
+    socket_data.AddWrite(SYNCHRONOUS, ConstructInitialSettingsPacket());
+  socket_data.AddSocketDataToFactory(socket_factory_.get());
+
+  QuicStreamRequest request(factory_.get());
+  EXPECT_EQ(ERR_IO_PENDING,
+            request.Request(
+                scheme_host_port_, quic::ParsedQuicVersion::Unsupported(),
+                privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
+                NetworkIsolationKey(), SecureDnsPolicy::kAllow,
+                /*use_dns_aliases=*/true, /*require_dns_https_alpn=*/true,
+                /*cert_verify_flags=*/0, url_, net_log_, &net_error_details_,
+                failed_on_default_network_callback_, callback_.callback()));
+
+  if (expect_success) {
+    EXPECT_THAT(callback_.WaitForResult(), IsOk());
+  } else {
+    EXPECT_THAT(callback_.WaitForResult(),
+                IsError(ERR_DNS_NO_MACHING_SUPPORTED_ALPN));
+  }
+}
+
 namespace {
 
 // Run QuicStreamFactoryDnsAliasPoolingTest instances with all value
@@ -14861,8 +14957,9 @@
       request1.Request(
           kOrigin1, version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
           NetworkIsolationKey(), SecureDnsPolicy::kAllow, use_dns_aliases_,
-          /*cert_verify_flags=*/0, kUrl1, net_log_, &net_error_details_,
-          failed_on_default_network_callback_, callback_.callback()));
+          /*require_dns_https_alpn=*/false, /*cert_verify_flags=*/0, kUrl1,
+          net_log_, &net_error_details_, failed_on_default_network_callback_,
+          callback_.callback()));
   EXPECT_THAT(callback_.WaitForResult(), IsOk());
 
   std::unique_ptr<HttpStream> stream1 = CreateStream(&request1);
@@ -14876,8 +14973,9 @@
       request2.Request(
           kOrigin2, version_, privacy_mode_, DEFAULT_PRIORITY, SocketTag(),
           NetworkIsolationKey(), SecureDnsPolicy::kAllow, use_dns_aliases_,
-          /*cert_verify_flags=*/0, kUrl2, net_log_, &net_error_details_,
-          failed_on_default_network_callback_, callback2.callback()));
+          /*require_dns_https_alpn=*/false, /*cert_verify_flags=*/0, kUrl2,
+          net_log_, &net_error_details_, failed_on_default_network_callback_,
+          callback2.callback()));
   EXPECT_THAT(callback2.WaitForResult(), IsOk());
 
   std::unique_ptr<HttpStream> stream2 = CreateStream(&request2);
diff --git a/net/socket/ssl_client_socket_unittest.cc b/net/socket/ssl_client_socket_unittest.cc
index 8063357..ffbb0cc1 100644
--- a/net/socket/ssl_client_socket_unittest.cc
+++ b/net/socket/ssl_client_socket_unittest.cc
@@ -3778,6 +3778,9 @@
 // Test that |ssl_info.pkp_bypassed| is set when a local trust anchor causes
 // pinning to be bypassed.
 TEST_P(SSLClientSocketVersionTest, PKPBypassedSet) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   ASSERT_TRUE(
       StartEmbeddedTestServer(EmbeddedTestServer::CERT_OK, GetServerConfig()));
   scoped_refptr<X509Certificate> server_cert =
@@ -3793,6 +3796,7 @@
   cert_verifier_->AddResultForCert(server_cert.get(), verify_result, OK);
 
   transport_security_state_->EnableStaticPinsForTesting();
+  transport_security_state_->SetPinningListAlwaysTimelyForTesting(true);
   ScopedTransportSecurityStateSource scoped_security_state_source;
 
   SSLConfig ssl_config;
@@ -3811,6 +3815,9 @@
 }
 
 TEST_P(SSLClientSocketVersionTest, PKPEnforced) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   ASSERT_TRUE(
       StartEmbeddedTestServer(EmbeddedTestServer::CERT_OK, GetServerConfig()));
   scoped_refptr<X509Certificate> server_cert =
@@ -3826,6 +3833,7 @@
   cert_verifier_->AddResultForCert(server_cert.get(), verify_result, OK);
 
   transport_security_state_->EnableStaticPinsForTesting();
+  transport_security_state_->SetPinningListAlwaysTimelyForTesting(true);
   ScopedTransportSecurityStateSource scoped_security_state_source;
 
   SSLConfig ssl_config;
@@ -4110,6 +4118,9 @@
 // When both PKP and CT are required for a host, and both fail, the more
 // serious error is that the pin validation failed.
 TEST_P(SSLClientSocketVersionTest, PKPMoreImportantThanCT) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   ASSERT_TRUE(
       StartEmbeddedTestServer(EmbeddedTestServer::CERT_OK, GetServerConfig()));
   scoped_refptr<X509Certificate> server_cert =
@@ -4125,6 +4136,7 @@
   cert_verifier_->AddResultForCert(server_cert.get(), verify_result, OK);
 
   transport_security_state_->EnableStaticPinsForTesting();
+  transport_security_state_->SetPinningListAlwaysTimelyForTesting(true);
   ScopedTransportSecurityStateSource scoped_security_state_source;
 
   const char kCTHost[] = "pkp-expect-ct.preloaded.test";
diff --git a/net/spdy/spdy_session_unittest.cc b/net/spdy/spdy_session_unittest.cc
index 074393d..0ca1291 100644
--- a/net/spdy/spdy_session_unittest.cc
+++ b/net/spdy/spdy_session_unittest.cc
@@ -7296,8 +7296,12 @@
 }
 
 TEST(CanPoolTest, CanNotPoolWithBadPins) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   TransportSecurityState tss;
   tss.EnableStaticPinsForTesting();
+  tss.SetPinningListAlwaysTimelyForTesting(true);
   ScopedTransportSecurityStateSource scoped_security_state_source;
 
   TestSSLConfigService ssl_config_service;
@@ -7403,6 +7407,7 @@
 TEST(CanPoolTest, CanPoolWithAcceptablePins) {
   TransportSecurityState tss;
   tss.EnableStaticPinsForTesting();
+  tss.SetPinningListAlwaysTimelyForTesting(true);
   ScopedTransportSecurityStateSource scoped_security_state_source;
 
   TestSSLConfigService ssl_config_service;
diff --git a/net/third_party/quiche/BUILD.gn b/net/third_party/quiche/BUILD.gn
index d50ad56..b78cd4db 100644
--- a/net/third_party/quiche/BUILD.gn
+++ b/net/third_party/quiche/BUILD.gn
@@ -123,6 +123,8 @@
     "src/quiche/http2/adapter/http2_util.cc",
     "src/quiche/http2/adapter/http2_util.h",
     "src/quiche/http2/adapter/http2_visitor_interface.h",
+    "src/quiche/http2/adapter/noop_header_validator.cc",
+    "src/quiche/http2/adapter/noop_header_validator.h",
     "src/quiche/http2/adapter/oghttp2_adapter.cc",
     "src/quiche/http2/adapter/oghttp2_adapter.h",
     "src/quiche/http2/adapter/oghttp2_session.cc",
@@ -966,10 +968,8 @@
     "overrides/quiche_platform_impl/quiche_test_output_impl.cc",
     "overrides/quiche_platform_impl/quiche_test_output_impl.h",
     "src/quiche/common/platform/api/quiche_expect_bug.h",
-    "src/quiche/common/platform/api/quiche_mock_log.h",
     "src/quiche/common/platform/api/quiche_test.h",
     "src/quiche/common/platform/api/quiche_test.h",
-    "src/quiche/common/platform/api/quiche_test_helpers.h",
     "src/quiche/common/platform/api/quiche_test_loopback.cc",
     "src/quiche/common/platform/api/quiche_test_loopback.h",
     "src/quiche/common/platform/api/quiche_test_output.h",
@@ -980,7 +980,6 @@
     "src/quiche/quic/core/quic_trace_visitor.cc",
     "src/quiche/quic/core/quic_trace_visitor.h",
     "src/quiche/quic/platform/api/quic_expect_bug.h",
-    "src/quiche/quic/platform/api/quic_mock_log.h",
     "src/quiche/quic/platform/api/quic_test.h",
     "src/quiche/quic/platform/api/quic_test_loopback.h",
     "src/quiche/quic/platform/api/quic_test_output.h",
@@ -1321,6 +1320,7 @@
     "src/quiche/common/structured_headers_test.cc",
     "src/quiche/http2/adapter/event_forwarder_test.cc",
     "src/quiche/http2/adapter/header_validator_test.cc",
+    "src/quiche/http2/adapter/noop_header_validator_test.cc",
     "src/quiche/http2/adapter/oghttp2_adapter_test.cc",
     "src/quiche/http2/adapter/oghttp2_session_test.cc",
     "src/quiche/http2/adapter/test_frame_sequence.cc",
@@ -1400,8 +1400,8 @@
     "src/quiche/http2/test_tools/http2_structures_test_util.h",
     "src/quiche/http2/test_tools/payload_decoder_base_test_util.cc",
     "src/quiche/http2/test_tools/payload_decoder_base_test_util.h",
-    "src/quiche/http2/test_tools/random_decoder_test.cc",
-    "src/quiche/http2/test_tools/random_decoder_test.h",
+    "src/quiche/http2/test_tools/random_decoder_test_base.cc",
+    "src/quiche/http2/test_tools/random_decoder_test_base.h",
     "src/quiche/http2/test_tools/random_util.cc",
     "src/quiche/http2/test_tools/random_util.h",
     "src/quiche/quic/core/congestion_control/bandwidth_sampler_test.cc",
diff --git a/net/tools/transport_security_state_generator/input_file_parsers.cc b/net/tools/transport_security_state_generator/input_file_parsers.cc
index 5eee1b6a..0345fd0 100644
--- a/net/tools/transport_security_state_generator/input_file_parsers.cc
+++ b/net/tools/transport_security_state_generator/input_file_parsers.cc
@@ -16,6 +16,7 @@
 #include "base/strings/string_piece.h"
 #include "base/strings/string_split.h"
 #include "base/strings/string_util.h"
+#include "base/time/time.h"
 #include "base/values.h"
 #include "net/tools/transport_security_state_generator/cert_util.h"
 #include "net/tools/transport_security_state_generator/pinset.h"
@@ -160,7 +161,8 @@
   PRE_NAME,
   POST_NAME,
   IN_CERTIFICATE,
-  IN_PUBLIC_KEY
+  IN_PUBLIC_KEY,
+  PRE_TIMESTAMP,
 };
 
 // Valid keys for entries in the input JSON. These fields will be included in
@@ -173,6 +175,7 @@
 static const char kPinsJSONKey[] = "pins";
 static const char kExpectCTJSONKey[] = "expect_ct";
 static const char kExpectCTReportURIJSONKey[] = "expect_ct_report_uri";
+static const char kTimestampName[] = "PinsListTimestamp";
 
 // Additional valid keys for entries in the input JSON that will not be included
 // in the output and contain metadata (e.g., for list maintenance).
@@ -180,7 +183,9 @@
 
 }  // namespace
 
-bool ParseCertificatesFile(base::StringPiece certs_input, Pinsets* pinsets) {
+bool ParseCertificatesFile(base::StringPiece certs_input,
+                           Pinsets* pinsets,
+                           base::Time* timestamp) {
   if (certs_input.find("\r\n") != base::StringPiece::npos) {
     LOG(ERROR) << "CRLF line-endings found in the pins file. All files must "
                   "use LF (unix style) line-endings.";
@@ -188,6 +193,7 @@
   }
 
   CertificateParserState current_state = CertificateParserState::PRE_NAME;
+  bool timestamp_parsed = false;
 
   const base::CompareCase& compare_mode = base::CompareCase::INSENSITIVE_ASCII;
   std::string name;
@@ -208,6 +214,10 @@
 
     switch (current_state) {
       case CertificateParserState::PRE_NAME:
+        if (line == kTimestampName) {
+          current_state = CertificateParserState::PRE_TIMESTAMP;
+          break;
+        }
         if (!IsValidName(line)) {
           LOG(ERROR) << "Invalid name in pins file: " << line;
           return false;
@@ -281,11 +291,30 @@
         pinsets->RegisterSPKIHash(name, hash);
         current_state = CertificateParserState::PRE_NAME;
         break;
+      case CertificateParserState::PRE_TIMESTAMP:
+        uint64_t timestamp_epoch;
+        if (!base::StringToUint64(line, &timestamp_epoch) ||
+            !base::IsValueInRangeForNumericType<time_t>(timestamp_epoch)) {
+          LOG(ERROR) << "Could not parse the timestamp value";
+          return false;
+        }
+        *timestamp = base::Time::FromTimeT(timestamp_epoch);
+        if (timestamp_parsed) {
+          LOG(ERROR) << "File contains multiple timestamps";
+          return false;
+        }
+        timestamp_parsed = true;
+        current_state = CertificateParserState::PRE_NAME;
+        break;
       default:
         DCHECK(false) << "Unknown parser state";
     }
   }
 
+  if (!timestamp_parsed) {
+    LOG(ERROR) << "Timestamp is missing";
+    return false;
+  }
   return true;
 }
 
diff --git a/net/tools/transport_security_state_generator/input_file_parsers.h b/net/tools/transport_security_state_generator/input_file_parsers.h
index ac28bfd..a2e67d61 100644
--- a/net/tools/transport_security_state_generator/input_file_parsers.h
+++ b/net/tools/transport_security_state_generator/input_file_parsers.h
@@ -8,6 +8,10 @@
 #include "base/strings/string_piece.h"
 #include "net/tools/transport_security_state_generator/transport_security_state_entry.h"
 
+namespace base {
+class Time;
+}
+
 namespace net {
 
 namespace transport_security_state {
@@ -19,7 +23,9 @@
 //
 // More info on the format can be found in
 // net/http/transport_security_state_static.pins
-bool ParseCertificatesFile(base::StringPiece certs_input, Pinsets* pinsets);
+bool ParseCertificatesFile(base::StringPiece certs_input,
+                           Pinsets* pinsets,
+                           base::Time* timestamp);
 
 // Parses the |json| string; copies the items under the "entries" key to
 // |entries| and the pinsets under the "pinsets" key to |pinsets|.
diff --git a/net/tools/transport_security_state_generator/input_file_parsers_unittest.cc b/net/tools/transport_security_state_generator/input_file_parsers_unittest.cc
index 4b5057cdc..28031f4 100644
--- a/net/tools/transport_security_state_generator/input_file_parsers_unittest.cc
+++ b/net/tools/transport_security_state_generator/input_file_parsers_unittest.cc
@@ -4,6 +4,7 @@
 
 #include <string>
 
+#include "base/time/time.h"
 #include "net/tools/transport_security_state_generator/input_file_parsers.h"
 #include "net/tools/transport_security_state_generator/pinsets.h"
 #include "net/tools/transport_security_state_generator/transport_security_state_entry.h"
@@ -247,6 +248,8 @@
 TEST(InputFileParsersTest, ParseCertificatesFile) {
   std::string valid =
       "# This line should ignored. The rest should result in 3 pins.\n"
+      "PinsListTimestamp\n"
+      "1649894400\n"
       "TestPublicKey1\n"
       "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n"
       "\n"
@@ -286,9 +289,18 @@
       "-----END CERTIFICATE-----";
 
   Pinsets pinsets;
-  EXPECT_TRUE(ParseCertificatesFile(valid, &pinsets));
+  base::Time timestamp;
+
+  base::Time expected_timestamp;
+  ASSERT_TRUE(
+      base::Time::FromUTCString("2022-04-14T00:00:00Z", &expected_timestamp));
+
+  EXPECT_TRUE(ParseCertificatesFile(valid, &pinsets, &timestamp));
+
   EXPECT_EQ(3U, pinsets.spki_size());
 
+  EXPECT_EQ(timestamp, expected_timestamp);
+
   const SPKIHashMap& hashes = pinsets.spki_hashes();
   EXPECT_NE(hashes.cend(), hashes.find("TestPublicKey1"));
   EXPECT_NE(hashes.cend(), hashes.find("TestPublicKey2"));
@@ -297,43 +309,60 @@
 
 TEST(InputFileParsersTest, ParseCertificatesFileInvalid) {
   Pinsets pinsets;
+  base::Time unused;
 
   std::string invalid =
+      "PinsListTimestamp\n"
+      "1649894400\n"
       "TestName\n"
       "unexpected";
-  EXPECT_FALSE(ParseCertificatesFile(invalid, &pinsets));
+  EXPECT_FALSE(ParseCertificatesFile(invalid, &pinsets, &unused));
 }
 
 // Test that parsing invalid certificate names fails.
 TEST(InputFileParsersTest, ParseCertificatesFileInvalidName) {
   Pinsets pinsets;
+  base::Time unused;
 
   std::string invalid_name_small_character =
+      "PinsListTimestamp\n"
+      "1649894400\n"
       "startsWithSmallLetter\n"
       "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n";
-  EXPECT_FALSE(ParseCertificatesFile(invalid_name_small_character, &pinsets));
+  EXPECT_FALSE(
+      ParseCertificatesFile(invalid_name_small_character, &pinsets, &unused));
 
   std::string invalid_name_invalid_characters =
+      "PinsListTimestamp\n"
+      "1649894400\n"
       "Invalid-Characters-In-Name\n"
       "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n";
-  EXPECT_FALSE(
-      ParseCertificatesFile(invalid_name_invalid_characters, &pinsets));
+  EXPECT_FALSE(ParseCertificatesFile(invalid_name_invalid_characters, &pinsets,
+                                     &unused));
 
   std::string invalid_name_number =
+      "PinsListTimestamp\n"
+      "1649894400\n"
       "1InvalidName\n"
       "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n";
-  EXPECT_FALSE(ParseCertificatesFile(invalid_name_number, &pinsets));
+  EXPECT_FALSE(ParseCertificatesFile(invalid_name_number, &pinsets, &unused));
 
   std::string invalid_name_space =
+      "PinsListTimestamp\n"
+      "1649894400\n"
       "Invalid Name\n"
       "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n";
-  EXPECT_FALSE(ParseCertificatesFile(invalid_name_space, &pinsets));
+  EXPECT_FALSE(ParseCertificatesFile(invalid_name_space, &pinsets, &unused));
 }
 
 // Test that parsing of a certificate with an incomplete or incorrect name
 // fails.
 TEST(InputFileParsersTest, ParseCertificatesFileInvalidCertificateName) {
   Pinsets pinsets;
+  base::Time unused;
+  std::string timestamp_prefix =
+      "PinsListTimestamp\n"
+      "1649894400\n";
   std::string certificate =
       "-----BEGIN CERTIFICATE-----\n"
       "MIIDIzCCAgugAwIBAgIJALs84KlxWh4GMA0GCSqGSIb3DQEBCwUAMCgxGTAXBgNV\n"
@@ -355,17 +384,67 @@
       "E2j3m+jTVIv3CZ+ivGxggZQ8ZYN8FJ/iTW3pXGojogHh0NRJJ8dM\n"
       "-----END CERTIFICATE-----";
 
-  std::string missing_prefix = "Class3_G1_Test\n" + certificate;
-  EXPECT_FALSE(ParseCertificatesFile(missing_prefix, &pinsets));
+  std::string missing_prefix =
+      timestamp_prefix + "Class3_G1_Test\n" + certificate;
+  EXPECT_FALSE(ParseCertificatesFile(missing_prefix, &pinsets, &unused));
 
-  std::string missing_class = "Chromium_G1_Test\n" + certificate;
-  EXPECT_FALSE(ParseCertificatesFile(missing_class, &pinsets));
+  std::string missing_class =
+      timestamp_prefix + "Chromium_G1_Test\n" + certificate;
+  EXPECT_FALSE(ParseCertificatesFile(missing_class, &pinsets, &unused));
 
-  std::string missing_number = "Chromium_Class3_Test\n" + certificate;
-  EXPECT_FALSE(ParseCertificatesFile(missing_number, &pinsets));
+  std::string missing_number =
+      timestamp_prefix + "Chromium_Class3_Test\n" + certificate;
+  EXPECT_FALSE(ParseCertificatesFile(missing_number, &pinsets, &unused));
 
-  std::string valid = "Chromium_Class3_G1_Test\n" + certificate;
-  EXPECT_TRUE(ParseCertificatesFile(valid, &pinsets));
+  std::string valid =
+      timestamp_prefix + "Chromium_Class3_G1_Test\n" + certificate;
+  EXPECT_TRUE(ParseCertificatesFile(valid, &pinsets, &unused));
+}
+
+// Tests that parsing a certificate with a missing or incorrect timestamp fails.
+TEST(InputFileParsersTest, ParseCertificatesFileInvalidTimestamp) {
+  Pinsets pinsets;
+  base::Time unused;
+  std::string timestamp_prefix =
+      "PinsListTimestamp\n"
+      "1649894400\n";
+  std::string bad_timestamp_prefix =
+      "PinsListTimestamp\n"
+      "NotReallyTimestamp\n";
+  std::string certificate =
+      "Chromium_Class3_G1_Test\n"
+      "-----BEGIN CERTIFICATE-----\n"
+      "MIIDIzCCAgugAwIBAgIJALs84KlxWh4GMA0GCSqGSIb3DQEBCwUAMCgxGTAXBgNV\n"
+      "BAoMEENocm9taXVtIENsYXNzIDMxCzAJBgNVBAsMAkcxMB4XDTE3MDIwMTE5NTUw\n"
+      "NVoXDTE4MDIwMTE5NTUwNVowKDEZMBcGA1UECgwQQ2hyb21pdW0gQ2xhc3MgMzEL\n"
+      "MAkGA1UECwwCRzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkolrR\n"
+      "7gCPm22Cc9psS2Jh1mksVneee5ntEezZ2gEU20y9Z9URBReo8SFvaZcgKkAkca1v\n"
+      "552YIG+FBO/u8njxzlHXvuVJ5x2geciqqR4TRhA4jO1ndrNW6nlJfOoYueWbdym3\n"
+      "8zwugoULoCtyLyzdiMI5g8iVBQHDh8+K3TZIHar3HS49TjX5u5nv4igO4RfDcFUa\n"
+      "h8g+6x5nWoFF8oa3FG0YTN+q6iI1i2JHmj/q03fVPv3WLPGJ3JADau9gO1Lw1/qf\n"
+      "R/N3l4MVtjDFFGYzclfqW2UmL6zRirEV0GF2gwSBAGVX3WWhpOcM8rFIWYkZCsI5\n"
+      "iUdtwFNBfcKS9sNpAgMBAAGjUDBOMB0GA1UdDgQWBBTm4VJfibducqwb9h4XELn3\n"
+      "p6zLVzAfBgNVHSMEGDAWgBTm4VJfibducqwb9h4XELn3p6zLVzAMBgNVHRMEBTAD\n"
+      "AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQApTm40RfsZG20IIgWJ62pZ2end/lvaneTh\n"
+      "MZSgFnoTRjKkd/5dh22YyKPw9PnpIuiyi85L36COreqZUvbxqRQnpL1oSCRlLBJQ\n"
+      "2LcGlF0j0Opa+SY2VWup4XjnYF8CvwMl4obNpSuywTFmkXCRxzN23tn8whNHvWHM\n"
+      "BQ7abw8X1KY02uPbHucrpou6KXkKkhyhfML8OD8IRkSM56K6YyedqV97cmEdW0Ie\n"
+      "LlpFJQVX13bmojtSNI1zaiCiEenn5xLa/dAlyFT18Mq6y8plioBinVWFYd0qcRoA\n"
+      "E2j3m+jTVIv3CZ+ivGxggZQ8ZYN8FJ/iTW3pXGojogHh0NRJJ8dM\n"
+      "-----END CERTIFICATE-----";
+
+  std::string missing_timestamp = certificate;
+  EXPECT_FALSE(ParseCertificatesFile(certificate, &pinsets, &unused));
+
+  std::string incorrect_timestamp = bad_timestamp_prefix + certificate;
+  EXPECT_FALSE(ParseCertificatesFile(incorrect_timestamp, &pinsets, &unused));
+
+  std::string multiple_timestamp =
+      timestamp_prefix + timestamp_prefix + certificate;
+  EXPECT_FALSE(ParseCertificatesFile(multiple_timestamp, &pinsets, &unused));
+
+  std::string valid = timestamp_prefix + certificate;
+  EXPECT_TRUE(ParseCertificatesFile(valid, &pinsets, &unused));
 }
 
 }  // namespace
diff --git a/net/tools/transport_security_state_generator/preloaded_state_generator.cc b/net/tools/transport_security_state_generator/preloaded_state_generator.cc
index 80c9e4f0..95269a9 100644
--- a/net/tools/transport_security_state_generator/preloaded_state_generator.cc
+++ b/net/tools/transport_security_state_generator/preloaded_state_generator.cc
@@ -131,7 +131,8 @@
 std::string PreloadedStateGenerator::Generate(
     const std::string& preload_template,
     const TransportSecurityStateEntries& entries,
-    const Pinsets& pinsets) {
+    const Pinsets& pinsets,
+    const base::Time& timestamp) {
   std::string output = preload_template;
 
   ProcessSPKIHashes(pinsets, &output);
@@ -183,6 +184,9 @@
   ReplaceTag("HSTS_TRIE_BITS", base::NumberToString(new_length), &output);
   ReplaceTag("HSTS_TRIE_ROOT", base::NumberToString(root_position), &output);
 
+  ReplaceTag("PINS_LIST_TIMESTAMP", base::NumberToString(timestamp.ToTimeT()),
+             &output);
+
   return output;
 }
 
diff --git a/net/tools/transport_security_state_generator/preloaded_state_generator.h b/net/tools/transport_security_state_generator/preloaded_state_generator.h
index 815777a..2d1c83d 100644
--- a/net/tools/transport_security_state_generator/preloaded_state_generator.h
+++ b/net/tools/transport_security_state_generator/preloaded_state_generator.h
@@ -10,6 +10,7 @@
 #include <memory>
 #include <string>
 
+#include "base/time/time.h"
 #include "net/tools/huffman_trie/trie/trie_writer.h"
 #include "net/tools/transport_security_state_generator/pinset.h"
 #include "net/tools/transport_security_state_generator/pinsets.h"
@@ -31,7 +32,8 @@
   // Returns the generated C++ code on success and the empty string on failure.
   std::string Generate(const std::string& preload_template,
                        const TransportSecurityStateEntries& entries,
-                       const Pinsets& pinsets);
+                       const Pinsets& pinsets,
+                       const base::Time& timestamp);
 
  private:
   void ProcessSPKIHashes(const Pinsets& pinset, std::string* tpl);
diff --git a/net/tools/transport_security_state_generator/transport_security_state_generator.cc b/net/tools/transport_security_state_generator/transport_security_state_generator.cc
index 24dd111..d1daa41 100644
--- a/net/tools/transport_security_state_generator/transport_security_state_generator.cc
+++ b/net/tools/transport_security_state_generator/transport_security_state_generator.cc
@@ -13,6 +13,7 @@
 #include "base/logging.h"
 #include "base/path_service.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
 #include "build/build_config.h"
 #include "crypto/openssl_util.h"
 #include "net/tools/transport_security_state_generator/input_file_parsers.h"
@@ -251,8 +252,9 @@
 
   TransportSecurityStateEntries entries;
   Pinsets pinsets;
+  base::Time timestamp;
 
-  if (!ParseCertificatesFile(certs_input, &pinsets) ||
+  if (!ParseCertificatesFile(certs_input, &pinsets, &timestamp) ||
       !ParseJSON(json_input, &entries, &pinsets)) {
     LOG(ERROR) << "Error while parsing the input files.";
     return 1;
@@ -280,7 +282,7 @@
 
   std::string output;
   PreloadedStateGenerator generator;
-  output = generator.Generate(preload_template, entries, pinsets);
+  output = generator.Generate(preload_template, entries, pinsets, timestamp);
   if (output.empty()) {
     LOG(ERROR) << "Trie generation failed.";
     return 1;
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index f44e3d8c..08e12bc5 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -6177,6 +6177,9 @@
 
 // Tests that reports get sent on PKP violations when a report-uri is set.
 TEST_F(URLRequestTestHTTP, ProcessPKPAndSendReport) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   GURL report_uri(kPKPReportUri);
   EmbeddedTestServer https_test_server(net::EmbeddedTestServer::TYPE_HTTPS);
   https_test_server.SetSSLConfig(
@@ -6209,6 +6212,8 @@
   auto context = context_builder->Build();
   MockCertificateReportSender mock_report_sender;
   context->transport_security_state()->EnableStaticPinsForTesting();
+  context->transport_security_state()->SetPinningListAlwaysTimelyForTesting(
+      true);
   context->transport_security_state()->SetReportSender(&mock_report_sender);
 
   IsolationInfo isolation_info = IsolationInfo::CreateTransient();
@@ -6243,6 +6248,9 @@
 // Tests that reports do not get sent on requests to static pkp hosts that
 // don't have pin violations.
 TEST_F(URLRequestTestHTTP, ProcessPKPWithNoViolation) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   EmbeddedTestServer https_test_server(net::EmbeddedTestServer::TYPE_HTTPS);
   https_test_server.SetSSLConfig(
       net::EmbeddedTestServer::CERT_COMMON_NAME_IS_DOMAIN);
@@ -6272,6 +6280,8 @@
   auto context = context_builder->Build();
   MockCertificateReportSender mock_report_sender;
   context->transport_security_state()->EnableStaticPinsForTesting();
+  context->transport_security_state()->SetPinningListAlwaysTimelyForTesting(
+      true);
   context->transport_security_state()->SetReportSender(&mock_report_sender);
 
   // Now send a request that does not trigger the violation.
@@ -6298,6 +6308,9 @@
 }
 
 TEST_F(URLRequestTestHTTP, PKPBypassRecorded) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   EmbeddedTestServer https_test_server(net::EmbeddedTestServer::TYPE_HTTPS);
   https_test_server.SetSSLConfig(
       net::EmbeddedTestServer::CERT_COMMON_NAME_IS_DOMAIN);
@@ -6328,6 +6341,8 @@
   auto context = context_builder->Build();
   MockCertificateReportSender mock_report_sender;
   context->transport_security_state()->EnableStaticPinsForTesting();
+  context->transport_security_state()->SetPinningListAlwaysTimelyForTesting(
+      true);
   context->transport_security_state()->SetReportSender(&mock_report_sender);
 
   TestDelegate d;
@@ -9763,6 +9778,9 @@
 // This tests that cached HTTPS page loads do not cause any updates to the
 // TransportSecurityState.
 TEST_F(HTTPSRequestTest, HTTPSErrorsNoClobberTSSTest) {
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
   SetTransportSecurityStateSourceForTesting(&test_default::kHSTSSource);
 
   // The actual problem -- CERT_MISMATCHED_NAME in this case -- doesn't
@@ -9788,6 +9806,7 @@
       *context->transport_security_state();
 
   transport_security_state.EnableStaticPinsForTesting();
+  transport_security_state.SetPinningListAlwaysTimelyForTesting(true);
 
   TransportSecurityState::STSState static_sts_state;
   TransportSecurityState::PKPState static_pkp_state;
@@ -11906,6 +11925,7 @@
   ASSERT_TRUE(context->transport_security_state());
   TransportSecurityState& security_state = *context->transport_security_state();
   security_state.EnableStaticPinsForTesting();
+  security_state.SetPinningListAlwaysTimelyForTesting(true);
   SetTransportSecurityStateSourceForTesting(&test_default::kHSTSSource);
 
   // Connect to the test server and see the certificate error flagged, but
diff --git a/printing/print_settings_conversion.cc b/printing/print_settings_conversion.cc
index b8679e02..eecc360 100644
--- a/printing/print_settings_conversion.cc
+++ b/printing/print_settings_conversion.cc
@@ -132,6 +132,47 @@
   settings->set_should_print_backgrounds(backgrounds.value());
   settings->set_selection_only(selection_only.value());
 
+  absl::optional<bool> collate = job_settings.FindBool(kSettingCollate);
+  absl::optional<int> copies = job_settings.FindInt(kSettingCopies);
+  absl::optional<int> color = job_settings.FindInt(kSettingColor);
+  absl::optional<int> duplex_mode = job_settings.FindInt(kSettingDuplexMode);
+  absl::optional<bool> landscape = job_settings.FindBool(kSettingLandscape);
+  absl::optional<int> scale_factor = job_settings.FindInt(kSettingScaleFactor);
+  absl::optional<bool> rasterize_pdf =
+      job_settings.FindBool(kSettingRasterizePdf);
+  absl::optional<int> pages_per_sheet =
+      job_settings.FindInt(kSettingPagesPerSheet);
+  if (!collate.has_value() || !copies.has_value() || !color.has_value() ||
+      !duplex_mode.has_value() || !landscape.has_value() ||
+      !scale_factor.has_value() || !rasterize_pdf.has_value() ||
+      !pages_per_sheet.has_value()) {
+    return nullptr;
+  }
+  settings->set_collate(collate.value());
+  settings->set_copies(copies.value());
+  settings->SetOrientation(landscape.value());
+  settings->set_device_name(
+      base::UTF8ToUTF16(*job_settings.FindString(kSettingDeviceName)));
+  settings->set_duplex_mode(
+      static_cast<mojom::DuplexMode>(duplex_mode.value()));
+  settings->set_color(static_cast<mojom::ColorModel>(color.value()));
+  settings->set_scale_factor(static_cast<double>(scale_factor.value()) / 100.0);
+  settings->set_rasterize_pdf(rasterize_pdf.value());
+  settings->set_pages_per_sheet(pages_per_sheet.value());
+
+  absl::optional<int> dpi_horizontal =
+      job_settings.FindInt(kSettingDpiHorizontal);
+  absl::optional<int> dpi_vertical = job_settings.FindInt(kSettingDpiVertical);
+  if (!dpi_horizontal.has_value() || !dpi_vertical.has_value())
+    return nullptr;
+
+  settings->set_dpi_xy(dpi_horizontal.value(), dpi_vertical.value());
+
+  absl::optional<int> rasterize_pdf_dpi =
+      job_settings.FindInt(kSettingRasterizePdfDpi);
+  if (rasterize_pdf_dpi.has_value())
+    settings->set_rasterize_pdf_dpi(rasterize_pdf_dpi.value());
+
   PrintSettings::RequestedMedia requested_media;
   const base::Value::Dict* media_size_value =
       job_settings.FindDict(kSettingMediaSize);
@@ -168,47 +209,6 @@
 
   settings->set_ranges(GetPageRangesFromJobSettings(job_settings));
 
-  absl::optional<bool> collate = job_settings.FindBool(kSettingCollate);
-  absl::optional<int> copies = job_settings.FindInt(kSettingCopies);
-  absl::optional<int> color = job_settings.FindInt(kSettingColor);
-  absl::optional<int> duplex_mode = job_settings.FindInt(kSettingDuplexMode);
-  absl::optional<bool> landscape = job_settings.FindBool(kSettingLandscape);
-  absl::optional<int> scale_factor = job_settings.FindInt(kSettingScaleFactor);
-  absl::optional<bool> rasterize_pdf =
-      job_settings.FindBool(kSettingRasterizePdf);
-  absl::optional<int> pages_per_sheet =
-      job_settings.FindInt(kSettingPagesPerSheet);
-
-  if (!collate.has_value() || !copies.has_value() || !color.has_value() ||
-      !duplex_mode.has_value() || !landscape.has_value() ||
-      !scale_factor.has_value() || !rasterize_pdf.has_value() ||
-      !pages_per_sheet.has_value()) {
-    return nullptr;
-  }
-
-  absl::optional<int> dpi_horizontal =
-      job_settings.FindInt(kSettingDpiHorizontal);
-  absl::optional<int> dpi_vertical = job_settings.FindInt(kSettingDpiVertical);
-  if (!dpi_horizontal.has_value() || !dpi_vertical.has_value())
-    return nullptr;
-  settings->set_dpi_xy(dpi_horizontal.value(), dpi_vertical.value());
-
-  absl::optional<int> rasterize_pdf_dpi =
-      job_settings.FindInt(kSettingRasterizePdfDpi);
-  if (rasterize_pdf_dpi.has_value())
-    settings->set_rasterize_pdf_dpi(rasterize_pdf_dpi.value());
-
-  settings->set_collate(collate.value());
-  settings->set_copies(copies.value());
-  settings->SetOrientation(landscape.value());
-  settings->set_device_name(
-      base::UTF8ToUTF16(*job_settings.FindString(kSettingDeviceName)));
-  settings->set_duplex_mode(
-      static_cast<mojom::DuplexMode>(duplex_mode.value()));
-  settings->set_color(static_cast<mojom::ColorModel>(color.value()));
-  settings->set_scale_factor(static_cast<double>(scale_factor.value()) / 100.0);
-  settings->set_rasterize_pdf(rasterize_pdf.value());
-  settings->set_pages_per_sheet(pages_per_sheet.value());
   absl::optional<bool> is_modifiable =
       job_settings.FindBool(kSettingPreviewModifiable);
   if (is_modifiable.has_value()) {
diff --git a/remoting/protocol/session_config.h b/remoting/protocol/session_config.h
index 408b54d..a0363f6d 100644
--- a/remoting/protocol/session_config.h
+++ b/remoting/protocol/session_config.h
@@ -190,9 +190,6 @@
   std::list<ChannelConfig> event_configs_;
   std::list<ChannelConfig> video_configs_;
   std::list<ChannelConfig> audio_configs_;
-
-  bool vp9_experiment_enabled_ = false;
-  bool h264_experiment_enabled_ = false;
 };
 
 }  // namespace protocol
diff --git a/sandbox/policy/win/lpac_capability.cc b/sandbox/policy/win/lpac_capability.cc
index 1b6dc65..df4234b 100644
--- a/sandbox/policy/win/lpac_capability.cc
+++ b/sandbox/policy/win/lpac_capability.cc
@@ -7,9 +7,11 @@
 namespace sandbox {
 namespace policy {
 
+// WARNING: Renaming a capability could cause backward compatible issues!
+
 // Capability used by the Media Foundation CDM to grant read and write access to
 // a folder under the Chrome user's profile.
-const wchar_t kMediaFoundationCdmData[] = L"mediaFoundationCdmData";
+const wchar_t kMediaFoundationCdmData[] = L"lpacMediaFoundationCdmData";
 
 // Capability for Media Foundation CDM files that needs read and execute access.
 const wchar_t kMediaFoundationCdmFiles[] = L"mediaFoundationCdmFiles";
diff --git a/services/audio/audio_manager_power_user.h b/services/audio/audio_manager_power_user.h
index 457a83f..19c2a35d 100644
--- a/services/audio/audio_manager_power_user.h
+++ b/services/audio/audio_manager_power_user.h
@@ -27,6 +27,10 @@
     return audio_manager_->GetDefaultOutputDeviceID();
   }
 
+  std::string GetCommunicationsOutputDeviceID() {
+    return audio_manager_->GetCommunicationsOutputDeviceID();
+  }
+
   media::AudioParameters GetOutputStreamParameters(
       const std::string& device_id) {
     return media::AudioDeviceDescription::IsDefaultDevice(device_id)
diff --git a/services/audio/output_device_mixer_manager.cc b/services/audio/output_device_mixer_manager.cc
index dbb418c..993c3c19 100644
--- a/services/audio/output_device_mixer_manager.cc
+++ b/services/audio/output_device_mixer_manager.cc
@@ -49,6 +49,8 @@
     : audio_manager_(audio_manager),
       current_default_device_id_(
           AudioManagerPowerUser(audio_manager_).GetDefaultOutputDeviceID()),
+      current_communication_device_id_(AudioManagerPowerUser(audio_manager_)
+                                           .GetCommunicationsOutputDeviceID()),
       create_mixer_callback_(std::move(create_mixer_callback)),
       device_change_weak_ptr_factory_(this) {
   // This should be a static_assert, but there is no compile time way to run
@@ -102,6 +104,9 @@
   current_default_device_id_ =
       AudioManagerPowerUser(audio_manager_).GetDefaultOutputDeviceID();
 
+  current_communication_device_id_ =
+      AudioManagerPowerUser(audio_manager_).GetCommunicationsOutputDeviceID();
+
   // Invalidate WeakPtrs, cancelling any pending device change callbacks
   // generated by the same device change event.
   device_change_weak_ptr_factory_.InvalidateWeakPtrs();
@@ -116,14 +121,14 @@
 
 void OutputDeviceMixerManager::StartNewListener(
     ReferenceOutput::Listener* listener,
-    const std::string& device_id) {
+    const std::string& listener_device_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
-  DCHECK(IsNormalizedIfDefault(device_id));
+  DCHECK(IsNormalizedIfDefault(listener_device_id));
 
   DCHECK(!listener_registration_.contains(listener));
-  listener_registration_[listener] = device_id;
+  listener_registration_[listener] = listener_device_id;
 
-  OutputDeviceMixer* mixer = FindMixer(ToMixerDeviceId(device_id));
+  OutputDeviceMixer* mixer = FindMixer(ToMixerDeviceId(listener_device_id));
 
   if (!mixer)
     return;
@@ -136,37 +141,40 @@
     const std::string& output_device_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
 
-  std::string device_id = NormalizeIfDefault(output_device_id);
+  std::string listener_device_id = NormalizeIfDefault(output_device_id);
 
   if (!listener_registration_.contains(listener)) {
-    StartNewListener(listener, device_id);
+    StartNewListener(listener, listener_device_id);
     return;
   }
 
-  std::string registered_device_id = listener_registration_.at(listener);
+  std::string registered_listener_device_id =
+      listener_registration_.at(listener);
 
-  if (ToMixerDeviceId(registered_device_id) != ToMixerDeviceId(device_id)) {
+  if (ToMixerDeviceId(registered_listener_device_id) !=
+      ToMixerDeviceId(listener_device_id)) {
     // |listener| is listening to a completely different mixer.
     StopListening(listener);
-    StartNewListener(listener, device_id);
+    StartNewListener(listener, listener_device_id);
     return;
   }
 
   // |listener| is listening to the right mixer, but we might need to update
   // its registration (e.g. when switching between
-  // |current_default_device_id_| and kNormalizedDefaultId).
-  if (registered_device_id != device_id)
-    listener_registration_[listener] = device_id;
+  // |current_default_device_id_| and kNormalizedDefaultId, or
+  // |current_communications_device_id_| and kCommunicationsDeviceId).
+  if (registered_listener_device_id != listener_device_id)
+    listener_registration_[listener] = listener_device_id;
 }
 
 void OutputDeviceMixerManager::StopListening(
     ReferenceOutput::Listener* listener) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
 
-  const std::string device_id = listener_registration_.at(listener);
+  const std::string listener_device_id = listener_registration_.at(listener);
   listener_registration_.erase(listener);
 
-  OutputDeviceMixer* mixer = FindMixer(ToMixerDeviceId(device_id));
+  OutputDeviceMixer* mixer = FindMixer(ToMixerDeviceId(listener_device_id));
 
   // The mixer was never created, because there was no playback to that device
   // (possibly after a device device change). Listening never started, so there
@@ -182,14 +190,32 @@
   if (media::AudioDeviceDescription::IsDefaultDevice(device_id))
     return kNormalizedDefaultDeviceId;
 
-  return device_id == current_default_device_id_ ? kNormalizedDefaultDeviceId
-                                                 : device_id;
+  DCHECK(!device_id.empty());
+
+  if (device_id == current_default_device_id_)
+    return kNormalizedDefaultDeviceId;
+
+  // It's possible for |current_communication_device_id_| and
+  // |current_default_device_id_| to match. In that case, replace the
+  // communications mixer device ID with the default mixer device ID.
+  // Similarly, replace "communications" with kNormalizedDefaultDeviceId when
+  // |current_communication_device_id_| is unsupported/unconfigured.
+  if (device_id == media::AudioDeviceDescription::kCommunicationsDeviceId &&
+      (current_communication_device_id_.empty() ||
+       current_communication_device_id_ == current_default_device_id_)) {
+    return kNormalizedDefaultDeviceId;
+  }
+
+  if (device_id == current_communication_device_id_)
+    return media::AudioDeviceDescription::kCommunicationsDeviceId;
+
+  return device_id;
 }
 
 OutputDeviceMixer* OutputDeviceMixerManager::FindMixer(
     const std::string& device_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
-  DCHECK(IsValidMixerId(device_id));
+  DCHECK_EQ(ToMixerDeviceId(device_id), device_id);
 
   for (const auto& mixer : output_device_mixers_) {
     if (mixer->device_id() == device_id)
@@ -202,7 +228,7 @@
 OutputDeviceMixer* OutputDeviceMixerManager::AddMixer(
     const std::string& device_id) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
-  DCHECK(IsValidMixerId(device_id));
+  DCHECK_EQ(ToMixerDeviceId(device_id), device_id);
 
   DCHECK(!FindMixer(device_id));
 
@@ -245,31 +271,15 @@
   auto* mixer = output_device_mixer.get();
   output_device_mixers_.push_back(std::move(output_device_mixer));
 
-  AttachListenersById(device_id, mixer);
-
-  // We only create a single "default" mixer for both kNormalizedDefaultDeviceId
-  // and |current_default_device_id_|. If we just created this "default" mixer,
-  // also attach any listeners matching a physical |current_default_device_id_|.
-  if (device_id == kNormalizedDefaultDeviceId &&
-      current_default_device_id_ != kNormalizedDefaultDeviceId) {
-    AttachListenersById(current_default_device_id_, mixer);
+  // Attach any interested listeners.
+  for (auto&& listener_device_kvp : listener_registration_) {
+    if (ToMixerDeviceId(listener_device_kvp.second) == mixer->device_id())
+      mixer->StartListening(listener_device_kvp.first);
   }
 
   return mixer;
 }
 
-void OutputDeviceMixerManager::AttachListenersById(const std::string& device_id,
-                                                   OutputDeviceMixer* mixer) {
-  DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
-  DCHECK(IsNormalizedIfDefault(device_id));
-  DCHECK(mixer);
-
-  for (auto&& listener_device_kvp : listener_registration_) {
-    if (listener_device_kvp.second == device_id)
-      mixer->StartListening(listener_device_kvp.first);
-  }
-}
-
 base::OnceClosure OutputDeviceMixerManager::GetOnDeviceChangeCallback() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(owning_sequence_);
   return base::BindOnce(&OutputDeviceMixerManager::OnDeviceChange,
@@ -307,11 +317,6 @@
                                         std::move(on_device_change_callback));
 }
 
-bool OutputDeviceMixerManager::IsValidMixerId(const std::string& device_id) {
-  return device_id == kNormalizedDefaultDeviceId ||
-         device_id != current_default_device_id_;
-}
-
 bool OutputDeviceMixerManager::IsNormalizedIfDefault(
     const std::string& device_id) {
   return device_id == kNormalizedDefaultDeviceId ||
diff --git a/services/audio/output_device_mixer_manager.h b/services/audio/output_device_mixer_manager.h
index 38f24eef..e517a98 100644
--- a/services/audio/output_device_mixer_manager.h
+++ b/services/audio/output_device_mixer_manager.h
@@ -63,10 +63,11 @@
   // Forwards device change notifications to OutputDeviceMixers.
   void OnDeviceChange();
 
-  // Helper function which merges "default IDs" (as defined by
-  // AudioDeviceDescription::IsDefaultId()) and physical IDs matching
-  // GetCurrentDefaultPhysicalDeviceIdOrEmpty() into the same normalized default
-  // ID. This guarantees we create a single "default ID" OutputDeviceMixer.
+  // Helper function which maps physical and reserved IDs to normalized mixer
+  // device IDs. Physical IDs matching the current "default" or "communications"
+  // physical devices will be converted to reserved IDs
+  // (kNormalizedDefaultDeviceId or kCommunicationsDeviceId), ensuring we only
+  // create one mixer per device.
   std::string ToMixerDeviceId(const std::string& device_id);
 
   // Returns a callback that call OnDeviceChange(), and that can be cancelled
@@ -83,9 +84,6 @@
       const std::string& device_id,
       const media::AudioParameters& params);
 
-  void AttachListenersById(const std::string& device_id,
-                           OutputDeviceMixer* mixer);
-
   // Returns a mixer if it exists, or nullptr otherwise.
   OutputDeviceMixer* FindMixer(const std::string& physical_device_id);
 
@@ -95,7 +93,6 @@
   void StartNewListener(ReferenceOutput::Listener* listener,
                         const std::string& device_id);
 
-  bool IsValidMixerId(const std::string& device_id);
   bool IsNormalizedIfDefault(const std::string& device_id);
 
   SEQUENCE_CHECKER(owning_sequence_);
@@ -105,6 +102,10 @@
   // if not supported by the platform.
   std::string current_default_device_id_;
 
+  // Physical device ID of the current communication device, or an empty string
+  // if not supported by the platform or not configured.
+  std::string current_communication_device_id_;
+
   OutputDeviceMixer::CreateCallback create_mixer_callback_;
   OutputDeviceMixers output_device_mixers_;
   ListenerToDeviceMap listener_registration_;
diff --git a/services/audio/output_device_mixer_manager_unittest.cc b/services/audio/output_device_mixer_manager_unittest.cc
index 93561048..59c79417 100644
--- a/services/audio/output_device_mixer_manager_unittest.cc
+++ b/services/audio/output_device_mixer_manager_unittest.cc
@@ -36,6 +36,14 @@
 namespace audio {
 namespace {
 
+// The "default" and "communications" strings represent reserved device IDs.
+// They are used in different situations, but the OutputDeviceMixerManager
+// should treat all reserved device IDs the same way.
+enum class ReservedIdTestType {
+  kDefault,
+  kCommunications,
+};
+
 // Matches non-null device change callbacks.
 MATCHER(ValidDeviceChangeCallback, "") {
   return !arg.is_null();
@@ -59,9 +67,13 @@
 
 const std::string kFakeDeviceId = "0x1234";
 const std::string kOtherFakeDeviceId = "0x9876";
+const std::string kFakeCommunicationsId = "0xabcd";
 const std::string kEmptyDeviceId = std::string();
 const std::string kNormalizedDefaultDeviceId = kEmptyDeviceId;
-const auto* kDefaultDeviceId = media::AudioDeviceDescription::kDefaultDeviceId;
+const auto* kReservedDefaultId =
+    media::AudioDeviceDescription::kDefaultDeviceId;
+const auto* kReservedCommsId =
+    media::AudioDeviceDescription::kCommunicationsDeviceId;
 
 class MockAudioOutputStream : public AudioOutputStream {
  public:
@@ -85,6 +97,7 @@
   ~LocalMockAudioManager() override = default;
 
   MOCK_METHOD(std::string, GetDefaultOutputDeviceID, (), (override));
+  MOCK_METHOD(std::string, GetCommunicationsOutputDeviceID, (), (override));
   MOCK_METHOD(AudioParameters,
               GetOutputStreamParameters,
               (const std::string&),
@@ -93,7 +106,6 @@
               GetDefaultOutputStreamParameters,
               (),
               (override));
-
   MOCK_METHOD(AudioOutputStream*,
               MakeAudioOutputStreamProxy,
               (const media::AudioParameters&, const std::string&),
@@ -129,10 +141,11 @@
 }  // namespace
 
 class OutputDeviceMixerManagerTest
-    : public ::testing::TestWithParam<std::string> {
+    : public ::testing::TestWithParam<ReservedIdTestType> {
  public:
   OutputDeviceMixerManagerTest()
       : current_default_physical_device_id_(kFakeDeviceId),
+        current_communications_physical_device_id_(kFakeCommunicationsId),
         default_params_(AudioParameters::Format::AUDIO_PCM_LOW_LATENCY,
                         media::ChannelLayout::CHANNEL_LAYOUT_STEREO,
                         /*sample_rate=*/8000,
@@ -154,6 +167,13 @@
                  : kEmptyDeviceId;
     });
 
+    EXPECT_CALL(audio_manager_, GetCommunicationsOutputDeviceID())
+        .WillRepeatedly([&] {
+          return audio_manager_supports_communications_physical_id_
+                     ? current_communications_physical_device_id_
+                     : kEmptyDeviceId;
+        });
+
     // Force |output_mixer_manager_| to pick up the latest default device ID
     // from AudioManager::GetDefaultOutputDeviceID().
     output_mixer_manager_.OnDeviceChange();
@@ -173,7 +193,11 @@
     return current_default_physical_device_id_;
   }
 
-  void SetAudioManagerGetDefaultOutputDeviceIdSupport(bool support) {
+  std::string current_communications_physical_device() {
+    return current_communications_physical_device_id_;
+  }
+
+  void SetAudioManagerDefaultIdSupport(bool support) {
     bool needs_device_change =
         audio_manager_supports_default_physical_id_ != support;
     audio_manager_supports_default_physical_id_ = support;
@@ -183,6 +207,16 @@
       output_mixer_manager_.OnDeviceChange();
   }
 
+  void SetAudioManagerCommunicationsIdSupport(bool support) {
+    bool needs_device_change =
+        audio_manager_supports_communications_physical_id_ != support;
+    audio_manager_supports_communications_physical_id_ = support;
+
+    // Force |output_mixer_manager_| to pick up the latest default device ID.
+    if (needs_device_change)
+      output_mixer_manager_.OnDeviceChange();
+  }
+
   MockOutputDeviceMixer* SetUpMockMixerCreation(
       std::string device_id = kNormalizedDefaultDeviceId) {
     auto mock_output_mixer =
@@ -228,11 +262,21 @@
                                            GetNoopDeviceChangeCallback());
   }
 
+  void SimulateDeviceChange() {
+    SimulateDeviceChange(absl::nullopt, absl::nullopt);
+  }
+
   void SimulateDeviceChange(
-      absl::optional<std::string> new_default_physical_device = absl::nullopt) {
+      absl::optional<std::string> new_default_physical_device,
+      absl::optional<std::string> new_communications_physical_device) {
     if (new_default_physical_device)
       current_default_physical_device_id_ = *new_default_physical_device;
 
+    if (new_communications_physical_device) {
+      current_communications_physical_device_id_ =
+          *new_communications_physical_device;
+    }
+
     output_mixer_manager_.OnDeviceChange();
   }
 
@@ -247,12 +291,81 @@
   // Syntactic sugar, to differentiate from base::OnceClosure in tests.
   base::OnceClosure GetNoopDeviceChangeCallback() { return base::DoNothing(); }
 
+  // ----------------------------------------------------------
+  // The following methods are use to parameterize tests that are identical for
+  // the "communications" and "default" reserved IDs.
+  //
+
+  // Whether we are testing the "default" or "communications" reserved ID.
+  ReservedIdTestType reserved_id_test_type() { return GetParam(); }
+
+  void SetAudioManagerReservedIdSupport(bool support) {
+    switch (reserved_id_test_type()) {
+      case ReservedIdTestType::kDefault:
+        return SetAudioManagerDefaultIdSupport(support);
+      case ReservedIdTestType::kCommunications:
+        return SetAudioManagerCommunicationsIdSupport(support);
+    }
+  }
+
+  MockOutputDeviceMixer* SetUpReservedMixerCreation() {
+    switch (reserved_id_test_type()) {
+      case ReservedIdTestType::kDefault:
+        return SetUpMockMixerCreation(kNormalizedDefaultDeviceId);
+      case ReservedIdTestType::kCommunications:
+        return SetUpMockMixerCreation(kReservedCommsId);
+    }
+  }
+
+  MockOutputDeviceMixer* SetUpReservedMixer_NoStreams() {
+    switch (reserved_id_test_type()) {
+      case ReservedIdTestType::kDefault:
+        return SetUpMockMixer_NoStreams(kNormalizedDefaultDeviceId);
+      case ReservedIdTestType::kCommunications:
+        return SetUpMockMixer_NoStreams(kReservedCommsId);
+    }
+  }
+
+  std::string reserved_device_id() {
+    switch (reserved_id_test_type()) {
+      case ReservedIdTestType::kDefault:
+        return kReservedDefaultId;
+      case ReservedIdTestType::kCommunications:
+        return kReservedCommsId;
+    }
+  }
+
+  std::string current_reserved_physical_device() {
+    switch (reserved_id_test_type()) {
+      case ReservedIdTestType::kDefault:
+        return current_default_physical_device();
+      case ReservedIdTestType::kCommunications:
+        return current_communications_physical_device();
+    }
+  }
+
+  void SimulateReservedDeviceChange(std::string new_reserved_physical_id) {
+    switch (reserved_id_test_type()) {
+      case ReservedIdTestType::kDefault:
+        SimulateDeviceChange(new_reserved_physical_id, absl::nullopt);
+        return;
+      case ReservedIdTestType::kCommunications:
+        SimulateDeviceChange(absl::nullopt, new_reserved_physical_id);
+        return;
+    }
+  }
+
   bool audio_manager_supports_default_physical_id_ = true;
+  bool audio_manager_supports_communications_physical_id_ = true;
 
   // Simulate the value that would be returned by
   // AudioManager::GetDefaultOutputDeviceId() if it is supported.
   std::string current_default_physical_device_id_;
 
+  // Simulate the value that would be returned by
+  // AudioManager::GetCommunicationsOutputDeviceId() if it is supported.
+  std::string current_communications_physical_device_id_;
+
   base::test::SingleThreadTaskEnvironment task_environment_;
   AudioParameters default_params_;
   NiceMock<LocalMockAudioManager> audio_manager_;
@@ -274,40 +387,44 @@
   }
 };
 
-// Makes sure we can create an output stream for the default output device.
-TEST_F(OutputDeviceMixerManagerTest, MakeOutputStream_ForDefaultDevice) {
-  MockOutputDeviceMixer* default_mixer = SetUpMockMixerCreation();
+// Makes sure we can create an output stream for the reserved output devices.
+TEST_P(OutputDeviceMixerManagerTest, MakeOutputStream_ForReservedDevice) {
+  MockOutputDeviceMixer* reserved_mixer = SetUpReservedMixerCreation();
 
   MockAudioOutputStream mock_stream;
-  EXPECT_CALL(*default_mixer, MakeMixableStream(ExactParams(default_params_),
-                                                ValidDeviceChangeCallback()))
+  EXPECT_CALL(*reserved_mixer, MakeMixableStream(ExactParams(default_params_),
+                                                 ValidDeviceChangeCallback()))
       .WillOnce(Return(&mock_stream));
 
   AudioOutputStream* out_stream = output_mixer_manager_.MakeOutputStream(
-      kDefaultDeviceId, default_params_, GetNoopDeviceChangeCallback());
+      reserved_device_id(), default_params_, GetNoopDeviceChangeCallback());
 
   EXPECT_EQ(&mock_stream, out_stream);
 }
 
 // Makes sure we can create a default output stream when AudioManager doesn't
 // support getting the current default ID.
-TEST_F(OutputDeviceMixerManagerTest,
-       MakeOutputStream_ForDefaultDevice_NoGetDefaultOuputDeviceIdSupport) {
-  SetAudioManagerGetDefaultOutputDeviceIdSupport(false);
+TEST_P(OutputDeviceMixerManagerTest,
+       MakeOutputStream_ForReservedDevice_NoGetReservedOuputDeviceIdSupport) {
+  SetAudioManagerReservedIdSupport(false);
 
-  MockOutputDeviceMixer* default_mixer = SetUpMockMixerCreation();
+  // Note: kReservedCommsId maps to kNormalizedDefaultDeviceId when
+  //       |!audio_manager_supports_communications_physical_id_|.
+  MockOutputDeviceMixer* reserved_mixer =
+      SetUpMockMixerCreation(kNormalizedDefaultDeviceId);
 
   MockAudioOutputStream mock_stream;
-  EXPECT_CALL(*default_mixer, MakeMixableStream(ExactParams(default_params_),
-                                                ValidDeviceChangeCallback()))
+  EXPECT_CALL(*reserved_mixer, MakeMixableStream(ExactParams(default_params_),
+                                                 ValidDeviceChangeCallback()))
       .WillOnce(Return(&mock_stream));
 
   AudioOutputStream* out_stream = output_mixer_manager_.MakeOutputStream(
-      kDefaultDeviceId, default_params_, GetNoopDeviceChangeCallback());
+      reserved_device_id(), default_params_, GetNoopDeviceChangeCallback());
 
   EXPECT_EQ(&mock_stream, out_stream);
 }
 
+// Makes sure the empty string resolves to the "default" device.
 TEST_F(OutputDeviceMixerManagerTest,
        MakeOutputStream_ForDefaultDevice_EmptyDeviceId) {
   MockOutputDeviceMixer* default_mixer = SetUpMockMixerCreation();
@@ -317,30 +434,30 @@
                                                 ValidDeviceChangeCallback()))
       .WillOnce(Return(&mock_stream));
 
-  // kEmptyDeviceId should be treated the same as kDefaultDeviceId.
+  // kEmptyDeviceId should be treated the same as kReservedDefaultId.
   AudioOutputStream* out_stream = output_mixer_manager_.MakeOutputStream(
       kEmptyDeviceId, default_params_, GetNoopDeviceChangeCallback());
 
   EXPECT_EQ(&mock_stream, out_stream);
 }
 
-// Makes sure we can create an output stream for a device ID that happens to be
-// the current default.
-TEST_F(OutputDeviceMixerManagerTest,
-       MakeOutputStream_ForSpecificDeviceId_IdIsDefault) {
-  SetAudioManagerGetDefaultOutputDeviceIdSupport(true);
+// Makes sure we can create an output stream for physical IDs that match a
+// reserved ID's.
+TEST_P(OutputDeviceMixerManagerTest,
+       MakeOutputStream_ForSpecificDeviceId_MatchesCurrentReservedId) {
+  SetAudioManagerReservedIdSupport(true);
 
-  MockOutputDeviceMixer* default_mixer = SetUpMockMixerCreation();
+  MockOutputDeviceMixer* reserved_mixer = SetUpReservedMixerCreation();
 
   MockAudioOutputStream mock_stream;
-  EXPECT_CALL(*default_mixer, MakeMixableStream(ExactParams(default_params_),
-                                                ValidDeviceChangeCallback()))
+  EXPECT_CALL(*reserved_mixer, MakeMixableStream(ExactParams(default_params_),
+                                                 ValidDeviceChangeCallback()))
       .WillOnce(Return(&mock_stream));
 
-  // Getting a stream for current_default_physical_device() should create
-  // the |default_mixer| instead of a mixer for that physical ID.
+  // Getting a stream for current_reserved_physical_device() should create
+  // the |reserved_mixer| instead of a mixer for that physical ID.
   AudioOutputStream* out_stream = output_mixer_manager_.MakeOutputStream(
-      current_default_physical_device(), default_params_,
+      current_reserved_physical_device(), default_params_,
       GetNoopDeviceChangeCallback());
 
   EXPECT_EQ(&mock_stream, out_stream);
@@ -348,9 +465,9 @@
 
 // Makes sure we can create an output stream for a device ID when
 // AudioManager::GetDefaultOutputDeviceId() is unsupported.
-TEST_F(OutputDeviceMixerManagerTest,
+TEST_P(OutputDeviceMixerManagerTest,
        MakeOutputStream_ForSpecificDeviceId_NoGetDefaultOuputDeviceIdSupport) {
-  SetAudioManagerGetDefaultOutputDeviceIdSupport(false);
+  SetAudioManagerDefaultIdSupport(false);
 
   // A mixer for the physical device ID should be created, instead of the
   // default mixer.
@@ -370,11 +487,36 @@
   EXPECT_EQ(&mock_stream, out_stream);
 }
 
+// Makes sure we can create an output stream for a device ID when
+// AudioManager doesn't support getting the current reserved ID.
+TEST_P(OutputDeviceMixerManagerTest,
+       MakeOutputStream_ForSpecificDeviceId_NoGetGetReservedIdSupport) {
+  SetAudioManagerReservedIdSupport(false);
+
+  // A mixer for the physical device ID should be created, instead of the
+  // reserved mixer.
+  MockOutputDeviceMixer* physical_device_mixer =
+      SetUpMockMixerCreation(current_reserved_physical_device());
+
+  MockAudioOutputStream mock_stream;
+  EXPECT_CALL(*physical_device_mixer,
+              MakeMixableStream(ExactParams(default_params_),
+                                ValidDeviceChangeCallback()))
+      .WillOnce(Return(&mock_stream));
+
+  AudioOutputStream* out_stream = output_mixer_manager_.MakeOutputStream(
+      current_reserved_physical_device(), default_params_,
+      GetNoopDeviceChangeCallback());
+
+  EXPECT_EQ(&mock_stream, out_stream);
+}
+
 // Makes sure we can create an output stream a device ID for a device that is
-// not the default device.
+// not any device.
 TEST_F(OutputDeviceMixerManagerTest,
-       MakeOutputStream_ForSpecificDeviceId_IdIsNotDefaultOutput) {
+       MakeOutputStream_ForSpecificDeviceId_IdDoesntMatchReservedIds) {
   ASSERT_NE(kOtherFakeDeviceId, current_default_physical_device());
+  ASSERT_NE(kOtherFakeDeviceId, current_communications_physical_device());
 
   MockOutputDeviceMixer* mock_mixer =
       SetUpMockMixerCreation(kOtherFakeDeviceId);
@@ -403,7 +545,7 @@
   EXPECT_CALL(audio_manager_, GetDefaultOutputStreamParameters())
       .WillOnce(Return(default_params_));
 
-  output_mixer_manager_.MakeOutputStream(kDefaultDeviceId, default_params_,
+  output_mixer_manager_.MakeOutputStream(kReservedDefaultId, default_params_,
                                          GetNoopDeviceChangeCallback());
 
   testing::Mock::VerifyAndClearExpectations(this);
@@ -494,7 +636,7 @@
       .WillOnce(Return(ByMove(nullptr)));
 
   AudioOutputStream* out_stream = output_mixer_manager_.MakeOutputStream(
-      kDefaultDeviceId, default_params_, GetNoopDeviceChangeCallback());
+      kReservedDefaultId, default_params_, GetNoopDeviceChangeCallback());
 
   EXPECT_FALSE(out_stream);
 }
@@ -509,7 +651,7 @@
       .WillOnce(Return(nullptr));
 
   AudioOutputStream* out_stream = output_mixer_manager_.MakeOutputStream(
-      kDefaultDeviceId, default_params_, GetNoopDeviceChangeCallback());
+      kReservedDefaultId, default_params_, GetNoopDeviceChangeCallback());
 
   EXPECT_FALSE(out_stream);
 }
@@ -539,34 +681,35 @@
   EXPECT_NE(out_stream_a, out_stream_b);
 }
 
-// Makes sure creating an output stream for the "default ID" or the
-// "current default device" is equivalent, and the mixer are shared.
-TEST_F(OutputDeviceMixerManagerTest,
-       MakeOutputStream_DefaultIdAndCurrentDefaultShareOneMixer) {
-  MockOutputDeviceMixer* default_mixer = SetUpMockMixerCreation();
+// Makes sure creating an output stream for a "reserved ID" or the
+// "current reserved device ID" is equivalent, and the mixer is shared.
+TEST_P(OutputDeviceMixerManagerTest,
+       MakeOutputStream_ReservedIdAndCurrentReservedDeviceIdShareOneMixer) {
+  MockOutputDeviceMixer* special_mixer = SetUpReservedMixerCreation();
 
   MockAudioOutputStream mock_stream_a;
   MockAudioOutputStream mock_stream_b;
-  EXPECT_CALL(*default_mixer, MakeMixableStream(ExactParams(default_params_),
+  EXPECT_CALL(*special_mixer, MakeMixableStream(ExactParams(default_params_),
                                                 ValidDeviceChangeCallback()))
       .WillOnce(Return(&mock_stream_b))
       .WillOnce(Return(&mock_stream_a));
 
   // This call should create an OutputDeviceMixer.
   AudioOutputStream* out_stream_a = output_mixer_manager_.MakeOutputStream(
-      current_default_physical_device(), default_params_,
+      current_reserved_physical_device(), default_params_,
       GetNoopDeviceChangeCallback());
 
   // This call should re-use the same OutputDeviceMixer.
   AudioOutputStream* out_stream_b = output_mixer_manager_.MakeOutputStream(
-      kDefaultDeviceId, default_params_, GetNoopDeviceChangeCallback());
+      reserved_device_id(), default_params_, GetNoopDeviceChangeCallback());
 
   EXPECT_NE(out_stream_a, out_stream_b);
 }
 
 // Makes sure we create one output mixer per device ID.
 TEST_F(OutputDeviceMixerManagerTest, MakeOutputStream_TwoDevicesTwoMixers) {
-  SetAudioManagerGetDefaultOutputDeviceIdSupport(false);
+  SetAudioManagerDefaultIdSupport(false);
+  SetAudioManagerCommunicationsIdSupport(false);
 
   InSequence s;
   MockOutputDeviceMixer* mock_mixer_a = SetUpMockMixerCreation(kFakeDeviceId);
@@ -598,7 +741,7 @@
 // Makes sure the default mixer is separate from other mixers.
 TEST_F(OutputDeviceMixerManagerTest,
        MakeOutputStream_DefaultMixerDistinctFromOtherMixers) {
-  SetAudioManagerGetDefaultOutputDeviceIdSupport(false);
+  SetAudioManagerDefaultIdSupport(false);
 
   InSequence s;
   MockOutputDeviceMixer* fake_device_mixer =
@@ -610,7 +753,8 @@
                                 ValidDeviceChangeCallback()))
       .WillOnce(Return(&fake_stream));
 
-  MockOutputDeviceMixer* default_mixer = SetUpMockMixerCreation();
+  MockOutputDeviceMixer* default_mixer =
+      SetUpMockMixerCreation(kNormalizedDefaultDeviceId);
 
   MockAudioOutputStream default_stream;
   EXPECT_CALL(*default_mixer, MakeMixableStream(ExactParams(default_params_),
@@ -623,63 +767,102 @@
 
   // Create a second OutputDeviceMixer.
   AudioOutputStream* out_stream_b = output_mixer_manager_.MakeOutputStream(
-      kDefaultDeviceId, default_params_, GetNoopDeviceChangeCallback());
+      kReservedDefaultId, default_params_, GetNoopDeviceChangeCallback());
 
   EXPECT_NE(out_stream_a, out_stream_b);
 }
 
-// Makes sure we get the latest default device ID each time we create a stream
-// for the default device ID.
+// Makes sure the communications mixer is separate from other mixers.
 TEST_F(OutputDeviceMixerManagerTest,
-       MakeOutputStream_CurrentDefaultIsUpdatedAfterDeviceChange) {
-  SetAudioManagerGetDefaultOutputDeviceIdSupport(true);
+       MakeOutputStream_CommunicationsMixerDistinctFromOtherMixers) {
+  SetAudioManagerCommunicationsIdSupport(false);
 
-  MockOutputDeviceMixer* default_mixer_a = SetUpMockMixerCreation();
+  InSequence s;
+  MockOutputDeviceMixer* fake_device_mixer =
+      SetUpMockMixerCreation(kFakeCommunicationsId);
 
-  MockAudioOutputStream default_stream_a;
-  EXPECT_CALL(*default_mixer_a, MakeMixableStream(ExactParams(default_params_),
-                                                  ValidDeviceChangeCallback()))
-      .WillOnce(Return(&default_stream_a));
+  MockAudioOutputStream fake_stream;
+  EXPECT_CALL(*fake_device_mixer,
+              MakeMixableStream(ExactParams(default_params_),
+                                ValidDeviceChangeCallback()))
+      .WillOnce(Return(&fake_stream));
 
-  // Force the creation of |default_mixer_a|.
+  // Note: kReservedCommsId maps to kNormalizedDefaultDeviceId when
+  //       |!audio_manager_supports_communications_physical_id_|.
+  MockOutputDeviceMixer* default_mixer =
+      SetUpMockMixerCreation(kNormalizedDefaultDeviceId);
+
+  MockAudioOutputStream default_stream;
+  EXPECT_CALL(*default_mixer, MakeMixableStream(ExactParams(default_params_),
+                                                ValidDeviceChangeCallback()))
+      .WillOnce(Return(&default_stream));
+
+  // Create the first OutputDeviceMixer.
   AudioOutputStream* out_stream_a = output_mixer_manager_.MakeOutputStream(
-      current_default_physical_device(), default_params_,
+      kFakeCommunicationsId, default_params_, GetNoopDeviceChangeCallback());
+
+  // Create a second OutputDeviceMixer.
+  AudioOutputStream* out_stream_b = output_mixer_manager_.MakeOutputStream(
+      kReservedCommsId, default_params_, GetNoopDeviceChangeCallback());
+
+  EXPECT_NE(out_stream_a, out_stream_b);
+}
+
+// Makes sure we get the latest reserved device ID each time we create a stream
+// for a reserved ID.
+TEST_P(OutputDeviceMixerManagerTest,
+       MakeOutputStream_CurrentReseredIdIsUpdatedAfterDeviceChange) {
+  SetAudioManagerReservedIdSupport(true);
+
+  MockOutputDeviceMixer* reserved_mixer_a = SetUpReservedMixerCreation();
+
+  MockAudioOutputStream reserved_stream_a;
+  EXPECT_CALL(*reserved_mixer_a, MakeMixableStream(ExactParams(default_params_),
+                                                   ValidDeviceChangeCallback()))
+      .WillOnce(Return(&reserved_stream_a));
+
+  // Force the creation of |reserved_mixer_a|.
+  AudioOutputStream* out_stream_a = output_mixer_manager_.MakeOutputStream(
+      current_reserved_physical_device(), default_params_,
       GetNoopDeviceChangeCallback());
 
   // Update the current default physical device.
-  ASSERT_NE(current_default_physical_device(), kOtherFakeDeviceId);
-  SimulateDeviceChange(/*new_default_physical_device=*/kOtherFakeDeviceId);
-  ASSERT_EQ(current_default_physical_device(), kOtherFakeDeviceId);
+  ASSERT_NE(current_reserved_physical_device(), kOtherFakeDeviceId);
+  SimulateReservedDeviceChange(kOtherFakeDeviceId);
+  ASSERT_EQ(current_reserved_physical_device(), kOtherFakeDeviceId);
 
   testing::Mock::VerifyAndClearExpectations(this);
 
-  MockOutputDeviceMixer* default_mixer_b = SetUpMockMixerCreation();
+  MockOutputDeviceMixer* reserved_mixer_b = SetUpReservedMixerCreation();
 
-  MockAudioOutputStream default_stream_b;
-  EXPECT_CALL(*default_mixer_b, MakeMixableStream(ExactParams(default_params_),
-                                                  ValidDeviceChangeCallback()))
-      .WillOnce(Return(&default_stream_b));
+  MockAudioOutputStream reseved_stream_b;
+  EXPECT_CALL(*reserved_mixer_b, MakeMixableStream(ExactParams(default_params_),
+                                                   ValidDeviceChangeCallback()))
+      .WillOnce(Return(&reseved_stream_b));
 
-  // Force the creation of |default_mixer_b|, with a new
-  // current_default_physical_device().
+  // Force the creation of |reserved_mixer_b|, with a new
+  // current_reserved_physical_device().
   AudioOutputStream* out_stream_b = output_mixer_manager_.MakeOutputStream(
-      current_default_physical_device(), default_params_,
+      current_reserved_physical_device(), default_params_,
       GetNoopDeviceChangeCallback());
 
   EXPECT_NE(out_stream_a, out_stream_b);
 }
 
 // Makes sure OutputDeviceMixers are notified of device changes.
-TEST_F(OutputDeviceMixerManagerTest,
+TEST_P(OutputDeviceMixerManagerTest,
        OnDeviceChange_MixersReceiveDeviceChanges) {
-  SetAudioManagerGetDefaultOutputDeviceIdSupport(false);
+  SetAudioManagerReservedIdSupport(false);
 
   // We don't care about the streams these devices will create.
   InSequence s;
   MockOutputDeviceMixer* pre_mock_mixer_a =
-      SetUpMockMixer_NoStreams(kFakeDeviceId);
+      SetUpMockMixer_NoStreams(current_reserved_physical_device());
   MockOutputDeviceMixer* pre_mock_mixer_b =
       SetUpMockMixer_NoStreams(kOtherFakeDeviceId);
+
+  // Note: kReservedCommsId maps to kNormalizedDefaultDeviceId when
+  //       |!audio_manager_supports_communications_physical_id_|.
   MockOutputDeviceMixer* pre_mock_mixer_c =
       SetUpMockMixer_NoStreams(kNormalizedDefaultDeviceId);
 
@@ -688,13 +871,14 @@
   EXPECT_CALL(*pre_mock_mixer_c, ProcessDeviceChange()).Times(1);
 
   // Create the OutputDeviceMixers.
-  output_mixer_manager_.MakeOutputStream(kFakeDeviceId, default_params_,
+  output_mixer_manager_.MakeOutputStream(current_reserved_physical_device(),
+                                         default_params_,
                                          GetNoopDeviceChangeCallback());
 
   output_mixer_manager_.MakeOutputStream(kOtherFakeDeviceId, default_params_,
                                          GetNoopDeviceChangeCallback());
 
-  output_mixer_manager_.MakeOutputStream(kDefaultDeviceId, default_params_,
+  output_mixer_manager_.MakeOutputStream(reserved_device_id(), default_params_,
                                          GetNoopDeviceChangeCallback());
 
   // Trigger the calls to ProcessDeviceChange()
@@ -708,7 +892,7 @@
   EXPECT_CALL(*default_mixer, ProcessDeviceChange()).Times(1);
 
   // Create the mixer.
-  ForceOutputMixerCreation(kDefaultDeviceId);
+  ForceOutputMixerCreation(kReservedDefaultId);
   auto first_device_change_callback = GetOnDeviceChangeCallback();
   auto second_device_change_callback = GetOnDeviceChangeCallback();
 
@@ -722,7 +906,7 @@
 
   // Make sure old callbacks don't trigger new device change events.
   EXPECT_CALL(*new_default_mixer, ProcessDeviceChange()).Times(0);
-  ForceOutputMixerCreation(kDefaultDeviceId);
+  ForceOutputMixerCreation(kReservedDefaultId);
   std::move(second_device_change_callback).Run();
 
   testing::Mock::VerifyAndClearExpectations(new_default_mixer);
@@ -774,13 +958,14 @@
   output_mixer_manager_.StopListening(&listener_b);
 }
 
-// Attach/detach to the default device.
-TEST_F(OutputDeviceMixerManagerTest, DeviceOutputListener_StartStop_DefaultId) {
+// Attach/detach to the reserved device.
+TEST_P(OutputDeviceMixerManagerTest,
+       DeviceOutputListener_StartStop_ReservedId) {
   ExpectNoMixerCreated();
 
   StrictMock<MockListener> listener;
 
-  output_mixer_manager_.StartListening(&listener, kDefaultDeviceId);
+  output_mixer_manager_.StartListening(&listener, reserved_device_id());
   output_mixer_manager_.StopListening(&listener);
 }
 
@@ -818,45 +1003,51 @@
 }
 
 // Listeners are attached as they are added.
-TEST_F(OutputDeviceMixerManagerTest,
-       DeviceOutputListener_CreateStartStop_NoGetDefaultOuputDeviceIdSupport) {
-  SetAudioManagerGetDefaultOutputDeviceIdSupport(false);
+TEST_P(OutputDeviceMixerManagerTest,
+       DeviceOutputListener_CreateStartStop_NoGetReservedIdSupport) {
+  SetAudioManagerReservedIdSupport(false);
 
-  MockOutputDeviceMixer* mixer = SetUpMockMixer_NoStreams(kFakeDeviceId);
+  MockOutputDeviceMixer* mixer =
+      SetUpMockMixer_NoStreams(current_reserved_physical_device());
 
   auto listener = GetListener_MixerExpectsStartStop(mixer);
 
-  ForceOutputMixerCreation(kFakeDeviceId);
-  output_mixer_manager_.StartListening(listener.get(), kFakeDeviceId);
+  ForceOutputMixerCreation(current_reserved_physical_device());
+  output_mixer_manager_.StartListening(listener.get(),
+                                       current_reserved_physical_device());
   output_mixer_manager_.StopListening(listener.get());
 }
 
 // Listeners are attached on mixer creation.
-TEST_F(OutputDeviceMixerManagerTest,
-       DeviceOutputListener_StartCreateStop_NoGetDefaultOuputDeviceIdSupport) {
-  SetAudioManagerGetDefaultOutputDeviceIdSupport(false);
+TEST_P(OutputDeviceMixerManagerTest,
+       DeviceOutputListener_StartCreateStop_NoGetReservedIdSupport) {
+  SetAudioManagerReservedIdSupport(false);
 
-  MockOutputDeviceMixer* mixer = SetUpMockMixer_NoStreams(kFakeDeviceId);
+  MockOutputDeviceMixer* mixer =
+      SetUpMockMixer_NoStreams(current_reserved_physical_device());
 
   auto listener = GetListener_MixerExpectsStartStop(mixer);
 
-  output_mixer_manager_.StartListening(listener.get(), kFakeDeviceId);
-  ForceOutputMixerCreation(kFakeDeviceId);
+  output_mixer_manager_.StartListening(listener.get(),
+                                       current_reserved_physical_device());
+  ForceOutputMixerCreation(current_reserved_physical_device());
   output_mixer_manager_.StopListening(listener.get());
 }
 
 // Removed listeners are not attached.
-TEST_F(OutputDeviceMixerManagerTest,
-       DeviceOutputListener_StartStopCreate_NoGetDefaultOuputDeviceIdSupport) {
-  SetAudioManagerGetDefaultOutputDeviceIdSupport(false);
+TEST_P(OutputDeviceMixerManagerTest,
+       DeviceOutputListener_StartStopCreate_NoGetReservedIdSupport) {
+  SetAudioManagerReservedIdSupport(false);
 
-  MockOutputDeviceMixer* mixer = SetUpMockMixer_NoStreams(kFakeDeviceId);
+  MockOutputDeviceMixer* mixer =
+      SetUpMockMixer_NoStreams(current_reserved_physical_device());
 
   auto listener = GetListener_MixerExpectsNoCalls(mixer);
 
-  output_mixer_manager_.StartListening(listener.get(), kFakeDeviceId);
+  output_mixer_manager_.StartListening(listener.get(),
+                                       current_reserved_physical_device());
   output_mixer_manager_.StopListening(listener.get());
-  ForceOutputMixerCreation(kFakeDeviceId);
+  ForceOutputMixerCreation(current_reserved_physical_device());
 }
 
 // Removed listeners are not attached, and remaining listeners are.
@@ -875,64 +1066,75 @@
   ForceOutputMixerCreation(current_default_physical_device());
 }
 
-TEST_F(OutputDeviceMixerManagerTest,
-       DeviceOutputListener_CreateStartStop_DefaultId) {
-  MockOutputDeviceMixer* default_mixer = SetUpMockMixer_NoStreams();
+TEST_P(OutputDeviceMixerManagerTest,
+       DeviceOutputListener_CreateStartStop_ReservedId) {
+  MockOutputDeviceMixer* reserved_mixer = SetUpReservedMixer_NoStreams();
 
-  auto listener = GetListener_MixerExpectsStartStop(default_mixer);
+  auto listener = GetListener_MixerExpectsStartStop(reserved_mixer);
 
-  ForceOutputMixerCreation(kDefaultDeviceId);
-  output_mixer_manager_.StartListening(listener.get(), kDefaultDeviceId);
+  ForceOutputMixerCreation(reserved_device_id());
+  output_mixer_manager_.StartListening(listener.get(), reserved_device_id());
   output_mixer_manager_.StopListening(listener.get());
 }
 
-TEST_F(OutputDeviceMixerManagerTest,
-       DeviceOutputListener_StartCreateStop_DefaultId) {
-  MockOutputDeviceMixer* default_mixer = SetUpMockMixer_NoStreams();
+TEST_P(OutputDeviceMixerManagerTest,
+       DeviceOutputListener_StartCreateStop_ReservedId) {
+  MockOutputDeviceMixer* reserved_mixer = SetUpReservedMixer_NoStreams();
 
-  auto listener = GetListener_MixerExpectsStartStop(default_mixer);
+  auto listener = GetListener_MixerExpectsStartStop(reserved_mixer);
 
-  output_mixer_manager_.StartListening(listener.get(), kDefaultDeviceId);
-  ForceOutputMixerCreation(kDefaultDeviceId);
+  output_mixer_manager_.StartListening(listener.get(), reserved_device_id());
+  ForceOutputMixerCreation(reserved_device_id());
   output_mixer_manager_.StopListening(listener.get());
 }
 
+TEST_P(OutputDeviceMixerManagerTest,
+       DeviceOutputListener_StartStopCreate_ReservedId) {
+  MockOutputDeviceMixer* reserved_mixer = SetUpReservedMixer_NoStreams();
+
+  auto listener = GetListener_MixerExpectsNoCalls(reserved_mixer);
+
+  output_mixer_manager_.StartListening(listener.get(), reserved_device_id());
+  output_mixer_manager_.StopListening(listener.get());
+  ForceOutputMixerCreation(reserved_device_id());
+}
+
 TEST_F(OutputDeviceMixerManagerTest,
        DeviceOutputListener_StartCreateStop_DefaultId_EmptyDeviceId) {
   MockOutputDeviceMixer* default_mixer = SetUpMockMixer_NoStreams();
 
   auto listener = GetListener_MixerExpectsStartStop(default_mixer);
 
-  // kEmptyDeviceId should be treated the same as kDefaultDeviceId.
+  // kEmptyDeviceId should be treated the same as kReservedDefaultId.
   output_mixer_manager_.StartListening(listener.get(), kEmptyDeviceId);
   ForceOutputMixerCreation(kEmptyDeviceId);
   output_mixer_manager_.StopListening(listener.get());
 }
 
-// Makes sure default-listeners are attached to the default-mixer when it is
-// created via current_default_physical_device().
-TEST_F(OutputDeviceMixerManagerTest,
-       DeviceOutputListener_DefaultIdListenersAttachToCurrentDefaultMixer) {
-  MockOutputDeviceMixer* default_mixer = SetUpMockMixer_NoStreams();
+// Makes sure reserved-listeners are attached to the reserved-mixer when it is
+// created via current_reserved_physical_device().
+TEST_P(OutputDeviceMixerManagerTest,
+       DeviceOutputListener_ReservedListenersAttachToCurrentReservedIdMixer) {
+  MockOutputDeviceMixer* reserved_mixer = SetUpReservedMixer_NoStreams();
 
-  auto listener = GetListener_MixerExpectsStartStop(default_mixer);
+  auto listener = GetListener_MixerExpectsStartStop(reserved_mixer);
 
-  output_mixer_manager_.StartListening(listener.get(), kDefaultDeviceId);
-  ForceOutputMixerCreation(current_default_physical_device());
+  output_mixer_manager_.StartListening(listener.get(), reserved_device_id());
+  ForceOutputMixerCreation(current_reserved_physical_device());
   output_mixer_manager_.StopListening(listener.get());
 }
 
-// Makes sure current_default_physical_device() listeners are attached when the
-// default-mixer is created.
-TEST_F(OutputDeviceMixerManagerTest,
-       DeviceOutputListener_CurrentDefaultListenersAttachToDefaultIdMixer) {
-  MockOutputDeviceMixer* default_mixer = SetUpMockMixer_NoStreams();
+// Makes sure current_reserved_physical_device() listeners are attached when the
+// reserved-mixer is created.
+TEST_P(OutputDeviceMixerManagerTest,
+       DeviceOutputListener_CurrentReservedIdListenersAttachToReservedMixer) {
+  MockOutputDeviceMixer* reserved_mixer = SetUpReservedMixer_NoStreams();
 
-  auto listener = GetListener_MixerExpectsStartStop(default_mixer);
+  auto listener = GetListener_MixerExpectsStartStop(reserved_mixer);
 
   output_mixer_manager_.StartListening(listener.get(),
-                                       current_default_physical_device());
-  ForceOutputMixerCreation(kDefaultDeviceId);
+                                       current_reserved_physical_device());
+  ForceOutputMixerCreation(reserved_device_id());
   output_mixer_manager_.StopListening(listener.get());
 }
 
@@ -978,65 +1180,70 @@
   ForceOutputMixerCreation(kOtherFakeDeviceId);
 }
 
-// Makes sure the default listeners are re-attached when mixers are
+// Makes sure the reserved listeners are re-attached when mixers are
 // re-created.
-TEST_F(OutputDeviceMixerManagerTest,
-       DeviceOutputListener_DefaultIdListenersReattachedAfterDeviceChange) {
-  SetAudioManagerGetDefaultOutputDeviceIdSupport(true);
+TEST_P(OutputDeviceMixerManagerTest,
+       DeviceOutputListener_ReservedIdListenersReattachedAfterDeviceChange) {
+  SetAudioManagerReservedIdSupport(true);
 
-  MockOutputDeviceMixer* default_mixer = SetUpMockMixer_NoStreams();
+  MockOutputDeviceMixer* reserved_mixer = SetUpReservedMixer_NoStreams();
 
-  auto listener = GetListener_MixerExpectsStart(default_mixer);
+  auto listener = GetListener_MixerExpectsStart(reserved_mixer);
 
-  output_mixer_manager_.StartListening(listener.get(), kDefaultDeviceId);
+  output_mixer_manager_.StartListening(listener.get(), reserved_device_id());
 
-  // |listener| will be started when |default_mixer| is created.
-  ForceOutputMixerCreation(current_default_physical_device());
+  // |listener| will be started when |reserved_mixer| is created.
+  ForceOutputMixerCreation(current_reserved_physical_device());
 
   // Make sure the AudioManager::GetDefaultOutputDeviceId() returns a new value.
-  ASSERT_NE(current_default_physical_device(), kOtherFakeDeviceId);
-  SimulateDeviceChange(/*new_default_physical_device=*/kOtherFakeDeviceId);
+  ASSERT_NE(current_reserved_physical_device(), kOtherFakeDeviceId);
+  SimulateReservedDeviceChange(kOtherFakeDeviceId);
 
   testing::Mock::VerifyAndClearExpectations(this);
   testing::Mock::VerifyAndClearExpectations(listener.get());
 
   // |listener| should be attached to |new_default_mixer| when it is created.
-  MockOutputDeviceMixer* new_default_mixer = SetUpMockMixer_NoStreams();
-  EXPECT_CALL(*new_default_mixer, StartListening(listener.get())).Times(1);
+  MockOutputDeviceMixer* new_reserved_mixer = SetUpReservedMixer_NoStreams();
+  EXPECT_CALL(*new_reserved_mixer, StartListening(listener.get())).Times(1);
 
-  ASSERT_EQ(kOtherFakeDeviceId, current_default_physical_device());
+  ASSERT_EQ(kOtherFakeDeviceId, current_reserved_physical_device());
   ForceOutputMixerCreation(kOtherFakeDeviceId);
 }
 
-// Makes sure the default listeners are not attached to non-default listeners,
-// if support for AudioManager::GetDefaultOutputDeviceId() changes.
-TEST_F(OutputDeviceMixerManagerTest,
+// Makes sure the reserved listeners are not attached to non-reserved listeners,
+// if support for AudioManager's GetDefaultOutputDeviceID() or
+// GetCommunicationsOutputDeviceID() changes.
+TEST_P(OutputDeviceMixerManagerTest,
        DeviceOutputListener_CurrentDefaultListenersNotReattached) {
-  SetAudioManagerGetDefaultOutputDeviceIdSupport(true);
+  SetAudioManagerReservedIdSupport(true);
 
-  MockOutputDeviceMixer* default_mixer = SetUpMockMixer_NoStreams();
+  MockOutputDeviceMixer* reserved_mixer = SetUpReservedMixer_NoStreams();
 
-  // |default_mixer| should never get a call to StopListening(|listener|).
-  auto listener = GetListener_MixerExpectsStart(default_mixer);
+  // |reserved_mixer| should never get a call to StopListening(|listener|).
+  auto listener = GetListener_MixerExpectsStart(reserved_mixer);
 
   output_mixer_manager_.StartListening(listener.get(),
-                                       current_default_physical_device());
+                                       current_reserved_physical_device());
 
   // |listener| should be attached to |mixer|.
-  ForceOutputMixerCreation(kDefaultDeviceId);
+  ForceOutputMixerCreation(reserved_device_id());
 
-  SetAudioManagerGetDefaultOutputDeviceIdSupport(false);
+  SetAudioManagerReservedIdSupport(false);
   SimulateDeviceChange();
 
   testing::Mock::VerifyAndClearExpectations(this);
   testing::Mock::VerifyAndClearExpectations(listener.get());
 
-  // Now that AudioManager::GetDefaultOutputDeviceId() only returns
-  // kEmptyDeviceId, |listener| should not be attached to |new_default_mixer|.
-  MockOutputDeviceMixer* new_default_mixer = SetUpMockMixer_NoStreams();
-  EXPECT_CALL(*new_default_mixer, StartListening(listener.get())).Times(0);
+  // Now that AudioManager::GetDefaultOutputDeviceID() or
+  // AudioManager::GetCommunicationsOutputDeviceID() only returns
+  // kEmptyDeviceId, |listener| should not be attached to |new_reserved_mixer|.
+  // Note: kReservedCommsId maps to kNormalizedDefaultDeviceId when
+  //       |!audio_manager_supports_communications_physical_id_|.
+  MockOutputDeviceMixer* new_reserved_mixer =
+      SetUpMockMixer_NoStreams(kNormalizedDefaultDeviceId);
+  EXPECT_CALL(*new_reserved_mixer, StartListening(listener.get())).Times(0);
 
-  ForceOutputMixerCreation(kDefaultDeviceId);
+  ForceOutputMixerCreation(reserved_device_id());
 
   testing::Mock::VerifyAndClearExpectations(this);
   testing::Mock::VerifyAndClearExpectations(listener.get());
@@ -1044,93 +1251,93 @@
   // |listener| should still be attached to |new_physical_mixer| when it's
   // created after a device change.
   MockOutputDeviceMixer* new_physical_mixer =
-      SetUpMockMixer_NoStreams(current_default_physical_device());
+      SetUpMockMixer_NoStreams(current_reserved_physical_device());
   EXPECT_CALL(*new_physical_mixer, StartListening(listener.get())).Times(1);
 
   // |listener| should be attached to |new_physical_mixer|.
-  ForceOutputMixerCreation(current_default_physical_device());
+  ForceOutputMixerCreation(current_reserved_physical_device());
 }
 
-// Makes sure both "default listeners" and "current_default_physical_device()
-// listeners" get attached to the same current_default_physical_device() mixer.
-TEST_F(OutputDeviceMixerManagerTest,
+// Makes sure both "default listeners" and "current_reserved_physical_device()
+// listeners" get attached to the same current_reserved_physical_device() mixer.
+TEST_P(OutputDeviceMixerManagerTest,
        DeviceOutputListener_CurrentDefaultMixerCreation_ListenersAttached) {
-  MockOutputDeviceMixer* default_mixer = SetUpMockMixer_NoStreams();
+  MockOutputDeviceMixer* reserved_mixer = SetUpReservedMixer_NoStreams();
 
-  // Create listeners for kDefaultDeviceId and
-  // current_default_physical_device(), BOTH listening to |default_mixer|.
-  auto default_listener = GetListener_MixerExpectsStart(default_mixer);
-  auto current_default_physical_listener =
-      GetListener_MixerExpectsStart(default_mixer);
+  // Create listeners for reserved_device_id() and
+  // current_reserved_physical_device(), BOTH listening to |reserved_mixer|.
+  auto reserved_listener = GetListener_MixerExpectsStart(reserved_mixer);
+  auto current_reserved_physical_listener =
+      GetListener_MixerExpectsStart(reserved_mixer);
 
-  // Create another listener, NOT listening to |default_mixer|.
-  ASSERT_NE(kOtherFakeDeviceId, current_default_physical_device());
-  auto other_listener = GetListener_MixerExpectsNoCalls(default_mixer);
+  // Create another listener, NOT listening to |reserved_mixer|.
+  ASSERT_NE(kOtherFakeDeviceId, current_reserved_physical_device());
+  auto other_listener = GetListener_MixerExpectsNoCalls(reserved_mixer);
 
   // Start all listeners.
-  output_mixer_manager_.StartListening(default_listener.get(),
-                                       kDefaultDeviceId);
-  output_mixer_manager_.StartListening(current_default_physical_listener.get(),
-                                       current_default_physical_device());
+  output_mixer_manager_.StartListening(reserved_listener.get(),
+                                       reserved_device_id());
+  output_mixer_manager_.StartListening(current_reserved_physical_listener.get(),
+                                       current_reserved_physical_device());
   output_mixer_manager_.StartListening(other_listener.get(),
                                        kOtherFakeDeviceId);
 
   // |default_listener| and |current_default_physical_listener| should be
   // attached to |default_mixer|.
-  ForceOutputMixerCreation(current_default_physical_device());
+  ForceOutputMixerCreation(current_reserved_physical_device());
 }
 
-// Makes sure both "default listeners" and "current_default_physical_device()
-// listeners" get attached to the same default mixer.
-TEST_F(OutputDeviceMixerManagerTest,
-       DeviceOutputListener_DefaultIdMixerCreation_ListenersAttached) {
-  MockOutputDeviceMixer* default_mixer = SetUpMockMixer_NoStreams();
+// Makes sure both "reserved listeners" and "current_reserve_physical_device()
+// listeners" get attached to the same reserved mixer.
+TEST_P(OutputDeviceMixerManagerTest,
+       DeviceOutputListener_ReservedIdMixerCreation_ListenersAttached) {
+  MockOutputDeviceMixer* reserved_mixer = SetUpReservedMixer_NoStreams();
 
-  // Create listeners for kDefaultDeviceId and
-  // current_default_physical_device(), BOTH listening to |default_mixer|.
-  auto default_listener = GetListener_MixerExpectsStart(default_mixer);
-  auto current_default_physical_listener =
-      GetListener_MixerExpectsStart(default_mixer);
+  // Create listeners for reserved_device_id() and
+  // current_reserved_physical_device(), BOTH listening to |reserved_mixer|.
+  auto reserved_listener = GetListener_MixerExpectsStart(reserved_mixer);
+  auto current_reserved_physical_listener =
+      GetListener_MixerExpectsStart(reserved_mixer);
 
-  // Create another listener, NOT listening to |default_mixer|.
-  ASSERT_NE(kOtherFakeDeviceId, current_default_physical_device());
-  auto other_listener = GetListener_MixerExpectsNoCalls(default_mixer);
+  // Create another listener, NOT listening to |reserved_mixer|.
+  ASSERT_NE(kOtherFakeDeviceId, current_reserved_physical_device());
+  auto other_listener = GetListener_MixerExpectsNoCalls(reserved_mixer);
 
   // Start all listeners.
-  output_mixer_manager_.StartListening(default_listener.get(),
-                                       kDefaultDeviceId);
-  output_mixer_manager_.StartListening(current_default_physical_listener.get(),
-                                       current_default_physical_device());
+  output_mixer_manager_.StartListening(reserved_listener.get(),
+                                       reserved_device_id());
+  output_mixer_manager_.StartListening(current_reserved_physical_listener.get(),
+                                       current_reserved_physical_device());
   output_mixer_manager_.StartListening(other_listener.get(),
                                        kOtherFakeDeviceId);
 
-  // |default_listener| and |current_default_physical_listener| should be
-  // attached to |default_mixer|.
-  ForceOutputMixerCreation(kDefaultDeviceId);
+  // |reserved_listener| and |current_reserved_physical_listener| should be
+  // attached to |reserved_mixer|.
+  ForceOutputMixerCreation(reserved_device_id());
 }
 
-// Makes sure both "default listeners" and "current_default_physical_device()
-// listeners" don't get attached to non-default mixers.
-TEST_F(OutputDeviceMixerManagerTest,
+// Makes sure both "reserved listeners" and "current_reserved_physical_device()
+// listeners" don't get attached to non-reserved mixers.
+TEST_P(OutputDeviceMixerManagerTest,
        DeviceOutputListener_OtherDeviceMixerCreation_ListenersNotAttached) {
   MockOutputDeviceMixer* other_mixer =
       SetUpMockMixer_NoStreams(kOtherFakeDeviceId);
 
-  // Create listeners for kDefaultDeviceId and
-  // current_default_physical_device(), BOTH NOT listening to |other_mixer|.
-  auto default_listener = GetListener_MixerExpectsNoCalls(other_mixer);
-  auto current_default_physical_listener =
+  // Create listeners for reserved_device_id() and
+  // current_reserved_physical_device(), BOTH NOT listening to |other_mixer|.
+  auto reserved_listener = GetListener_MixerExpectsNoCalls(other_mixer);
+  auto current_reserved_physical_listener =
       GetListener_MixerExpectsNoCalls(other_mixer);
 
   // Create another listener, listening to |other_mixer|.
-  ASSERT_NE(kOtherFakeDeviceId, current_default_physical_device());
+  ASSERT_NE(kOtherFakeDeviceId, current_reserved_physical_device());
   auto other_listener = GetListener_MixerExpectsStart(other_mixer);
 
   // Start all listeners.
-  output_mixer_manager_.StartListening(default_listener.get(),
-                                       kDefaultDeviceId);
-  output_mixer_manager_.StartListening(current_default_physical_listener.get(),
-                                       current_default_physical_device());
+  output_mixer_manager_.StartListening(reserved_listener.get(),
+                                       reserved_device_id());
+  output_mixer_manager_.StartListening(current_reserved_physical_listener.get(),
+                                       current_reserved_physical_device());
   output_mixer_manager_.StartListening(other_listener.get(),
                                        kOtherFakeDeviceId);
 
@@ -1140,26 +1347,29 @@
 
 // Makes sure we can call StartListening multiple times with the same listener,
 // when the different device IDs map to the same mixer.
-TEST_F(OutputDeviceMixerManagerTest,
+TEST_P(OutputDeviceMixerManagerTest,
        DeviceOutputListener_MultipleStarts_EquivalentIds) {
-  MockOutputDeviceMixer* default_mixer = SetUpMockMixer_NoStreams();
-  ForceOutputMixerCreation(kDefaultDeviceId);
+  MockOutputDeviceMixer* default_mixer = SetUpReservedMixer_NoStreams();
+  ForceOutputMixerCreation(reserved_device_id());
 
   auto listener = GetListener_MixerExpectsStartStop(default_mixer);
 
   // Start listener.
-  output_mixer_manager_.StartListening(listener.get(), kDefaultDeviceId);
+  output_mixer_manager_.StartListening(listener.get(), reserved_device_id());
 
   // Verify starting with the same ID.
-  output_mixer_manager_.StartListening(listener.get(), kDefaultDeviceId);
+  output_mixer_manager_.StartListening(listener.get(), reserved_device_id());
 
   // Verify starting with equivalent IDs.
-  output_mixer_manager_.StartListening(listener.get(), kEmptyDeviceId);
+  if (reserved_id_test_type() == ReservedIdTestType::kDefault) {
+    // The kEmptyDeviceId also maps to kReservedDefaultId.
+    output_mixer_manager_.StartListening(listener.get(), kEmptyDeviceId);
+  }
   output_mixer_manager_.StartListening(listener.get(),
-                                       current_default_physical_device());
+                                       current_reserved_physical_device());
 
   // Return to the original ID.
-  output_mixer_manager_.StartListening(listener.get(), kDefaultDeviceId);
+  output_mixer_manager_.StartListening(listener.get(), reserved_device_id());
 
   output_mixer_manager_.StopListening(listener.get());
 }
@@ -1174,7 +1384,7 @@
     InSequence s;
     default_mixer = SetUpMockMixer_NoStreams(kNormalizedDefaultDeviceId);
     other_mixer = SetUpMockMixer_NoStreams(kOtherFakeDeviceId);
-    ForceOutputMixerCreation(kDefaultDeviceId);
+    ForceOutputMixerCreation(kReservedDefaultId);
     ForceOutputMixerCreation(kOtherFakeDeviceId);
   }
 
@@ -1182,44 +1392,94 @@
   EXPECT_CALL(*other_mixer, StartListening(listener.get())).Times(1);
   EXPECT_CALL(*other_mixer, StopListening(listener.get())).Times(0);
 
-  output_mixer_manager_.StartListening(listener.get(), kDefaultDeviceId);
+  output_mixer_manager_.StartListening(listener.get(), kReservedDefaultId);
 
   // This call should stop |default_mixer|.
   output_mixer_manager_.StartListening(listener.get(), kOtherFakeDeviceId);
 }
 
 // Makes sure listeners are properly updated internally when going from a
-// default to a specific device.
-TEST_F(OutputDeviceMixerManagerTest,
-       DeviceOutputListener_MultipleStarts_DefaultToSpecific) {
-  MockOutputDeviceMixer* default_mixer = SetUpMockMixer_NoStreams();
-  ForceOutputMixerCreation(kDefaultDeviceId);
+// reserved to a specific device.
+TEST_P(OutputDeviceMixerManagerTest,
+       DeviceOutputListener_MultipleStarts_ReservedToSpecific) {
+  MockOutputDeviceMixer* reserved_mixer = SetUpReservedMixer_NoStreams();
+  ForceOutputMixerCreation(reserved_device_id());
   testing::Mock::VerifyAndClearExpectations(this);
 
-  auto listener = GetListener_MixerExpectsStart(default_mixer);
-  output_mixer_manager_.StartListening(listener.get(), kDefaultDeviceId);
+  std::string original_reserved_id = current_reserved_physical_device();
 
-  // Switch |listener| to listen to the current default device.
-  ASSERT_EQ(current_default_physical_device(), kFakeDeviceId);
-  output_mixer_manager_.StartListening(listener.get(), kFakeDeviceId);
+  auto listener = GetListener_MixerExpectsStart(reserved_mixer);
+  output_mixer_manager_.StartListening(listener.get(), reserved_device_id());
 
-  // Change the default device.
-  ASSERT_NE(current_default_physical_device(), kOtherFakeDeviceId);
-  SimulateDeviceChange(/*new_default_physical_device=*/kOtherFakeDeviceId);
+  // Switch |listener| to listen to the current reserved device ID.
+  output_mixer_manager_.StartListening(listener.get(), original_reserved_id);
 
-  // The default mixer should not receive any start/stop calls with listener.
-  MockOutputDeviceMixer* new_default_mixer = SetUpMockMixer_NoStreams();
-  EXPECT_CALL(*new_default_mixer, StartListening(listener.get())).Times(0);
-  EXPECT_CALL(*new_default_mixer, StopListening(listener.get())).Times(0);
-  ForceOutputMixerCreation(kDefaultDeviceId);
+  // Change the reserved device ID.
+  ASSERT_NE(current_reserved_physical_device(), kOtherFakeDeviceId);
+  SimulateReservedDeviceChange(kOtherFakeDeviceId);
+  ASSERT_EQ(current_reserved_physical_device(), kOtherFakeDeviceId);
+
+  // The reserved mixer should not receive any start/stop calls with listener.
+  MockOutputDeviceMixer* new_reserved_mixer = SetUpReservedMixer_NoStreams();
+  EXPECT_CALL(*new_reserved_mixer, StartListening(listener.get())).Times(0);
+  EXPECT_CALL(*new_reserved_mixer, StopListening(listener.get())).Times(0);
+  ForceOutputMixerCreation(reserved_device_id());
   testing::Mock::VerifyAndClearExpectations(this);
 
-  // The kFakeDeviceId mixer should be started with listener.
+  // The |original_reserved_id| mixer should be started with listener.
   MockOutputDeviceMixer* physical_mixer =
-      SetUpMockMixer_NoStreams(kFakeDeviceId);
+      SetUpMockMixer_NoStreams(original_reserved_id);
   EXPECT_CALL(*physical_mixer, StartListening(listener.get())).Times(1);
   EXPECT_CALL(*physical_mixer, StopListening(listener.get())).Times(0);
+  ForceOutputMixerCreation(original_reserved_id);
+}
+
+// Makes sure that there one shared default and communications mixer, if the
+// current default and communication physical IDs are identical.
+TEST_F(OutputDeviceMixerManagerTest,
+       ReservedIds_DefaultAndCommunicationsPhysicalIdsShared) {
+  SetAudioManagerDefaultIdSupport(true);
+  SetAudioManagerCommunicationsIdSupport(true);
+
+  // Set both the "default" and "communications" physical ID to be the same.
+  SimulateDeviceChange(kFakeDeviceId, kFakeDeviceId);
+
+  SetUpMockMixer_NoStreams(kNormalizedDefaultDeviceId);
+
+  // All three IDs should resolve to a single kNormalizedDefaultDeviceId device.
+  ForceOutputMixerCreation(kReservedDefaultId);
+  ForceOutputMixerCreation(kReservedCommsId);
   ForceOutputMixerCreation(kFakeDeviceId);
 }
 
+// Makes sure that we map "communications" to kNormalizedDefaultDeviceId if the
+// current physical communications device ID is empty.
+TEST_F(OutputDeviceMixerManagerTest,
+       ReservedIds_EmptyCommunicationsPhysicalId) {
+  SetAudioManagerDefaultIdSupport(true);
+  SetAudioManagerCommunicationsIdSupport(true);
+
+  // Set the "communications" physical ID be empty.
+  SimulateDeviceChange(kFakeDeviceId, kEmptyDeviceId);
+
+  SetUpMockMixer_NoStreams(kNormalizedDefaultDeviceId);
+
+  // The "communications" ID should resolve to kNormalizedDefaultDeviceId.
+  ForceOutputMixerCreation(kReservedCommsId);
+}
+
+INSTANTIATE_TEST_SUITE_P(OutputDeviceMixerManagerTest,
+                         OutputDeviceMixerManagerTest,
+                         testing::Values(ReservedIdTestType::kDefault,
+                                         ReservedIdTestType::kCommunications),
+                         [](const ::testing::TestParamInfo<
+                             OutputDeviceMixerManagerTest::ParamType>& info) {
+                           switch (info.param) {
+                             case ReservedIdTestType::kDefault:
+                               return "Default";
+                             case ReservedIdTestType::kCommunications:
+                               return "Comms";
+                           }
+                         });
+
 }  // namespace audio
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index 4cfe537..812cfeb 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -1951,6 +1951,7 @@
   net::TransportSecurityState* state =
       url_request_context_->transport_security_state();
   state->EnableStaticPinsForTesting();
+  state->SetPinningListAlwaysTimelyForTesting(true);
   std::move(callback).Run();
 }
 
diff --git a/services/network/network_context_unittest.cc b/services/network/network_context_unittest.cc
index 4efa291..72d8e6c 100644
--- a/services/network/network_context_unittest.cc
+++ b/services/network/network_context_unittest.cc
@@ -1329,6 +1329,10 @@
   const char kReportHost[] = "report-uri.preloaded.test";
   const char kReportPath[] = "/pkp";
 
+  base::test::ScopedFeatureList scoped_feature_list_;
+  scoped_feature_list_.InitAndEnableFeature(
+      net::features::kStaticKeyPinningEnforcement);
+
   for (bool reporting_enabled : {false, true}) {
     // Server that PKP reports are sent to.
     net::test_server::EmbeddedTestServer report_test_server;
diff --git a/testing/buildbot/chrome.json b/testing/buildbot/chrome.json
index 0a928883..72d543d 100644
--- a/testing/buildbot/chrome.json
+++ b/testing/buildbot/chrome.json
@@ -1911,7 +1911,7 @@
       {
         "args": [],
         "cros_board": "atlas",
-        "cros_img": "atlas-release/R103-14800.0.0",
+        "cros_img": "atlas-release/R103-14803.0.0",
         "name": "lacros_all_tast_tests ATLAS_RELEASE_LKGM",
         "resultdb": {
           "enable": true,
@@ -1943,7 +1943,7 @@
       {
         "args": [],
         "cros_board": "eve",
-        "cros_img": "eve-release/R103-14800.0.0",
+        "cros_img": "eve-release/R103-14803.0.0",
         "name": "lacros_all_tast_tests EVE_RELEASE_LKGM",
         "resultdb": {
           "enable": true,
@@ -2020,7 +2020,7 @@
       {
         "args": [],
         "cros_board": "hana",
-        "cros_img": "hana-release/R103-14800.0.0",
+        "cros_img": "hana-release/R103-14803.0.0",
         "name": "lacros_all_tast_tests HANA_RELEASE_LKGM",
         "resultdb": {
           "enable": true,
@@ -2052,7 +2052,7 @@
       {
         "args": [],
         "cros_board": "jacuzzi",
-        "cros_img": "jacuzzi-release/R103-14800.0.0",
+        "cros_img": "jacuzzi-release/R103-14803.0.0",
         "name": "lacros_all_tast_tests JACUZZI_RELEASE_LKGM",
         "resultdb": {
           "enable": true,
@@ -2086,7 +2086,7 @@
           "--test-launcher-filter-file=../../testing/buildbot/filters/lacros-arm.ozone_unittests.filter"
         ],
         "cros_board": "hana",
-        "cros_img": "hana-release/R103-14800.0.0",
+        "cros_img": "hana-release/R103-14803.0.0",
         "name": "ozone_unittests HANA_RELEASE_LKGM",
         "resultdb": {
           "enable": true,
@@ -2120,7 +2120,7 @@
           "--test-launcher-filter-file=../../testing/buildbot/filters/lacros-arm.ozone_unittests.filter"
         ],
         "cros_board": "jacuzzi",
-        "cros_img": "jacuzzi-release/R103-14800.0.0",
+        "cros_img": "jacuzzi-release/R103-14803.0.0",
         "name": "ozone_unittests JACUZZI_RELEASE_LKGM",
         "resultdb": {
           "enable": true,
@@ -2154,7 +2154,7 @@
           "--test-launcher-filter-file=../../testing/buildbot/filters/lacros-arm.viz_unittests.filter"
         ],
         "cros_board": "hana",
-        "cros_img": "hana-release/R103-14800.0.0",
+        "cros_img": "hana-release/R103-14803.0.0",
         "name": "viz_unittests HANA_RELEASE_LKGM",
         "resultdb": {
           "enable": true,
@@ -2188,7 +2188,7 @@
           "--test-launcher-filter-file=../../testing/buildbot/filters/lacros-arm.viz_unittests.filter"
         ],
         "cros_board": "jacuzzi",
-        "cros_img": "jacuzzi-release/R103-14800.0.0",
+        "cros_img": "jacuzzi-release/R103-14803.0.0",
         "name": "viz_unittests JACUZZI_RELEASE_LKGM",
         "resultdb": {
           "enable": true,
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index 4421d8d9..781b739c 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -8154,15 +8154,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M101/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M101/out/Release",
           "--client-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8188,7 +8188,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.66"
+              "revision": "version:101.0.4951.68"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -8239,15 +8239,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M102/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M102/out/Release",
           "--client-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8273,7 +8273,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.50"
+              "revision": "version:102.0.5005.51"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -8664,15 +8664,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M101/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8698,7 +8698,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.66"
+              "revision": "version:101.0.4951.68"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -8749,15 +8749,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M102/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -8783,7 +8783,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.50"
+              "revision": "version:102.0.5005.51"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index 2563101..c12081b 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -46128,15 +46128,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M101/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M101/out/Release",
           "--client-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -46162,7 +46162,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.66"
+              "revision": "version:101.0.4951.68"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -46213,15 +46213,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M102/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M102/out/Release",
           "--client-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -46247,7 +46247,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.50"
+              "revision": "version:102.0.5005.51"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -46638,15 +46638,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M101/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -46672,7 +46672,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.66"
+              "revision": "version:101.0.4951.68"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -46723,15 +46723,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M102/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -46757,7 +46757,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.50"
+              "revision": "version:102.0.5005.51"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47152,15 +47152,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M101/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M101/out/Release",
           "--client-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47186,7 +47186,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.66"
+              "revision": "version:101.0.4951.68"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47237,15 +47237,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M102/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M102/out/Release",
           "--client-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47271,7 +47271,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.50"
+              "revision": "version:102.0.5005.51"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47662,15 +47662,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M101/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47696,7 +47696,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.66"
+              "revision": "version:101.0.4951.68"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -47747,15 +47747,15 @@
       {
         "args": [
           "--additional-apk=apks/ChromePublic.apk",
+          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/AOSP_SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M102/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -47781,7 +47781,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.50"
+              "revision": "version:102.0.5005.51"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48244,15 +48244,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M101/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M101/out/Release",
           "--client-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48278,7 +48278,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.66"
+              "revision": "version:101.0.4951.68"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48329,15 +48329,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M102/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M102/out/Release",
           "--client-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48363,7 +48363,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.50"
+              "revision": "version:102.0.5005.51"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48754,15 +48754,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M101/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48788,7 +48788,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.66"
+              "revision": "version:101.0.4951.68"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -48839,15 +48839,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M102/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -48873,7 +48873,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.50"
+              "revision": "version:102.0.5005.51"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49336,15 +49336,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M101/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M101/out/Release",
           "--client-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49370,7 +49370,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.66"
+              "revision": "version:101.0.4951.68"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49421,15 +49421,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
+          "--client-outdir",
+          "../../weblayer_instrumentation_test_M102/out/Release",
           "--implementation-outdir",
           ".",
           "--test-expectations",
           "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
-          "--client-outdir",
-          "../../weblayer_instrumentation_test_M102/out/Release",
           "--client-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49455,7 +49455,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.50"
+              "revision": "version:102.0.5005.51"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49846,15 +49846,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M101/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=101",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49880,7 +49880,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M101",
-              "revision": "version:101.0.4951.66"
+              "revision": "version:101.0.4951.68"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
@@ -49931,15 +49931,15 @@
       {
         "args": [
           "--additional-apk=apks/WebLayerShellSystemWebView.apk",
+          "--webview-apk-path=apks/SystemWebView.apk",
           "--test-runner-outdir",
           ".",
           "--client-outdir",
           ".",
-          "--test-expectations",
-          "../../weblayer/browser/android/javatests/skew/expectations.txt",
-          "--webview-apk-path=apks/SystemWebView.apk",
           "--implementation-outdir",
           "../../weblayer_instrumentation_test_M102/out/Release",
+          "--test-expectations",
+          "../../weblayer/browser/android/javatests/skew/expectations.txt",
           "--impl-version=102",
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
@@ -49965,7 +49965,7 @@
             {
               "cipd_package": "chromium/testing/weblayer-x86",
               "location": "weblayer_instrumentation_test_M102",
-              "revision": "version:102.0.5005.50"
+              "revision": "version:102.0.5005.51"
             },
             {
               "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index b4e777b9..21d0e68 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -5919,21 +5919,21 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5057.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5058.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests Lacros version skew testing ash 103.0.5057.0",
+        "name": "lacros_chrome_browsertests Lacros version skew testing ash 103.0.5058.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v103.0.5057.0",
-              "revision": "version:103.0.5057.0"
+              "location": "lacros_version_skew_tests_v103.0.5058.0",
+              "revision": "version:103.0.5058.0"
             }
           ],
           "dimension_sets": [
@@ -5945,7 +5945,7 @@
         },
         "test": "lacros_chrome_browsertests",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests/",
-        "variant_id": "Lacros version skew testing ash 103.0.5057.0"
+        "variant_id": "Lacros version skew testing ash 103.0.5058.0"
       },
       {
         "args": [
@@ -6065,21 +6065,21 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5057.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5058.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 103.0.5057.0",
+        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 103.0.5058.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v103.0.5057.0",
-              "revision": "version:103.0.5057.0"
+              "location": "lacros_version_skew_tests_v103.0.5058.0",
+              "revision": "version:103.0.5058.0"
             }
           ],
           "dimension_sets": [
@@ -6091,7 +6091,7 @@
         },
         "test": "lacros_chrome_browsertests_run_in_series",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests_run_in_series/",
-        "variant_id": "Lacros version skew testing ash 103.0.5057.0"
+        "variant_id": "Lacros version skew testing ash 103.0.5058.0"
       },
       {
         "isolate_profile_data": true,
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 1eda38f..729e88b1 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -87922,28 +87922,28 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5057.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5058.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests Lacros version skew testing ash 103.0.5057.0",
+        "name": "lacros_chrome_browsertests Lacros version skew testing ash 103.0.5058.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v103.0.5057.0",
-              "revision": "version:103.0.5057.0"
+              "location": "lacros_version_skew_tests_v103.0.5058.0",
+              "revision": "version:103.0.5058.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "lacros_chrome_browsertests",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests/",
-        "variant_id": "Lacros version skew testing ash 103.0.5057.0"
+        "variant_id": "Lacros version skew testing ash 103.0.5058.0"
       },
       {
         "args": [
@@ -88043,28 +88043,28 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5057.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5058.0/test_ash_chrome"
         ],
         "isolate_profile_data": true,
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 103.0.5057.0",
+        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 103.0.5058.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v103.0.5057.0",
-              "revision": "version:103.0.5057.0"
+              "location": "lacros_version_skew_tests_v103.0.5058.0",
+              "revision": "version:103.0.5058.0"
             }
           ],
           "service_account": "chromium-tester@chops-service-accounts.iam.gserviceaccount.com"
         },
         "test": "lacros_chrome_browsertests_run_in_series",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests_run_in_series/",
-        "variant_id": "Lacros version skew testing ash 103.0.5057.0"
+        "variant_id": "Lacros version skew testing ash 103.0.5058.0"
       },
       {
         "isolate_profile_data": true,
@@ -89439,20 +89439,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5057.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5058.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests Lacros version skew testing ash 103.0.5057.0",
+        "name": "lacros_chrome_browsertests Lacros version skew testing ash 103.0.5058.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v103.0.5057.0",
-              "revision": "version:103.0.5057.0"
+              "location": "lacros_version_skew_tests_v103.0.5058.0",
+              "revision": "version:103.0.5058.0"
             }
           ],
           "dimension_sets": [
@@ -89465,7 +89465,7 @@
         },
         "test": "lacros_chrome_browsertests",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests/",
-        "variant_id": "Lacros version skew testing ash 103.0.5057.0"
+        "variant_id": "Lacros version skew testing ash 103.0.5058.0"
       },
       {
         "args": [
@@ -89585,20 +89585,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5057.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5058.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 103.0.5057.0",
+        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 103.0.5058.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v103.0.5057.0",
-              "revision": "version:103.0.5057.0"
+              "location": "lacros_version_skew_tests_v103.0.5058.0",
+              "revision": "version:103.0.5058.0"
             }
           ],
           "dimension_sets": [
@@ -89611,7 +89611,7 @@
         },
         "test": "lacros_chrome_browsertests_run_in_series",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests_run_in_series/",
-        "variant_id": "Lacros version skew testing ash 103.0.5057.0"
+        "variant_id": "Lacros version skew testing ash 103.0.5058.0"
       },
       {
         "merge": {
@@ -91144,20 +91144,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5057.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5058.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests Lacros version skew testing ash 103.0.5057.0",
+        "name": "lacros_chrome_browsertests Lacros version skew testing ash 103.0.5058.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v103.0.5057.0",
-              "revision": "version:103.0.5057.0"
+              "location": "lacros_version_skew_tests_v103.0.5058.0",
+              "revision": "version:103.0.5058.0"
             }
           ],
           "dimension_sets": [
@@ -91170,7 +91170,7 @@
         },
         "test": "lacros_chrome_browsertests",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests/",
-        "variant_id": "Lacros version skew testing ash 103.0.5057.0"
+        "variant_id": "Lacros version skew testing ash 103.0.5058.0"
       },
       {
         "args": [
@@ -91290,20 +91290,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.lacros_chrome_browsertests.skew.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5057.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5058.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 103.0.5057.0",
+        "name": "lacros_chrome_browsertests_run_in_series Lacros version skew testing ash 103.0.5058.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v103.0.5057.0",
-              "revision": "version:103.0.5057.0"
+              "location": "lacros_version_skew_tests_v103.0.5058.0",
+              "revision": "version:103.0.5058.0"
             }
           ],
           "dimension_sets": [
@@ -91316,7 +91316,7 @@
         },
         "test": "lacros_chrome_browsertests_run_in_series",
         "test_id_prefix": "ninja://chrome/test:lacros_chrome_browsertests_run_in_series/",
-        "variant_id": "Lacros version skew testing ash 103.0.5057.0"
+        "variant_id": "Lacros version skew testing ash 103.0.5058.0"
       },
       {
         "merge": {
@@ -92051,20 +92051,20 @@
       {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/linux-lacros.interactive_ui_tests.filter",
-          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5057.0/test_ash_chrome"
+          "--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5058.0/test_ash_chrome"
         ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
         },
-        "name": "interactive_ui_tests Lacros version skew testing ash 103.0.5057.0",
+        "name": "interactive_ui_tests Lacros version skew testing ash 103.0.5058.0",
         "swarming": {
           "can_use_on_swarming_builders": true,
           "cipd_packages": [
             {
               "cipd_package": "chromium/testing/linux-ash-chromium/x86_64/ash.zip",
-              "location": "lacros_version_skew_tests_v103.0.5057.0",
-              "revision": "version:103.0.5057.0"
+              "location": "lacros_version_skew_tests_v103.0.5058.0",
+              "revision": "version:103.0.5058.0"
             }
           ],
           "dimension_sets": [
@@ -92077,7 +92077,7 @@
         },
         "test": "interactive_ui_tests",
         "test_id_prefix": "ninja://chrome/test:interactive_ui_tests/",
-        "variant_id": "Lacros version skew testing ash 103.0.5057.0"
+        "variant_id": "Lacros version skew testing ash 103.0.5058.0"
       }
     ]
   },
diff --git a/testing/buildbot/chromium.json b/testing/buildbot/chromium.json
index b6121a1..fe74b4cd 100644
--- a/testing/buildbot/chromium.json
+++ b/testing/buildbot/chromium.json
@@ -37,11 +37,6 @@
       "all"
     ]
   },
-  "linux-archive-tagged": {
-    "additional_compile_targets": [
-      "all"
-    ]
-  },
   "linux-official": {
     "additional_compile_targets": [
       "all"
@@ -57,11 +52,6 @@
       "all"
     ]
   },
-  "mac-archive-tagged": {
-    "additional_compile_targets": [
-      "all"
-    ]
-  },
   "mac-arm64-archive-dbg": {
     "additional_compile_targets": [
       "all"
@@ -72,11 +62,6 @@
       "all"
     ]
   },
-  "mac-arm64-archive-tagged": {
-    "additional_compile_targets": [
-      "all"
-    ]
-  },
   "mac-official": {
     "additional_compile_targets": [
       "all"
@@ -99,18 +84,6 @@
       }
     ]
   },
-  "win-archive-tagged": {
-    "additional_compile_targets": [
-      "all"
-    ],
-    "scripts": [
-      {
-        "name": "checkbins",
-        "script": "checkbins.py",
-        "swarming": {}
-      }
-    ]
-  },
   "win-official": {
     "additional_compile_targets": [
       "all"
@@ -133,18 +106,6 @@
       }
     ]
   },
-  "win32-archive-tagged": {
-    "additional_compile_targets": [
-      "all"
-    ],
-    "scripts": [
-      {
-        "name": "checkbins",
-        "script": "checkbins.py",
-        "swarming": {}
-      }
-    ]
-  },
   "win32-official": {
     "additional_compile_targets": [
       "all"
diff --git a/testing/buildbot/filters/fuchsia.browser_tests.filter b/testing/buildbot/filters/fuchsia.browser_tests.filter
index 11c366d9..8108e1ef 100644
--- a/testing/buildbot/filters/fuchsia.browser_tests.filter
+++ b/testing/buildbot/filters/fuchsia.browser_tests.filter
@@ -26,6 +26,7 @@
 -All/MediaHistoryBrowserTest.DoNotRecordWatchtime_Muted/0
 -All/MediaHistoryBrowserTest.DoNotRecordWatchtime_Muted/2
 -All/MediaHistoryBrowserTest.RecordWatchtime_VideoOnly/0
+-All/NetworkRequestMetricsBrowserTest.FileURLSuccess/2
 -All/PermissionPromptBubbleViewBrowserTest.ActiveTabClosedAfterRendererCrashesWithPendingPermissionRequest/0
 -All/PermissionPromptBubbleViewBrowserTest.ActiveTabClosedAfterRendererCrashesWithPendingPermissionRequest/1
 -All/PrintBackendPrintBrowserTest.UpdatePrintSettings/2
@@ -34,6 +35,7 @@
 -All/PrintBackendPrintBrowserTestService.StartPrintingSpoolingSharedMemoryError/0
 -All/PrintBackendPrintBrowserTestService.StartPrintingSpoolingSharedMemoryError/1
 -All/ReportingBrowserTest.CrashReportUnresponsive/1
+-All/SubresourceFilterWebSocketBrowserTest.BlockWebSocket/1
 -AllForms/FormStructureBrowserTest.DataDrivenHeuristics/153
 -AppSessionRestoreTest.RestoreAppMinimized
 -AppSessionRestoreTest.RestoreMaximizedApp
@@ -42,6 +44,7 @@
 -AppWindowApiTest.OnMinimizedEvent
 -AppWindowApiTest.OnRestoredEvent
 -BackForwardCacheSiteDetailsBrowserTest.MemoryDetailsForBackForwardCache
+-BluetoothApiTest.Discovery
 -BrowserTest.FaviconChange
 -BrowserViewTest.GetAccessibleTabModalDialogTitle
 -BrowsingDataRemoverBrowserTest.Database
@@ -78,6 +81,7 @@
 -ChromeMimeHandlerViewTest.SingleRequest
 -ChromeMimeHandlerViewTest.TargetBlankAnchor
 -ChromeMojoProxyResolverFactoryBrowserTest.DestroyAndCreateService
+-ChromeMojoProxyResolverFactoryBrowserTest.DestroyFactory
 -ChromeMojoProxyResolverFactoryBrowserTest.ServiceLifecycle
 -ChromeNavigationBrowserTest.CrossSiteRedirectionToPDF
 -ChromeNavigationBrowserTestWithMobileEmulation.CrossSiteRedirectionToPDFWithMobileEmulation
@@ -94,10 +98,12 @@
 -ClientHintsBrowserTest.SwitchAppliesStorageOneOrigin
 -ClientHintsBrowserTest.SwitchNotJson
 -ClientHintsBrowserTest.SwitchOriginNotSecure
+-CommerceHintAgentTest.PurchaseByURL
 -ContentAnalysisDelegateBlockingSettingBrowserTest.BlockLargeFiles/0
 -ContentAnalysisDelegateBlockingSettingBrowserTest.BlockLargeFiles/1
 -ContentAnalysisDelegateBlockingSettingBrowserTest.BlockLargeFiles/2
 -ContentAnalysisDelegateBlockingSettingBrowserTest.BlockLargeFiles/3
+-ContentVerifierTest.UnreadableContentScriptFailsContentVerification
 -ContextMenuBrowserTest.OpenProfileNoneReferrer
 -CookieControlsBubbleViewTest.InvokeUi_NotWorkingClicked
 -CookiePolicyBrowserTest.NestedFirstPartyIFrameStorageForFrame
@@ -187,6 +193,8 @@
 -IsolatedAppTest.SessionStorage
 -LoadingPredictorBrowserTestWithProxy.PrepareForPageLoadWithPrediction
 -LoadingPredictorBrowserTestWithProxy.PrepareForPageLoadWithoutPrediction
+-LocalCardMigrationBrowserTestForStatusChip.Feedback_CardSavingAnimation
+-LocalCardMigrationBrowserTest.DeleteSuccessfullyMigratedCardsFromLocal
 -LocalCardMigrationBrowserUiTest.InvokeUi_default
 -MediaEngagementBrowserTest.RecordSingleVisitOnSameOrigin
 -MediaEngagementBrowserTest.SessionNewTabNavigateSameURLWithOpener_Typed
@@ -220,6 +228,7 @@
 -PrivacyBudgetFieldtrialConfigTest.LoadsSettingsFromFieldTrialConfig
 -PrivateNetworkAccessWithFeatureEnabledBrowserTest.DoesNotRecordAddressSpaceFeatureForBlockedRequests
 -ProcessManagementTest.ProcessOverflow
+-ProcessMemoryMetricsEmitterTest.FetchAndEmitMetrics
 -ProcessMemoryMetricsEmitterTest.FetchAndEmitMetricsWithExtensions
 -ProcessMemoryMetricsEmitterTest.FetchDuringTrace
 -ProcessMemoryMetricsEmitterTest.ForegroundAndBackgroundPages
@@ -231,9 +240,11 @@
 -ProfileMenuViewExtensionsTest.CloseIPH
 -ProfileWindowBrowserTest.OpenBrowserWindowForProfileWithSigninRequired
 -ProxyBrowserTest.BasicAuthWSConnect
+-ProxyBrowserTest.ProxyAuthHTTP
 -ProxyBrowserTest.ProxyAuthHTTPS
 -ProxySettingsApiTest.ProxyEventsParseError
 -RecordLanguagesMetricsBrowserTest.ContentLanguageEmpty
+-RecordLanguagesMetricsBrowserTest.ContentLanguageIsWildcard
 -RecordLanguagesMetricsBrowserTest.ContentLanguageMatchTopMostAcceptLanguage
 -RecordLanguagesMetricsBrowserTest.ContentLanguageMatchesAnyAcceptLanguage
 -RecordLanguagesMetricsBrowserTest.IframeBothParentAndChildContentLanguageIsEmpty
@@ -243,6 +254,7 @@
 -RecordLanguagesMetricsBrowserTest.IframeParentContentLanguageIsEmptyAndChildContentLanguageHasValue
 -RecordLanguagesMetricsBrowserTest.IframeParentContentLanuageIsStarAndChildContentLanguageHasValue
 -RecordLanguagesMetricsBrowserTest.IframeParentHasConentLanguageAndChildContentLanguageIsEmpty
+-RecordLanguagesMetricsBrowserTest.IframeParentHasConentLanguageAndChildContentLanguageIsStar
 -RecordLanguagesMetricsBrowserTest.LargeAcceptLanguageList
 -RemoteCopyBrowserTest.ImageUrl
 -RemoteCopyBrowserTest.Text
@@ -256,6 +268,7 @@
 -RssLinksFetcherTest.FetchSuccessfulFromHead
 -SafetyTipPageInfoBubbleViewDigitalAssetLinksBrowserTest.NoShowOnDigitalAssetLinkMatch
 -SaveAsCompleteHtml/SavePageOriginalVsSavedComparisonTest.CrossSiteObject/0
+-SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream.Upload_SubmittingFormWithExpirationDateMonthAndYearExpired
 -SaveCardBubbleViewsSyncTransportFullFormBrowserTest.Local_TransportMode_InfoTextIconDoesNotExist
 -SaveCardBubbleViewsSyncTransportFullFormBrowserTest.Upload_TransportMode_ClickingSaveClosesBubble
 -SaveCardBubbleViewsSyncTransportFullFormBrowserTest.Upload_TransportMode_InfoTextIconExists
@@ -317,6 +330,7 @@
 -VariationsSafeModeEndToEndBrowserTest.ExtendedSafeModeEndToEnd
 -WebAudioBrowserTest.VerifyDynamicsCompressorFingerprint
 -WebNavigationApiBackForwardCacheTest.ForwardBack
+-WebSocketBrowserTest.WebSocketSplitSegments
 -WebViewTests/WebViewTest.DownloadCookieIsolation_CrossSession/SiteIsolationForGuestsDisabled
 -WebViewTests/WebViewTest.DownloadCookieIsolation_CrossSession/SiteIsolationForGuestsEnabled
 -WebViewTests/WebViewTest.NoPrerenderer/SiteIsolationForGuestsDisabled
diff --git a/testing/buildbot/filters/pixel_tests.filter b/testing/buildbot/filters/pixel_tests.filter
index 3e076ab..660035a 100644
--- a/testing/buildbot/filters/pixel_tests.filter
+++ b/testing/buildbot/filters/pixel_tests.filter
@@ -36,6 +36,7 @@
 HungRendererDialogViewBrowserTest.*
 ImportLockDialogViewBrowserTest.*
 InlineLoginHelperBrowserTest.InvokeUi_*
+IntentPickerDialogTest.*
 InteractionSequenceBrowserUtilTest.CompareScreenshot_*
 LocalCardMigrationBrowserUiTest.*
 NewTabPageTest.*
diff --git a/testing/buildbot/internal.chromeos.fyi.json b/testing/buildbot/internal.chromeos.fyi.json
index 15ec9830..3d1391e 100644
--- a/testing/buildbot/internal.chromeos.fyi.json
+++ b/testing/buildbot/internal.chromeos.fyi.json
@@ -1126,7 +1126,7 @@
       {
         "args": [],
         "cros_board": "octopus",
-        "cros_img": "octopus-release/R103-14800.0.0",
+        "cros_img": "octopus-release/R103-14803.0.0",
         "name": "lacros_fyi_tast_tests OCTOPUS_RELEASE_LKGM",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1174,7 +1174,7 @@
       {
         "args": [],
         "cros_board": "octopus",
-        "cros_img": "octopus-release/R103-14800.0.0",
+        "cros_img": "octopus-release/R103-14803.0.0",
         "name": "ozone_unittests OCTOPUS_RELEASE_LKGM",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1226,7 +1226,7 @@
       {
         "args": [],
         "cros_board": "strongbad",
-        "cros_img": "strongbad-release/R103-14800.0.0",
+        "cros_img": "strongbad-release/R103-14803.0.0",
         "name": "lacros_all_tast_tests STRONGBAD_RELEASE_LKGM",
         "swarming": {},
         "tast_expr": "(\"group:mainline\" && \"dep:lacros\" && !informational)",
@@ -1274,7 +1274,7 @@
       {
         "args": [],
         "cros_board": "strongbad",
-        "cros_img": "strongbad-release/R103-14800.0.0",
+        "cros_img": "strongbad-release/R103-14803.0.0",
         "name": "ozone_unittests STRONGBAD_RELEASE_LKGM",
         "swarming": {},
         "test": "ozone_unittests",
@@ -1318,7 +1318,7 @@
       {
         "args": [],
         "cros_board": "strongbad",
-        "cros_img": "strongbad-release/R103-14800.0.0",
+        "cros_img": "strongbad-release/R103-14803.0.0",
         "name": "viz_unittests STRONGBAD_RELEASE_LKGM",
         "swarming": {},
         "test": "viz_unittests",
diff --git a/testing/buildbot/variants.pyl b/testing/buildbot/variants.pyl
index 111a3af..2cca4a82 100644
--- a/testing/buildbot/variants.pyl
+++ b/testing/buildbot/variants.pyl
@@ -22,15 +22,15 @@
   },
   'LACROS_VERSION_SKEW_CANARY': {
     'args': [
-      '--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5057.0/test_ash_chrome',
+      '--ash-chrome-path-override=../../lacros_version_skew_tests_v103.0.5058.0/test_ash_chrome',
     ],
-    'identifier': 'Lacros version skew testing ash 103.0.5057.0',
+    'identifier': 'Lacros version skew testing ash 103.0.5058.0',
     'swarming': {
       'cipd_packages': [
         {
           'cipd_package': 'chromium/testing/linux-ash-chromium/x86_64/ash.zip',
-          'location': 'lacros_version_skew_tests_v103.0.5057.0',
-          'revision': 'version:103.0.5057.0',
+          'location': 'lacros_version_skew_tests_v103.0.5058.0',
+          'revision': 'version:103.0.5058.0',
         },
       ],
     },
@@ -462,16 +462,16 @@
   },
   'WEBLAYER_10_AND_M_IMPL_SKEW_TESTS_NTH_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--test-expectations',
-      '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
       '--implementation-outdir',
       '../../weblayer_instrumentation_test_M102/out/Release',
-      '--impl-version=102'
+      '--test-expectations',
+      '../../weblayer/browser/android/javatests/skew/expectations.txt',
+      '--impl-version=102',
     ],
     'identifier': 'with_impl_from_102',
     'swarming': {
@@ -479,23 +479,23 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M102',
-          'revision': 'version:102.0.5005.50'
+          'revision': 'version:102.0.5005.51',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_10_AND_M_IMPL_SKEW_TESTS_NTH_MINUS_ONE_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--test-expectations',
-      '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/AOSP_SystemWebView.apk',
       '--implementation-outdir',
       '../../weblayer_instrumentation_test_M101/out/Release',
-      '--impl-version=101'
+      '--test-expectations',
+      '../../weblayer/browser/android/javatests/skew/expectations.txt',
+      '--impl-version=101',
     ],
     'identifier': 'with_impl_from_101',
     'swarming': {
@@ -503,10 +503,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M101',
-          'revision': 'version:101.0.4951.66'
+          'revision': 'version:101.0.4951.68',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_10_AND_M_IMPL_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
@@ -606,16 +606,16 @@
   },
   'WEBLAYER_IMPL_SKEW_TESTS_NTH_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--test-expectations',
-      '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/SystemWebView.apk',
       '--implementation-outdir',
       '../../weblayer_instrumentation_test_M102/out/Release',
-      '--impl-version=102'
+      '--test-expectations',
+      '../../weblayer/browser/android/javatests/skew/expectations.txt',
+      '--impl-version=102',
     ],
     'identifier': 'with_impl_from_102',
     'swarming': {
@@ -623,23 +623,23 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M102',
-          'revision': 'version:102.0.5005.50'
+          'revision': 'version:102.0.5005.51',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_IMPL_SKEW_TESTS_NTH_MINUS_ONE_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
       '--client-outdir',
       '.',
-      '--test-expectations',
-      '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/SystemWebView.apk',
       '--implementation-outdir',
       '../../weblayer_instrumentation_test_M101/out/Release',
-      '--impl-version=101'
+      '--test-expectations',
+      '../../weblayer/browser/android/javatests/skew/expectations.txt',
+      '--impl-version=101',
     ],
     'identifier': 'with_impl_from_101',
     'swarming': {
@@ -647,10 +647,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M101',
-          'revision': 'version:101.0.4951.66'
+          'revision': 'version:101.0.4951.68',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_IMPL_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
@@ -750,16 +750,16 @@
   },
   'WEBLAYER_CLIENT_SKEW_TESTS_NTH_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
+      '--client-outdir',
+      '../../weblayer_instrumentation_test_M102/out/Release',
       '--implementation-outdir',
       '.',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/SystemWebView.apk',
-      '--client-outdir',
-      '../../weblayer_instrumentation_test_M102/out/Release',
-      '--client-version=102'
+      '--client-version=102',
     ],
     'identifier': 'with_client_from_102',
     'swarming': {
@@ -767,23 +767,23 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M102',
-          'revision': 'version:102.0.5005.50'
+          'revision': 'version:102.0.5005.51',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_CLIENT_SKEW_TESTS_NTH_MINUS_ONE_MILESTONE': {
     'args': [
+      '--webview-apk-path=apks/SystemWebView.apk',
       '--test-runner-outdir',
       '.',
+      '--client-outdir',
+      '../../weblayer_instrumentation_test_M101/out/Release',
       '--implementation-outdir',
       '.',
       '--test-expectations',
       '../../weblayer/browser/android/javatests/skew/expectations.txt',
-      '--webview-apk-path=apks/SystemWebView.apk',
-      '--client-outdir',
-      '../../weblayer_instrumentation_test_M101/out/Release',
-      '--client-version=101'
+      '--client-version=101',
     ],
     'identifier': 'with_client_from_101',
     'swarming': {
@@ -791,10 +791,10 @@
         {
           'cipd_package': 'chromium/testing/weblayer-x86',
           'location': 'weblayer_instrumentation_test_M101',
-          'revision': 'version:101.0.4951.66'
+          'revision': 'version:101.0.4951.68',
         }
-      ]
-    }
+      ],
+    },
   },
   'WEBLAYER_CLIENT_SKEW_TESTS_NTH_MINUS_TWO_MILESTONE': {
     'args': [
@@ -896,7 +896,7 @@
     'skylab': {
       'cros_board': 'atlas',
       'cros_chrome_version': '103.0.5045.0',
-      'cros_img': 'atlas-release/R103-14800.0.0',
+      'cros_img': 'atlas-release/R103-14803.0.0',
     },
     'enabled': True,
     'identifier': 'ATLAS_RELEASE_LKGM',
@@ -914,7 +914,7 @@
     'skylab': {
       'cros_board': 'eve',
       'cros_chrome_version': '103.0.5045.0',
-      'cros_img': 'eve-release/R103-14800.0.0',
+      'cros_img': 'eve-release/R103-14803.0.0',
     },
     'enabled': True,
     'identifier': 'EVE_RELEASE_LKGM',
@@ -941,7 +941,7 @@
     'skylab': {
       'cros_board': 'hana',
       'cros_chrome_version': '103.0.5045.0',
-      'cros_img': 'hana-release/R103-14800.0.0',
+      'cros_img': 'hana-release/R103-14803.0.0',
     },
     'enabled': True,
     'identifier': 'HANA_RELEASE_LKGM',
@@ -959,7 +959,7 @@
     'skylab': {
       'cros_board': 'jacuzzi',
       'cros_chrome_version': '103.0.5045.0',
-      'cros_img': 'jacuzzi-release/R103-14800.0.0',
+      'cros_img': 'jacuzzi-release/R103-14803.0.0',
     },
     'enabled': True,
     'identifier': 'JACUZZI_RELEASE_LKGM',
@@ -986,7 +986,7 @@
     'skylab': {
       'cros_board': 'octopus',
       'cros_chrome_version': '103.0.5045.0',
-      'cros_img': 'octopus-release/R103-14800.0.0',
+      'cros_img': 'octopus-release/R103-14803.0.0',
     },
     'enabled': True,
     'identifier': 'OCTOPUS_RELEASE_LKGM',
@@ -1022,7 +1022,7 @@
     'skylab': {
       'cros_board': 'strongbad',
       'cros_chrome_version': '103.0.5045.0',
-      'cros_img': 'strongbad-release/R103-14800.0.0',
+      'cros_img': 'strongbad-release/R103-14803.0.0',
     },
     'enabled': True,
     'identifier': 'STRONGBAD_RELEASE_LKGM',
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 677c079f..20999977 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -405,14 +405,6 @@
           'all',
         ],
       },
-      'linux-archive-tagged': {
-        'mixins': [
-          'linux-archive-rel-args',
-        ],
-        'additional_compile_targets': [
-          'all',
-        ],
-      },
       'linux-official': {
         'additional_compile_targets': [
           'all',
@@ -429,12 +421,6 @@
           'all',
         ],
       },
-      'mac-archive-tagged': {
-        'mixins': ['mac-archive-rel-args'],
-        'additional_compile_targets': [
-          'all',
-        ],
-      },
       'mac-arm64-archive-dbg': {
         'additional_compile_targets': [
           'all',
@@ -446,12 +432,6 @@
           'all',
         ],
       },
-      'mac-arm64-archive-tagged': {
-        'mixins': ['mac-archive-rel-args'],
-        'additional_compile_targets': [
-          'all',
-        ],
-      },
       'mac-official': {
         'additional_compile_targets': [
           'all',
@@ -470,14 +450,6 @@
           'scripts': 'public_build_scripts',
         },
       },
-      'win-archive-tagged': {
-        'additional_compile_targets': [
-          'all',
-        ],
-        'test_suites': {
-          'scripts': 'public_build_scripts',
-        },
-      },
       'win-official': {
         'additional_compile_targets': [
           'all',
@@ -496,14 +468,6 @@
           'scripts': 'public_build_scripts',
         },
       },
-      'win32-archive-tagged': {
-        'additional_compile_targets': [
-          'all',
-        ],
-        'test_suites': {
-          'scripts': 'public_build_scripts',
-        },
-      },
       'win32-official': {
         'additional_compile_targets': [
           'all',
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 210418e..1e624c13 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3408,7 +3408,7 @@
             ],
             "experiments": [
                 {
-                    "name": "EnabledServiceWTesRhc",
+                    "name": "EnabledServiceWTesRhc_05102022",
                     "params": {
                         "UploadIntervalSeconds": "3600"
                     },
@@ -3419,7 +3419,7 @@
                     ]
                 },
                 {
-                    "name": "EnabledService",
+                    "name": "EnabledService_05102022",
                     "params": {
                         "UploadIntervalSeconds": "3600"
                     },
@@ -3428,7 +3428,7 @@
                     ]
                 },
                 {
-                    "name": "EnabledServiceWTes",
+                    "name": "EnabledServiceWTes_05102022",
                     "params": {
                         "UploadIntervalSeconds": "3600"
                     },
@@ -3438,7 +3438,7 @@
                     ]
                 },
                 {
-                    "name": "EnabledServiceWRhc",
+                    "name": "EnabledServiceWRhc_05102022",
                     "params": {
                         "UploadIntervalSeconds": "3600"
                     },
@@ -3448,7 +3448,7 @@
                     ]
                 },
                 {
-                    "name": "EnabledServiceWTesRhcPer",
+                    "name": "EnabledServiceWTesRhcPer_05102022",
                     "params": {
                         "UploadIntervalSeconds": "3600"
                     },
@@ -4319,25 +4319,6 @@
             ]
         }
     ],
-    "JourneysUseEngagementScoreCache": [
-        {
-            "platforms": [
-                "chromeos",
-                "chromeos_lacros",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "JourneysUseEngagementScoreCache"
-                    ]
-                }
-            ]
-        }
-    ],
     "KeyPinningComponentUpdater": [
         {
             "platforms": [
@@ -5451,46 +5432,47 @@
             ]
         }
     ],
-    "PageContentAnnotations": [
+    "PageContentAnnotationsAndroid": [
         {
             "platforms": [
                 "android"
             ],
             "experiments": [
                 {
-                    "name": "PageContentAnnotationsMemImproved_20210604",
+                    "name": "EnabledNoFilters_20220502",
                     "params": {
-                        "write_to_history_service": "false"
+                        "extract_related_searches": "true",
+                        "models_to_execute_v2": "OPTIMIZATION_TARGET_PAGE_ENTITIES:de:en:en-AU:en-CA:en-GB:en-US:es:fr:it:nl:pt:tr",
+                        "supported_locales": "de,en,en-AU,en-CA,en-GB,en-US,es,fr,it,nl,pt,tr",
+                        "write_to_history_service": "true"
                     },
                     "enable_features": [
-                        "LoadModelFileForEachExecution",
                         "PageContentAnnotations",
-                        "PageVisibilityPageContentAnnotations"
+                        "PageEntitiesModelBypassFilters",
+                        "PageEntitiesPageContentAnnotations"
                     ]
                 }
             ]
-        },
+        }
+    ],
+    "PageContentAnnotationsDesktopNonstableM102Plus": [
         {
             "platforms": [
                 "chromeos",
+                "chromeos_lacros",
                 "linux",
                 "mac",
                 "windows"
             ],
             "experiments": [
                 {
-                    "name": "PageContentAnnotationsWithVisibility_JourneysWithOmniboxAction_20211202",
+                    "name": "Enabled_20220502",
                     "params": {
-                        "annotate_title_instead_of_page_content": "true",
-                        "bag_of_words_entities": "false",
-                        "content_clustering_enabled": "false",
-                        "extract_related_searches": "true",
-                        "min_page_topics_model_version_for_visibility": "2110051800",
-                        "models_to_execute_v2": "OPTIMIZATION_TARGET_PAGE_TOPICS:en",
-                        "write_to_history_service": "true"
+                        "JourneysLocaleOrLanguageAllowlist": "de,en,en-AU,en-CA,en-GB,en-US,es,fr,it,nl,pt,tr,ar,pl,ru,zh",
+                        "supported_locales": "de,en,en-AU,en-CA,en-GB,en-US,es,fr,it,nl,pt,tr,ar,pl,ru,zh"
                     },
                     "enable_features": [
-                        "PageContentAnnotations",
+                        "Journeys",
                         "PageEntitiesPageContentAnnotations"
                     ]
                 }
@@ -5501,6 +5483,7 @@
         {
             "platforms": [
                 "chromeos",
+                "chromeos_lacros",
                 "linux",
                 "mac",
                 "windows"
@@ -5513,32 +5496,6 @@
                     ]
                 }
             ]
-        },
-        {
-            "platforms": [
-                "chromeos",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "PageContentAnnotationsWithVisibility_JourneysWithOmniboxAction_20211202",
-                    "params": {
-                        "annotate_title_instead_of_page_content": "true",
-                        "bag_of_words_entities": "false",
-                        "content_clustering_enabled": "false",
-                        "extract_related_searches": "true",
-                        "min_page_topics_model_version_for_visibility": "2110051800",
-                        "models_to_execute_v2": "OPTIMIZATION_TARGET_PAGE_TOPICS:en",
-                        "write_to_history_service": "true"
-                    },
-                    "enable_features": [
-                        "PageContentAnnotations",
-                        "PageEntitiesPageContentAnnotations"
-                    ]
-                }
-            ]
         }
     ],
     "PageInfoDiscoverabilityTimeout": [
@@ -6540,57 +6497,9 @@
             ],
             "experiments": [
                 {
-                    "name": "Enabled_28_0_20220314",
+                    "name": "Enabled_28_1_20220511",
                     "params": {
-                        "reporter_omaha_tag": "28.0"
-                    },
-                    "enable_features": [
-                        "ClientSideDetectionTag"
-                    ]
-                }
-            ]
-        }
-    ],
-    "SafeBrowsingCTDownloadWarning": [
-        {
-            "platforms": [
-                "chromeos",
-                "chromeos_lacros",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "SafeBrowsingCTDownloadWarning"
-                    ]
-                }
-            ]
-        }
-    ],
-    "SafeBrowsingCompareCSDDesktop": [
-        {
-            "platforms": [
-                "windows",
-                "linux",
-                "mac"
-            ],
-            "experiments": [
-                {
-                    "name": "EnabledTfLiteAndBaseline_20220217",
-                    "params": {
-                        "reporter_omaha_tag": "27.6"
-                    },
-                    "enable_features": [
-                        "ClientSideDetectionTag"
-                    ]
-                },
-                {
-                    "name": "EnabledTfLiteOnly_20220217",
-                    "params": {
-                        "reporter_omaha_tag": "27.7"
+                        "reporter_omaha_tag": "28.1"
                     },
                     "enable_features": [
                         "ClientSideDetectionTag"
diff --git a/third_party/.gitignore b/third_party/.gitignore
index f6cb858b..b61afd0 100644
--- a/third_party/.gitignore
+++ b/third_party/.gitignore
@@ -70,6 +70,7 @@
 /cld_2/src
 /cld_3/src
 /colorama/src
+/content_analysis_sdk/src
 /cpuinfo/src
 /crc32c/src
 /cros
diff --git a/third_party/blink/public/mojom/loader/resource_load_info_notifier.mojom b/third_party/blink/public/mojom/loader/resource_load_info_notifier.mojom
index b76fb04c..9bc5d7b 100644
--- a/third_party/blink/public/mojom/loader/resource_load_info_notifier.mojom
+++ b/third_party/blink/public/mojom/loader/resource_load_info_notifier.mojom
@@ -19,6 +19,8 @@
                                  network.mojom.URLResponseHead redirect_response);
 
   // Called to notify the response has been received.
+  // TODO(crbug.com/973885): make |response_url| as url.mojom.SchemeHostPort
+  // instead of url.mojom.Url if applicable.
   NotifyResourceResponseReceived(int64 request_id,
                                  url.mojom.Url response_url,
                                  network.mojom.URLResponseHead head,
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index 9620c3d..86cdee74 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1345,6 +1345,7 @@
     "imagebitmap/image_bitmap_test.cc",
     "input/event_handler_test.cc",
     "input/ime_on_focus_test.cc",
+    "input/mouse_event_manager_test.cc",
     "input/overscroll_behavior_test.cc",
     "input/pointer_event_manager_test.cc",
     "input/scroll_snap_test.cc",
diff --git a/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc b/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc
index 5ec03482..4455a243 100644
--- a/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_mojo_handler.cc
@@ -1200,7 +1200,7 @@
 
 void LocalFrameMojoHandler::ClosePage(
     mojom::blink::LocalMainFrame::ClosePageCallback completion_callback) {
-  SECURITY_CHECK(frame_->IsMainFrame());
+  SECURITY_CHECK(frame_->IsOutermostMainFrame());
 
   // There are two ways to close a page:
   //
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 2efb529..c639651 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -1906,6 +1906,8 @@
     }
   }
 
+  UpdateDocumentAnnotatedRegions();
+
   GetLayoutView()->EnclosingLayer()->UpdateLayerPositionsAfterLayout();
   frame_->Selection().DidLayout();
 
@@ -3255,7 +3257,6 @@
     PerformPostLayoutTasks(visual_viewport_size_changed);
     GetFrame().GetDocument()->LayoutUpdated();
   }
-  UpdateDocumentAnnotatedRegions();
   UpdateGeometriesIfNeeded();
 }
 
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 ab0a266..b8b34dd0 100644
--- a/third_party/blink/renderer/core/input/mouse_event_manager.cc
+++ b/third_party/blink/renderer/core/input/mouse_event_manager.cc
@@ -520,6 +520,13 @@
     // TODO(crbug.com/716694): Do not reset mouse_down_element_ for the purpose
     // of gathering data.
   }
+  if (mouse_press_node_ &&
+      node_to_be_removed.IsShadowIncludingInclusiveAncestorOf(
+          *mouse_press_node_)) {
+    // If the mouse_press_node_ is removed, we should dispatch future default
+    // keyboard actions (i.e. scrolling) to the still connected parent.
+    mouse_press_node_ = node_to_be_removed.parentNode();
+  }
 }
 
 Element* MouseEventManager::GetElementUnderMouse() {
@@ -721,6 +728,8 @@
       frame_->GetEventHandler().GetSelectionController().HandleMousePressEvent(
           event);
 
+  // TODO(crbug.com/1324667): Ensure that autoscroll handles mouse_press_node_
+  // removal correctly, allowing scrolling the still attached ancestor.
   mouse_down_may_start_autoscroll_ =
       frame_->GetEventHandler()
           .GetSelectionController()
diff --git a/third_party/blink/renderer/core/input/mouse_event_manager_test.cc b/third_party/blink/renderer/core/input/mouse_event_manager_test.cc
new file mode 100644
index 0000000..9f23d19
--- /dev/null
+++ b/third_party/blink/renderer/core/input/mouse_event_manager_test.cc
@@ -0,0 +1,101 @@
+// Copyright 2022 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 "third_party/blink/renderer/core/input/mouse_event_manager.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/input/web_keyboard_event.h"
+#include "third_party/blink/public/common/input/web_mouse_event.h"
+#include "third_party/blink/renderer/core/events/keyboard_event.h"
+#include "third_party/blink/renderer/core/input/event_handler.h"
+#include "third_party/blink/renderer/core/scroll/scroll_animator.h"
+#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
+#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
+#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
+#include "third_party/blink/renderer/platform/keyboard_codes.h"
+
+namespace blink {
+
+namespace {
+// Long enough to ensure scroll animation should be complete.
+const double kScrollAnimationDuration = 100.0;
+}  // namespace
+
+class MouseEventManagerTest : public SimTest {
+ protected:
+  EventHandler& GetEventHandler() {
+    return GetDocument().GetFrame()->GetEventHandler();
+  }
+
+  WebMouseEvent CreateTestMouseEvent(WebInputEvent::Type type,
+                                     const gfx::PointF& coordinates) {
+    WebMouseEvent event(type, coordinates, coordinates,
+                        WebPointerProperties::Button::kLeft, 0,
+                        WebInputEvent::kLeftButtonDown,
+                        WebInputEvent::GetStaticTimeStampForTests());
+    event.SetFrameScale(1);
+    return event;
+  }
+
+  void SendKeyDown(int key) {
+    WebKeyboardEvent web_event = {WebInputEvent::Type::kRawKeyDown,
+                                  WebInputEvent::kNoModifiers,
+                                  WebInputEvent::GetStaticTimeStampForTests()};
+    web_event.windows_key_code = key;
+    KeyboardEvent* event = KeyboardEvent::Create(web_event, nullptr);
+    event->SetTarget(&GetDocument());
+    GetDocument().GetFrame()->GetEventHandler().DefaultKeyboardEventHandler(
+        event);
+  }
+};
+
+TEST_F(MouseEventManagerTest, MousePressNodeRemoved) {
+  SimRequest request("https://example.com/test.html", "text/html");
+  LoadURL("https://example.com/test.html");
+  request.Complete(R"HTML(
+      <!DOCTYPE html>
+      <style>
+        #scroller {
+          overflow: auto;
+          height: 100px;
+        }
+        #target {
+          width: 100px;
+          height: 100px;
+          background: green;
+        }
+        .spacer, body {
+          height: 200vh;
+        }
+      </style>
+      <body>
+        <div id="scroller">
+          <div id="target"></div>
+          <div class="spacer"></div>
+        </div>
+      </body>
+      )HTML");
+  Compositor().BeginFrame();
+  EXPECT_FLOAT_EQ(GetDocument().getElementById("scroller")->scrollTop(), 0.0);
+
+  // Click on the target node to set the mouse_press_node_.
+  GetEventHandler().HandleMousePressEvent(CreateTestMouseEvent(
+      WebInputEvent::Type::kMouseDown, gfx::PointF(50, 50)));
+
+  // Now remove this node.
+  GetDocument().getElementById("target")->remove();
+  Compositor().BeginFrame();
+
+  // Now press the down key. This should still scroll the nested scroller as it
+  // was still the scroller that was clicked in.
+  SendKeyDown(VKEY_DOWN);
+  Compositor().ResetLastFrameTime();
+  // Start scroll animation.
+  Compositor().BeginFrame();
+  // Jump to end of scroll animation.
+  Compositor().BeginFrame(kScrollAnimationDuration);
+  EXPECT_GT(GetDocument().getElementById("scroller")->scrollTop(), 0.0);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/webgpu/dawn_object.cc b/third_party/blink/renderer/modules/webgpu/dawn_object.cc
index 021f530..349c011 100644
--- a/third_party/blink/renderer/modules/webgpu/dawn_object.cc
+++ b/third_party/blink/renderer/modules/webgpu/dawn_object.cc
@@ -26,35 +26,11 @@
 }
 
 void DawnObjectBase::EnsureFlush() {
-  bool needs_flush = false;
-  auto context_provider = GetContextProviderWeakPtr();
-  if (UNLIKELY(!context_provider))
-    return;
-  context_provider->ContextProvider()->WebGPUInterface()->EnsureAwaitingFlush(
-      &needs_flush);
-  if (!needs_flush) {
-    // We've already enqueued a task to flush, or the command buffer
-    // is empty. Do nothing.
-    return;
-  }
-  Microtask::EnqueueMicrotask(WTF::Bind(
-      [](scoped_refptr<DawnControlClientHolder> dawn_control_client) {
-        if (auto context_provider =
-                dawn_control_client->GetContextProviderWeakPtr()) {
-          context_provider->ContextProvider()
-              ->WebGPUInterface()
-              ->FlushAwaitingCommands();
-        }
-      },
-      dawn_control_client_));
+  dawn_control_client_->EnsureFlush();
 }
 
-// Flush commands up until now on this object's parent device immediately.
 void DawnObjectBase::FlushNow() {
-  auto context_provider = GetContextProviderWeakPtr();
-  if (LIKELY(context_provider)) {
-    context_provider->ContextProvider()->WebGPUInterface()->FlushCommands();
-  }
+  dawn_control_client_->Flush();
 }
 
 DawnObjectImpl::DawnObjectImpl(GPUDevice* device)
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_buffer.cc b/third_party/blink/renderer/modules/webgpu/gpu_buffer.cc
index 264791c5..125b391 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_buffer.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_buffer.cc
@@ -20,7 +20,7 @@
 #include "third_party/blink/renderer/modules/webgpu/gpu_adapter.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_device.h"
 #include "third_party/blink/renderer/modules/webgpu/gpu_queue.h"
-#include "third_party/blink/renderer/platform/graphics/gpu/dawn_callback.h"
+#include "third_party/blink/renderer/platform/graphics/gpu/webgpu_callback.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 #include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
@@ -239,7 +239,7 @@
 
   // And send the command, leaving remaining validation to Dawn.
   auto* callback =
-      BindDawnOnceCallback(&GPUBuffer::OnMapAsyncCallback, WrapPersistent(this),
+      BindWGPUOnceCallback(&GPUBuffer::OnMapAsyncCallback, WrapPersistent(this),
                            WrapPersistent(resolver));
 
   GetProcs().bufferMapAsync(GetHandle(), mode, map_offset, map_size,
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_device.cc b/third_party/blink/renderer/modules/webgpu/gpu_device.cc
index 029b297..37d17291 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_device.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_device.cc
@@ -71,9 +71,9 @@
           this,
           GetProcs().deviceGetQueue(GetHandle()))),
       lost_property_(MakeGarbageCollected<LostProperty>(execution_context)),
-      error_callback_(BindDawnRepeatingCallback(&GPUDevice::OnUncapturedError,
+      error_callback_(BindWGPURepeatingCallback(&GPUDevice::OnUncapturedError,
                                                 WrapWeakPersistent(this))),
-      logging_callback_(BindDawnRepeatingCallback(&GPUDevice::OnLogging,
+      logging_callback_(BindWGPURepeatingCallback(&GPUDevice::OnLogging,
                                                   WrapWeakPersistent(this))),
       // Note: This is a *repeating* callback even though we expect it to only
       // be called once. This is because it may be called *zero* times.
@@ -81,7 +81,7 @@
       // allocation so it can be appropriately freed on destruction. Thus, the
       // callback should not be a OnceCallback which self-deletes after it is
       // called.
-      lost_callback_(BindDawnRepeatingCallback(&GPUDevice::OnDeviceLostError,
+      lost_callback_(BindWGPURepeatingCallback(&GPUDevice::OnDeviceLostError,
                                                WrapWeakPersistent(this))) {
   DCHECK(dawn_device);
   DCHECK(limits);
@@ -368,7 +368,7 @@
     resolver->Reject(exception_state);
   } else {
     auto* callback =
-        BindDawnOnceCallback(&GPUDevice::OnCreateRenderPipelineAsyncCallback,
+        BindWGPUOnceCallback(&GPUDevice::OnCreateRenderPipelineAsyncCallback,
                              WrapPersistent(this), WrapPersistent(resolver));
     GetProcs().deviceCreateRenderPipelineAsync(
         GetHandle(), &dawn_desc_info.dawn_desc, callback->UnboundCallback(),
@@ -393,7 +393,7 @@
       AsDawnType(this, descriptor, &label, &computeStageDescriptor);
 
   auto* callback =
-      BindDawnOnceCallback(&GPUDevice::OnCreateComputePipelineAsyncCallback,
+      BindWGPUOnceCallback(&GPUDevice::OnCreateComputePipelineAsyncCallback,
                            WrapPersistent(this), WrapPersistent(resolver));
   GetProcs().deviceCreateComputePipelineAsync(GetHandle(), &dawn_desc,
                                               callback->UnboundCallback(),
@@ -430,7 +430,7 @@
   ScriptPromise promise = resolver->Promise();
 
   auto* callback =
-      BindDawnOnceCallback(&GPUDevice::OnPopErrorScopeCallback,
+      BindWGPUOnceCallback(&GPUDevice::OnPopErrorScopeCallback,
                            WrapPersistent(this), WrapPersistent(resolver));
 
   GetProcs().devicePopErrorScope(GetHandle(), callback->UnboundCallback(),
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_device.h b/third_party/blink/renderer/modules/webgpu/gpu_device.h
index 38c758ca..ba2b384 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_device.h
+++ b/third_party/blink/renderer/modules/webgpu/gpu_device.h
@@ -11,7 +11,7 @@
 #include "third_party/blink/renderer/core/dom/events/event_target.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/modules/webgpu/dawn_object.h"
-#include "third_party/blink/renderer/platform/graphics/gpu/dawn_callback.h"
+#include "third_party/blink/renderer/platform/graphics/gpu/webgpu_callback.h"
 #include "third_party/blink/renderer/platform/heap/garbage_collected.h"
 
 namespace blink {
@@ -174,16 +174,16 @@
   Member<GPUSupportedLimits> limits_;
   Member<GPUQueue> queue_;
   Member<LostProperty> lost_property_;
-  std::unique_ptr<DawnRepeatingCallback<void(WGPUErrorType, const char*)>>
+  std::unique_ptr<WGPURepeatingCallback<void(WGPUErrorType, const char*)>>
       error_callback_;
-  std::unique_ptr<DawnRepeatingCallback<void(WGPULoggingType, const char*)>>
+  std::unique_ptr<WGPURepeatingCallback<void(WGPULoggingType, const char*)>>
       logging_callback_;
   // lost_callback_ is stored as a unique_ptr since it may never be called.
   // We need to be sure to free it on deletion of the device.
   // Inside OnDeviceLostError we'll release the unique_ptr to avoid a double
   // free.
   std::unique_ptr<
-      DawnRepeatingCallback<void(WGPUDeviceLostReason, const char*)>>
+      WGPURepeatingCallback<void(WGPUDeviceLostReason, const char*)>>
       lost_callback_;
 
   static constexpr int kMaxAllowedConsoleWarnings = 500;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_queue.cc b/third_party/blink/renderer/modules/webgpu/gpu_queue.cc
index 8be8b1c8..eb22d92 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_queue.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_queue.cc
@@ -260,7 +260,7 @@
   ScriptPromise promise = resolver->Promise();
 
   auto* callback =
-      BindDawnOnceCallback(&GPUQueue::OnWorkDoneCallback, WrapPersistent(this),
+      BindWGPUOnceCallback(&GPUQueue::OnWorkDoneCallback, WrapPersistent(this),
                            WrapPersistent(resolver));
 
   GetProcs().queueOnSubmittedWorkDone(
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_shader_module.cc b/third_party/blink/renderer/modules/webgpu/gpu_shader_module.cc
index 984d850..2aedc42 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_shader_module.cc
+++ b/third_party/blink/renderer/modules/webgpu/gpu_shader_module.cc
@@ -16,7 +16,7 @@
 #include "third_party/blink/renderer/modules/webgpu/gpu_device.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
-#include "third_party/blink/renderer/platform/graphics/gpu/dawn_callback.h"
+#include "third_party/blink/renderer/platform/graphics/gpu/webgpu_callback.h"
 
 namespace blink {
 
@@ -106,7 +106,7 @@
   ScriptPromise promise = resolver->Promise();
 
   auto* callback =
-      BindDawnOnceCallback(&GPUShaderModule::OnCompilationInfoCallback,
+      BindWGPUOnceCallback(&GPUShaderModule::OnCompilationInfoCallback,
                            WrapPersistent(this), WrapPersistent(resolver));
 
   GetProcs().shaderModuleGetCompilationInfo(
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 8ba1bb0..5ef9ab2 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -890,7 +890,6 @@
     "graphics/filters/spot_light_source.h",
     "graphics/generated_image.cc",
     "graphics/generated_image.h",
-    "graphics/gpu/dawn_callback.h",
     "graphics/gpu/dawn_control_client_holder.cc",
     "graphics/gpu/dawn_control_client_holder.h",
     "graphics/gpu/drawing_buffer.cc",
@@ -907,6 +906,7 @@
     "graphics/gpu/shared_gpu_context.h",
     "graphics/gpu/webgl_image_conversion.cc",
     "graphics/gpu/webgl_image_conversion.h",
+    "graphics/gpu/webgpu_callback.h",
     "graphics/gpu/webgpu_mailbox_texture.cc",
     "graphics/gpu/webgpu_mailbox_texture.h",
     "graphics/gpu/webgpu_resource_provider_cache.cc",
@@ -1645,6 +1645,7 @@
     "//gin",
     "//gpu:gpu",
     "//gpu/command_buffer/client:webgpu_interface",
+    "//gpu/webgpu:common",
     "//media",
     "//media/capture:capture_switches",
     "//media/capture/mojom:video_capture",
diff --git a/third_party/blink/renderer/platform/graphics/gpu/DEPS b/third_party/blink/renderer/platform/graphics/gpu/DEPS
index 3238ca1..a2e8e5d 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/DEPS
+++ b/third_party/blink/renderer/platform/graphics/gpu/DEPS
@@ -13,6 +13,8 @@
     "+gpu/command_buffer/common/shared_image_usage.h",
     "+gpu/config/gpu_driver_bug_workaround_type.h",
     "+gpu/config/gpu_feature_info.h",
+    "+gpu/webgpu/callback.h",
+    "+third_party/blink/renderer/platform/bindings/microtask.h",
     "+ui/gfx/gpu_fence.h",
     "+ui/gl/gpu_preference.h",
 ]
diff --git a/third_party/blink/renderer/platform/graphics/gpu/dawn_callback.h b/third_party/blink/renderer/platform/graphics/gpu/dawn_callback.h
deleted file mode 100644
index 39f74edd..0000000
--- a/third_party/blink/renderer/platform/graphics/gpu/dawn_callback.h
+++ /dev/null
@@ -1,153 +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 THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_DAWN_CALLBACK_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_DAWN_CALLBACK_H_
-
-#include <memory>
-
-#include "third_party/blink/renderer/platform/wtf/functional.h"
-
-namespace blink {
-
-// DawnCallback<Callback> is a heap-allocated version of
-// base::OnceCallback or base::RepeatingCallback.
-// It is allocated on the heap so that it can be reinterpret_cast to/from
-// void* and passed to Dawn C callbacks.
-//
-// Example:
-//   DawnOnceCallback<F>* callback =
-//     BindDawnOnceCallback(func, arg1);
-//
-//   // |someDawnFunction| expects callback function with arguments:
-//   //    Args... args, void* userdata.
-//   // When it is called, it will forward to func(arg1, args...).
-//   GetProcs().someDawnFunction(
-//     callback->UnboundCallback(), callback->AsUserdata());
-template <typename Callback>
-class DawnCallbackBase;
-
-template <typename Callback>
-class DawnOnceCallback;
-
-template <typename Callback>
-class DawnRepeatingCallback;
-
-template <template <typename> class BaseCallbackTemplate,
-          typename R,
-          typename... Args>
-class DawnCallbackBase<BaseCallbackTemplate<R(Args...)>> {
-  using BaseCallback = BaseCallbackTemplate<R(Args...)>;
-
-  static constexpr bool is_once_callback =
-      std::is_same<BaseCallback, base::OnceCallback<R(Args...)>>::value;
-  static constexpr bool is_repeating_callback =
-      std::is_same<BaseCallback, base::RepeatingCallback<R(Args...)>>::value;
-  static_assert(
-      is_once_callback || is_repeating_callback,
-      "Callback must be base::OnceCallback or base::RepeatingCallback");
-
- public:
-  explicit DawnCallbackBase(BaseCallback callback)
-      : callback_(std::move(callback)) {}
-
-  void* AsUserdata() { return static_cast<void*>(this); }
-
- protected:
-  using UnboundCallbackFunction = R (*)(Args..., void*);
-
-  static DawnCallbackBase* FromUserdata(void* userdata) {
-    return static_cast<DawnCallbackBase*>(userdata);
-  }
-
-  R Run(Args... args) && {
-    static_assert(
-        is_once_callback,
-        "Run on a moved receiver must only be called on a once callback.");
-    return std::move(callback_).Run(std::forward<Args>(args)...);
-  }
-
-  R Run(Args... args) const& {
-    static_assert(is_repeating_callback,
-                  "Run on a unmoved receiver must only be called on a "
-                  "repeating callback.");
-    return callback_.Run(std::forward<Args>(args)...);
-  }
-
- private:
-  BaseCallback callback_;
-};
-
-template <typename R, typename... Args>
-class DawnOnceCallback<R(Args...)>
-    : public DawnCallbackBase<base::OnceCallback<R(Args...)>> {
-  using BaseCallback = base::OnceCallback<R(Args...)>;
-
- public:
-  using DawnCallbackBase<BaseCallback>::DawnCallbackBase;
-
-  typename DawnCallbackBase<BaseCallback>::UnboundCallbackFunction
-  UnboundCallback() {
-    return CallUnboundOnceCallback;
-  }
-
- private:
-  static R CallUnboundOnceCallback(Args... args, void* handle) {
-    // After this non-repeating callback is run, it should delete itself.
-    auto callback =
-        std::unique_ptr<DawnOnceCallback>(static_cast<DawnOnceCallback*>(
-            DawnCallbackBase<BaseCallback>::FromUserdata(handle)));
-    return std::move(*callback).Run(std::forward<Args>(args)...);
-  }
-};
-
-template <typename R, typename... Args>
-class DawnRepeatingCallback<R(Args...)>
-    : public DawnCallbackBase<base::RepeatingCallback<R(Args...)>> {
-  using BaseCallback = base::RepeatingCallback<R(Args...)>;
-
- public:
-  using DawnCallbackBase<BaseCallback>::DawnCallbackBase;
-
-  typename DawnCallbackBase<BaseCallback>::UnboundCallbackFunction
-  UnboundCallback() {
-    return CallUnboundRepeatingCallback;
-  }
-
- private:
-  static R CallUnboundRepeatingCallback(Args... args, void* handle) {
-    return static_cast<DawnRepeatingCallback*>(
-               DawnCallbackBase<BaseCallback>::FromUserdata(handle))
-        ->Run(std::forward<Args>(args)...);
-  }
-};
-
-template <typename FunctionType, typename... BoundParameters>
-auto BindDawnOnceCallback(FunctionType&& function,
-                          BoundParameters&&... bound_parameters) {
-  static constexpr bool is_method =
-      base::internal::MakeFunctorTraits<FunctionType>::is_method;
-  static constexpr bool is_weak_method =
-      base::internal::IsWeakMethod<is_method, BoundParameters...>();
-  static_assert(!is_weak_method,
-                "BindDawnOnceCallback cannot be used with weak methods");
-
-  auto cb = WTF::Bind(std::forward<FunctionType>(function),
-                      std::forward<BoundParameters>(bound_parameters)...);
-  return new DawnOnceCallback<typename decltype(cb)::RunType>(std::move(cb));
-}
-
-template <typename FunctionType, typename... BoundParameters>
-auto BindDawnRepeatingCallback(FunctionType&& function,
-                               BoundParameters&&... bound_parameters) {
-  auto cb =
-      WTF::BindRepeating(std::forward<FunctionType>(function),
-                         std::forward<BoundParameters>(bound_parameters)...);
-  return std::make_unique<
-      DawnRepeatingCallback<typename decltype(cb)::RunType>>(std::move(cb));
-}
-
-}  // namespace blink
-
-#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_DAWN_CALLBACK_H_
diff --git a/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.cc b/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.cc
index be5b392f..75deedcd 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.h"
 
 #include "base/check.h"
+#include "third_party/blink/renderer/platform/bindings/microtask.h"
 #include "third_party/blink/renderer/platform/graphics/gpu/webgpu_resource_provider_cache.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 
@@ -79,4 +80,34 @@
       info, is_origin_top_left);
 }
 
+void DawnControlClientHolder::Flush() {
+  auto context_provider = GetContextProviderWeakPtr();
+  if (LIKELY(context_provider)) {
+    context_provider->ContextProvider()->WebGPUInterface()->FlushCommands();
+  }
+}
+
+void DawnControlClientHolder::EnsureFlush() {
+  auto context_provider = GetContextProviderWeakPtr();
+  if (UNLIKELY(!context_provider))
+    return;
+  if (!context_provider->ContextProvider()
+           ->WebGPUInterface()
+           ->EnsureAwaitingFlush()) {
+    // We've already enqueued a task to flush, or the command buffer
+    // is empty. Do nothing.
+    return;
+  }
+  Microtask::EnqueueMicrotask(WTF::Bind(
+      [](scoped_refptr<DawnControlClientHolder> dawn_control_client) {
+        if (auto context_provider =
+                dawn_control_client->GetContextProviderWeakPtr()) {
+          context_provider->ContextProvider()
+              ->WebGPUInterface()
+              ->FlushAwaitingCommands();
+        }
+      },
+      scoped_refptr<DawnControlClientHolder>(this)));
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.h b/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.h
index 55003889..0ec3d10 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.h
+++ b/third_party/blink/renderer/platform/graphics/gpu/dawn_control_client_holder.h
@@ -52,6 +52,11 @@
       const SkImageInfo& info,
       bool is_origin_top_left);
 
+  // Flush commands on this client immediately.
+  void Flush();
+  // Ensure commands on this client are flushed by the end of the task.
+  void EnsureFlush();
+
  private:
   friend class RefCounted<DawnControlClientHolder>;
   ~DawnControlClientHolder();
diff --git a/third_party/blink/renderer/platform/graphics/gpu/webgpu_callback.h b/third_party/blink/renderer/platform/graphics/gpu/webgpu_callback.h
new file mode 100644
index 0000000..1695a919
--- /dev/null
+++ b/third_party/blink/renderer/platform/graphics/gpu/webgpu_callback.h
@@ -0,0 +1,22 @@
+// 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 THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_WEBGPU_CALLBACK_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_WEBGPU_CALLBACK_H_
+
+#include "gpu/webgpu/callback.h"
+
+namespace blink {
+
+// Alias these into the blink namespace as the gpu namespace is typically
+// disallowed from being used directly outside of
+// blink/renderer/platform/graphics/gpu.
+using gpu::webgpu::BindWGPUOnceCallback;
+using gpu::webgpu::BindWGPURepeatingCallback;
+using gpu::webgpu::WGPUOnceCallback;
+using gpu::webgpu::WGPURepeatingCallback;
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_WEBGPU_CALLBACK_H_
diff --git a/third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder_test.cc b/third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder_test.cc
index c7c5f04..a687044 100644
--- a/third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder_test.cc
+++ b/third_party/blink/renderer/platform/image-decoders/jxl/jxl_image_decoder_test.cc
@@ -414,11 +414,11 @@
                 SkColorSetARGB(255, 255, 0, 0),
                 SkColorSetARGB(255, 0, 255, 0),
                 SkColorSetARGB(255, 0, 0, 255),
-                SkColorSetARGB(255, 255, 0, 53),
-                SkColorSetARGB(255, 0, 255, 0),
-                SkColorSetARGB(255, 0, 60, 255),
+                SkColorSetARGB(255, 225, 0, 36),
+                SkColorSetARGB(255, 0, 191, 0),
+                SkColorSetARGB(255, 0, 41, 190),
                 SkColorSetARGB(255, 255, 255, 255),
-                SkColorSetARGB(255, 248, 248, 248),
+                SkColorSetARGB(255, 181, 181, 181),
                 SkColorSetARGB(255, 0, 0, 0),
             },
             ImageDecoder::AlphaOption::kAlphaPremultiplied,
@@ -430,11 +430,11 @@
                 SkColorSetARGB(255, 255, 0, 0),
                 SkColorSetARGB(255, 0, 255, 0),
                 SkColorSetARGB(255, 0, 0, 255),
-                SkColorSetARGB(255, 255, 0, 55),
-                SkColorSetARGB(255, 0, 255, 0),
-                SkColorSetARGB(255, 0, 55, 255),
+                SkColorSetARGB(255, 226, 0, 37),
+                SkColorSetARGB(255, 0, 191, 0),
+                SkColorSetARGB(255, 0, 37, 195),
                 SkColorSetARGB(255, 255, 255, 255),
-                SkColorSetARGB(255, 250, 246, 255),
+                SkColorSetARGB(255, 182, 180, 189),
                 SkColorSetARGB(255, 0, 0, 0),
             },
             ImageDecoder::AlphaOption::kAlphaPremultiplied,
@@ -446,11 +446,11 @@
                 SkColorSetARGB(128, 255, 0, 0),
                 SkColorSetARGB(128, 0, 255, 0),
                 SkColorSetARGB(128, 0, 0, 255),
-                SkColorSetARGB(128, 255, 0, 54),
-                SkColorSetARGB(128, 0, 255, 0),
-                SkColorSetARGB(128, 0, 60, 255),
+                SkColorSetARGB(128, 225, 0, 36),
+                SkColorSetARGB(128, 0, 191, 0),
+                SkColorSetARGB(128, 0, 42, 189),
                 SkColorSetARGB(128, 255, 255, 255),
-                SkColorSetARGB(128, 249, 249, 249),
+                SkColorSetARGB(128, 181, 181, 181),
                 SkColorSetARGB(128, 0, 0, 0),
             },
             ImageDecoder::AlphaOption::kAlphaPremultiplied,
@@ -462,11 +462,11 @@
                 SkColorSetARGB(128, 255, 0, 0),
                 SkColorSetARGB(128, 0, 255, 0),
                 SkColorSetARGB(128, 0, 0, 255),
-                SkColorSetARGB(128, 255, 0, 54),
-                SkColorSetARGB(128, 0, 255, 0),
-                SkColorSetARGB(128, 0, 54, 255),
+                SkColorSetARGB(128, 225, 0, 38),
+                SkColorSetARGB(128, 0, 191, 0),
+                SkColorSetARGB(128, 0, 38, 195),
                 SkColorSetARGB(128, 255, 255, 255),
-                SkColorSetARGB(128, 249, 247, 255),
+                SkColorSetARGB(128, 181, 179, 189),
                 SkColorSetARGB(128, 0, 0, 0),
             },
             ImageDecoder::AlphaOption::kAlphaPremultiplied,
@@ -494,11 +494,11 @@
                 SkColorSetARGB(255, 255, 0, 0),
                 SkColorSetARGB(255, 0, 255, 0),
                 SkColorSetARGB(255, 0, 0, 255),
-                SkColorSetARGB(255, 255, 108, 129),
-                SkColorSetARGB(255, 0, 255, 116),
-                SkColorSetARGB(255, 128, 133, 255),
+                SkColorSetARGB(255, 185, 61, 75),
+                SkColorSetARGB(255, 0, 161, 66),
+                SkColorSetARGB(255, 74, 77, 160),
                 SkColorSetARGB(255, 255, 255, 255),
-                SkColorSetARGB(255, 255, 253, 255),
+                SkColorSetARGB(255, 159, 151, 154),
                 SkColorSetARGB(255, 0, 0, 0),
             },
             ImageDecoder::AlphaOption::kAlphaPremultiplied,
@@ -526,11 +526,11 @@
                 SkColorSetARGB(128, 255, 0, 0),
                 SkColorSetARGB(128, 0, 255, 0),
                 SkColorSetARGB(128, 0, 0, 255),
-                SkColorSetARGB(128, 255, 108, 129),
-                SkColorSetARGB(128, 0, 255, 116),
-                SkColorSetARGB(128, 128, 133, 255),
+                SkColorSetARGB(128, 185, 62, 76),
+                SkColorSetARGB(128, 0, 161, 66),
+                SkColorSetARGB(128, 74, 78, 159),
                 SkColorSetARGB(128, 255, 255, 255),
-                SkColorSetARGB(128, 255, 253, 255),
+                SkColorSetARGB(128, 159, 151, 153),
                 SkColorSetARGB(128, 0, 0, 0),
             },
             ImageDecoder::AlphaOption::kAlphaPremultiplied,
@@ -565,8 +565,8 @@
           0.73333334922790527, 0.43921568989753723, 1);
   // sRGB as expected, but not an exact match
   TestHDR("/images/resources/jxl/pq_gradient_lossy.jxl",
-          ColorBehavior::TransformToSRGB(), true, -1.26171875, 2.62890625,
-          -0.61572265625, 1);
+          ColorBehavior::TransformToSRGB(), true, -0.9248046875, 1.943359375,
+          -0.4443359375, 1);
 
   // linear sRGB as expected.
   TestHDR("/images/resources/jxl/pq_gradient_lossy.jxl", ColorBehavior::Tag(),
@@ -578,8 +578,8 @@
           ColorBehavior::Ignore(), false, 0.58039218187332153,
           0.73725491762161255, 0.45098039507865906, 1);
   TestHDR("/images/resources/jxl/pq_gradient_lossless.jxl",
-          ColorBehavior::TransformToSRGB(), true, -1.3056640625, 2.662109375,
-          -0.5791015625, 1);
+          ColorBehavior::TransformToSRGB(), true, -0.95751953125, 1.9677734375,
+          -0.416748046875, 1);
   // correct, original PQ values
   TestHDR("/images/resources/jxl/pq_gradient_lossless.jxl",
           ColorBehavior::Tag(), true, 0.58056640625, 0.7373046875,
@@ -599,8 +599,8 @@
           ColorBehavior::Ignore(), false, 0.58039218187332153,
           0.73725491762161255, 0.45098039507865906, 1);
   TestHDR("/images/resources/jxl/pq_gradient_icc_lossless.jxl",
-          ColorBehavior::TransformToSRGB(), true, -1.3056640625, 2.662109375,
-          -0.5791015625, 1);
+          ColorBehavior::TransformToSRGB(), true, -0.95751953125, 1.9677734375,
+          -0.416748046875, 1);
   TestHDR("/images/resources/jxl/pq_gradient_icc_lossless.jxl",
           ColorBehavior::Tag(), true, 0.58039218187332153, 0.73725491762161255,
           0.45098039507865906, 1);
diff --git a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter_test.cc b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter_test.cc
index f42ae03..ce2c3a6 100644
--- a/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter_test.cc
+++ b/third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter_test.cc
@@ -32,7 +32,6 @@
 #include "media/video/mock_gpu_video_accelerator_factories.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_adapter.h"
 #include "third_party/blink/renderer/platform/peerconnection/rtc_video_decoder_stream_adapter.h"
 #include "third_party/webrtc/api/video_codecs/video_codec.h"
 #include "third_party/webrtc/api/video_codecs/vp9_profile.h"
@@ -401,8 +400,10 @@
 };
 
 TEST_P(RTCVideoDecoderStreamAdapterTest, Create_UnknownFormat) {
-  auto adapter = RTCVideoDecoderAdapter::Create(
+  auto adapter = RTCVideoDecoderStreamAdapter::Create(
       use_hw_decoders_ ? &gpu_factories_ : nullptr,
+      decoder_factory_->GetWeakPtr(), media_thread_task_runner_,
+      gfx::ColorSpace{},
       webrtc::SdpVideoFormat(
           webrtc::CodecTypeToPayloadString(webrtc::kVideoCodecGeneric)));
   ASSERT_FALSE(adapter);
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 2108cf3..f05f8f5 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -1030,7 +1030,6 @@
       name: "FedCm",
       origin_trial_feature_name: "FedCM",
       origin_trial_allows_third_party: true,
-      origin_trial_os: ["android"],
       status: "test",
     },
     {
diff --git a/third_party/blink/renderer/platform/wtf/allocator/partitions.cc b/third_party/blink/renderer/platform/wtf/allocator/partitions.cc
index 1e0035f6..1017b6c 100644
--- a/third_party/blink/renderer/platform/wtf/allocator/partitions.cc
+++ b/third_party/blink/renderer/platform/wtf/allocator/partitions.cc
@@ -223,7 +223,7 @@
 // static
 void Partitions::DumpMemoryStats(
     bool is_light_dump,
-    base::PartitionStatsDumper* partition_stats_dumper) {
+    partition_alloc::PartitionStatsDumper* partition_stats_dumper) {
   // Object model and rendering partitions are not thread safe and can be
   // accessed only on the main thread.
   DCHECK(IsMainThread());
@@ -241,7 +241,8 @@
 
 namespace {
 
-class LightPartitionStatsDumperImpl : public base::PartitionStatsDumper {
+class LightPartitionStatsDumperImpl
+    : public partition_alloc::PartitionStatsDumper {
  public:
   LightPartitionStatsDumperImpl() : total_active_bytes_(0) {}
 
diff --git a/third_party/blink/renderer/platform/wtf/allocator/partitions.h b/third_party/blink/renderer/platform/wtf/allocator/partitions.h
index 3d8ff11..1a723f9 100644
--- a/third_party/blink/renderer/platform/wtf/allocator/partitions.h
+++ b/third_party/blink/renderer/platform/wtf/allocator/partitions.h
@@ -81,7 +81,8 @@
 
   static size_t TotalActiveBytes();
 
-  static void DumpMemoryStats(bool is_light_dump, base::PartitionStatsDumper*);
+  static void DumpMemoryStats(bool is_light_dump,
+                              partition_alloc::PartitionStatsDumper*);
 
   static void* PA_MALLOC_FN BufferMalloc(size_t n, const char* type_name)
       PA_MALLOC_ALIGNED;
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 0d890005..fecabf4 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -248,6 +248,10 @@
 crbug.com/1311760 [ Mac ] virtual/oopr-canvas2d/fast/canvas/OffscreenCanvas-2d-gradients-in-worker.html [ Failure ]
 crbug.com/1311760 [ Mac ] virtual/oopr-canvas2d/fast/canvas/OffscreenCanvas-paths-in-worker.html [ Failure ]
 
+# Offscreen canvas tests that are flaky because sometimes the image is not rendered in time
+# See crbug.com/1315650
+crbug.com/1322089 virtual/gpu/fast/canvas/OffscreenCanvas-MessageChannel-transfer.html [ Failure Pass ]
+
 # These tests require shared-storage and fenced-frames
 # Keep this in sync with VirtualTestSuites.
 crbug.com/1218540 wpt_internal/shared_storage/* [ Skip ]
@@ -5753,9 +5757,10 @@
 # Started failing after rolling new version of check-layout-th.js
 css3/flexbox/perpendicular-writing-modes-inside-flex-item.html [ Failure ]
 
-# Sheriff 2021-05-18
-crbug.com/1210658 [ Mac ] virtual/jxl-enabled/images/jxl/jxl-images.html [ Failure Pass ]
-crbug.com/1210658 [ Win ] virtual/jxl-enabled/images/jxl/jxl-images.html [ Failure Pass ]
+# JXL tests fail due to rounding error differences.
+# TODO(https://crbug.com/1274220): Rebaseline all images once new tone mapping
+# lands.
+crbug.com/1210658 virtual/jxl-enabled/images/jxl/jxl-images.html [ Skip ]
 
 # Temporarily disabled to unblock https://crrev.com/c/3099011
 crbug.com/1199701 http/tests/devtools/console/console-big-array.js [ Skip ]
@@ -6839,9 +6844,6 @@
 # Sheriff 2022-04-25
 crbug.com/1319808 virtual/wbn-from-network/external/wpt/web-bundle/subresource-loading/script-reuse-web-bundle-resource.https.tentative.html [ Skip ]
 
-crbug.com/1267538 [ Linux ] http/tests/csspaint/border-color.html [ Failure Pass ]
-crbug.com/1267538 [ Mac ] http/tests/csspaint/border-color.html [ Failure Pass ]
-
 # Sheriff 2022-04-26
 crbug.com/1320140 [ Win ] virtual/fenced-frame-mparch/wpt_internal/fenced_frame/disallowed-navigations.https.html [ Pass Timeout ]
 crbug.com/1320140 [ Win ] virtual/fenced-frame-mparch/wpt_internal/fenced_frame/opaque-ad-sizes.https.html [ Pass Timeout ]
diff --git a/third_party/blink/web_tests/external/Version b/third_party/blink/web_tests/external/Version
index 9da1f39..81828ef0 100644
--- a/third_party/blink/web_tests/external/Version
+++ b/third_party/blink/web_tests/external/Version
@@ -1 +1 @@
-Version: da03d05625361b0d7d2e5185ae64efa79895d775
+Version: 464a7f038e55b17daab1e43beebdab13aa960dd3
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
index 5bb1168..f1bc3e2 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_8.json
@@ -77876,6 +77876,45 @@
        {}
       ]
      ],
+     "border-bottom-width-medium.html": [
+      "ab0d9b8affb9cedc06eec7a759ab9f4aceac2da9",
+      [
+       null,
+       [
+        [
+         "/css/css-backgrounds/reference/border-top-width-3px-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "border-bottom-width-thick.html": [
+      "91f2c4884c0385aa517b1d4ba13fd03e57de0a85",
+      [
+       null,
+       [
+        [
+         "/css/css-backgrounds/reference/border-top-width-5px-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "border-bottom-width-thin.html": [
+      "bbc82f4f14b01abc45fbe9d5d2d2088df4c54c50",
+      [
+       null,
+       [
+        [
+         "/css/css-backgrounds/reference/border-top-width-1px-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "border-image-017.xht": [
       "33492ca78b31e3078c9c63616340672aed356478",
       [
@@ -78531,6 +78570,45 @@
        {}
       ]
      ],
+     "border-left-width-medium.html": [
+      "b7bd80a83388fc5a56f2f1a9ed3c291778f9cd92",
+      [
+       null,
+       [
+        [
+         "/css/css-backgrounds/reference/border-right-width-3px-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "border-left-width-thick.html": [
+      "c4787cac8adfe088d92a68ab9b21da345825b8ca",
+      [
+       null,
+       [
+        [
+         "/css/css-backgrounds/reference/border-right-width-5px-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "border-left-width-thin.html": [
+      "1b8da56a34fd31ce8b9caf3f62041b838f202117",
+      [
+       null,
+       [
+        [
+         "/css/css-backgrounds/reference/border-right-width-1px-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "border-radius-001.xht": [
       "5029dd0f15105294d85612a0f50a6b28caaf7da0",
       [
@@ -78823,6 +78901,45 @@
        {}
       ]
      ],
+     "border-right-width-medium.html": [
+      "47de799e54fb8549136c7e7fb442604f3106ab70",
+      [
+       null,
+       [
+        [
+         "/css/css-backgrounds/reference/border-right-width-3px-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "border-right-width-thick.html": [
+      "af518a79f51f54136fef2c5d7792b06a2b353d42",
+      [
+       null,
+       [
+        [
+         "/css/css-backgrounds/reference/border-right-width-5px-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "border-right-width-thin.html": [
+      "d8483a87ca070473f6c3e1b719ac6228e2b10e4b",
+      [
+       null,
+       [
+        [
+         "/css/css-backgrounds/reference/border-right-width-1px-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "border-top-left-radius-001.xht": [
       "0346518e2161a81fc76008f415c746d3342b210b",
       [
@@ -78979,6 +79096,45 @@
        {}
       ]
      ],
+     "border-top-width-medium.html": [
+      "74c7d943f4ca9500218bcf43b360d25a41283c19",
+      [
+       null,
+       [
+        [
+         "/css/css-backgrounds/reference/border-top-width-3px-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "border-top-width-thick.html": [
+      "f67c5e22ed3dc6f3198af6d450c5a23cd22af8e0",
+      [
+       null,
+       [
+        [
+         "/css/css-backgrounds/reference/border-top-width-5px-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
+     "border-top-width-thin.html": [
+      "38597f5a22d4e5adfb3e68ef83a69bd16614cd38",
+      [
+       null,
+       [
+        [
+         "/css/css-backgrounds/reference/border-top-width-1px-ref.html",
+         "=="
+        ]
+       ],
+       {}
+      ]
+     ],
      "border-width-pixel-snapping-001-a.html": [
       "664e652c2bf277913eff4b15bcebbb4f6c679743",
       [
@@ -259128,6 +259284,30 @@
        "67324c71786b92d4207976284d915f1893b8007d",
        []
       ],
+      "border-right-width-1px-ref.html": [
+       "b725d6ea0f5fffeca926af02a75a4aebb0e78793",
+       []
+      ],
+      "border-right-width-3px-ref.html": [
+       "068da127d49fbb86aff21d6f132baee2d3a61df7",
+       []
+      ],
+      "border-right-width-5px-ref.html": [
+       "29706fa4f558a74c4e46a427e9eaeb9bc3b53933",
+       []
+      ],
+      "border-top-width-1px-ref.html": [
+       "15b194822640f68e0a0c61ad52dff1ea9e3fe6ad",
+       []
+      ],
+      "border-top-width-3px-ref.html": [
+       "3fea5943cfe3d565e5923ad7e7a2799094dffc2e",
+       []
+      ],
+      "border-top-width-5px-ref.html": [
+       "fa6db4a84341bda43cc1010e6296c960e9153066",
+       []
+      ],
       "border-width-pixel-snapping-001-ref.html": [
        "99dd2f0beb2a6a9b5bcf79408d1fbf089b8e15ec",
        []
@@ -296310,7 +296490,7 @@
       []
      ],
      "event-timing-test-utils.js": [
-      "32ff2b23c02f9faa4e90b65b5ef4b473a5ecc691",
+      "c549e7ebf616d62b02ba0a993a11db39bfda0b0b",
       []
      ],
      "slow-image.py": [
@@ -311693,7 +311873,7 @@
          []
         ],
         "script-onerror-insertion-point-2-helper.html": [
-         "7a17398156264413f779f1b49fce3844f4868fdf",
+         "a9ee80026aa671011c9537ab3fc4374ef157a761",
          []
         ],
         "script-onload-insertion-point-helper.html": [
@@ -319346,7 +319526,7 @@
       []
      ],
      "permissions-policy-payment-extension.html": [
-      "a97de5464282d50975f896365c2e961e58eb0f2b",
+      "86f0ea3e48aaa87db94f81843dceb137d8732de6",
       []
      ],
      "permissions-policy-payment.html": [
@@ -322778,7 +322958,7 @@
       []
      ],
      "iframe-enroll.html": [
-      "fa55214aa926f9035786909fa6f9a9303d0bdb17",
+      "0238e39826f3a3ae23d0fd34eb4cec31cc717949",
       []
      ]
     },
@@ -367670,6 +367850,13 @@
        {}
       ]
      ],
+     "border-width-cssom.html": [
+      "a1276e5362ceaf3672d979ba355c2ab9feedb16d",
+      [
+       null,
+       {}
+      ]
+     ],
      "inheritance.sub.html": [
       "01bb8422991ee48da525087d5462c380c5eb8b84",
       [
@@ -396050,6 +396237,13 @@
         {}
        ]
       ],
+      "target-pseudo-in-has.html": [
+       "254fc5eb27f4a5a3863e3bcce99ab1f0a4144af4",
+       [
+        null,
+        {}
+       ]
+      ],
       "user-action-pseudo-classes-in-has.html": [
        "466e8610fdb91a0fc27fd30cf49a7d675bec39a4",
        [
@@ -414800,8 +414994,28 @@
       }
      ]
     ],
+    "interactionid-press-key-as-input.html": [
+     "b1e725b5c6f76d9eb3d413e679bfdeb331d1b0b2",
+     [
+      null,
+      {
+       "testdriver": true,
+       "timeout": "long"
+      }
+     ]
+    ],
+    "interactionid-press-key-no-effect.html": [
+     "ad4dc2595cb852a8718d91dc64ee5717d410adf9",
+     [
+      null,
+      {
+       "testdriver": true,
+       "timeout": "long"
+      }
+     ]
+    ],
     "interactionid-tap.html": [
-     "7ef295a03dab49d4c0483ebfa423568b076fc22b",
+     "abe0b85f89dd69c275bf5a00aa52d084c2331709",
      [
       null,
       {
@@ -472157,8 +472371,15 @@
          {}
         ]
        ],
+       "script-text-modifications-csp.html": [
+        "a9911510661ff71260b2822381983d203a9328be",
+        [
+         null,
+         {}
+        ]
+       ],
        "script-text-modifications.html": [
-        "0ddec6a851fea76910dd97e7e8d42f8d907eef10",
+        "cb54da6995b7fa99e657e76254e567cae0455742",
         [
          null,
          {}
@@ -513071,7 +513292,7 @@
      ]
     ],
     "enrollment-in-iframe.sub.https.html": [
-     "0efc9eb80b3dd4ac63a0906a98aa22bc2e0f16d1",
+     "ad03941f50af2c44346c9f0ce3ad04422427f7cd",
      [
       null,
       {
@@ -535867,7 +536088,7 @@
      },
      "Animation": {
       "cancel.html": [
-       "711a33905a9101d876950dc974b6ce9e4f236ddf",
+       "a7da9755dd5b647e0d6f1a8c0d2cf8cc115ee64d",
        [
         null,
         {}
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-bottom-width-medium.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-bottom-width-medium.html
new file mode 100644
index 0000000..ab0d9b8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-bottom-width-medium.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-bottom-width: medium equals 3px</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-width">
+<link rel="match" href="reference/border-top-width-3px-ref.html">
+<meta name="assert" content="The 'medium' keyword for 'border-bottom-width' is 3px." />
+<style>
+  .red-if-too-thin { height: 3px; background: red; }
+  .cb { position: relative; }
+  .red-if-too-thick { height: 20px; background: red; }
+  .overlap-red-if-too-thick { height: 20px; background: white; position: absolute; top: 3px; width: 100%; }
+  .border-test { border-bottom-style: solid; border-bottom-width: medium; margin-top: -3px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div class=red-if-too-thin></div>
+  <div class=cb>
+    <div class=border-test></div>
+    <div class=red-if-too-thick></div>
+    <div class=overlap-red-if-too-thick></div>
+  </div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-bottom-width-thick.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-bottom-width-thick.html
new file mode 100644
index 0000000..91f2c48
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-bottom-width-thick.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-bottom-width: thick equals 5px</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-width">
+<link rel="match" href="reference/border-top-width-5px-ref.html">
+<meta name="assert" content="The 'thick' keyword for 'border-bottom-width' is 5px." />
+<style>
+  .red-if-too-thin { height: 5px; background: red; }
+  .cb { position: relative; }
+  .red-if-too-thick { height: 20px; background: red; }
+  .overlap-red-if-too-thick { height: 20px; background: white; position: absolute; top: 5px; width: 100%; }
+  .border-test { border-bottom-style: solid; border-bottom-width: thick; margin-top: -5px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div class=red-if-too-thin></div>
+  <div class=cb>
+    <div class=border-test></div>
+    <div class=red-if-too-thick></div>
+    <div class=overlap-red-if-too-thick></div>
+  </div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-bottom-width-thin.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-bottom-width-thin.html
new file mode 100644
index 0000000..bbc82f4f
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-bottom-width-thin.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-bottom-width: thin equals 1px</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-width">
+<link rel="match" href="reference/border-top-width-1px-ref.html">
+<meta name="assert" content="The 'thin' keyword for 'border-bottom-width' is 1px." />
+<style>
+  .red-if-too-thin { height: 1px; background: red; }
+  .cb { position: relative; }
+  .red-if-too-thick { height: 20px; background: red; }
+  .overlap-red-if-too-thick { height: 20px; background: white; position: absolute; top: 1px; width: 100%; }
+  .border-test { border-bottom-style: solid; border-bottom-width: thin; margin-top: -1px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div class=red-if-too-thin></div>
+  <div class=cb>
+    <div class=border-test></div>
+    <div class=red-if-too-thick></div>
+    <div class=overlap-red-if-too-thick></div>
+  </div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-left-width-medium.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-left-width-medium.html
new file mode 100644
index 0000000..b7bd80a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-left-width-medium.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-left-width: medium equals 3px</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-width">
+<link rel="match" href="reference/border-right-width-3px-ref.html">
+<meta name="assert" content="The 'thin' keyword for 'border-left-width' is 3px." />
+<style>
+  div { display: inline-block; height: 100px; }
+  .red-if-too-thin { width: 3px; background: red; }
+  .cb { position: relative; }
+  .red-if-too-thick { width: 20px; background: red; }
+  .overlap-red-if-too-thick { width: 20px; background: white; position: absolute; left: 0; }
+  .border-test { border-left-style: solid; border-left-width: medium; margin-left: -3px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div class=red-if-too-thin></div><!--
+  --><div class=cb><!--
+    --><div class=border-test></div><!--
+    --><div class=red-if-too-thick></div><!--
+    --><div class=overlap-red-if-too-thick></div><!--
+  --></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-left-width-thick.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-left-width-thick.html
new file mode 100644
index 0000000..c4787ca
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-left-width-thick.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-right-width: thick equals 5px</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-width">
+<link rel="match" href="reference/border-right-width-5px-ref.html">
+<meta name="assert" content="The 'thin' keyword for 'border-right-width' is 5px." />
+<style>
+  div { display: inline-block; height: 100px; }
+  .red-if-too-thin { width: 5px; background: red; }
+  .cb { position: relative; }
+  .red-if-too-thick { width: 20px; background: red; }
+  .overlap-red-if-too-thick { width: 20px; background: white; position: absolute; left: 0; }
+  .border-test { border-right-style: solid; border-right-width: thick; margin-left: -5px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div class=red-if-too-thin></div><!--
+  --><div class=cb><!--
+    --><div class=border-test></div><!--
+    --><div class=red-if-too-thick></div><!--
+    --><div class=overlap-red-if-too-thick></div><!--
+  --></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-left-width-thin.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-left-width-thin.html
new file mode 100644
index 0000000..1b8da56a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-left-width-thin.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-left-width: thin equals 1px</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-width">
+<link rel="match" href="reference/border-right-width-1px-ref.html">
+<meta name="assert" content="The 'thin' keyword for 'border-left-width' is 1px." />
+<style>
+  div { display: inline-block; height: 100px; }
+  .red-if-too-thin { width: 1px; background: red; }
+  .cb { position: relative; }
+  .red-if-too-thick { width: 20px; background: red; }
+  .overlap-red-if-too-thick { width: 20px; background: white; position: absolute; left: 0; }
+  .border-test { border-left-style: solid; border-left-width: thin; margin-left: -1px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div class=red-if-too-thin></div><!--
+  --><div class=cb><!--
+    --><div class=border-test></div><!--
+    --><div class=red-if-too-thick></div><!--
+    --><div class=overlap-red-if-too-thick></div><!--
+  --></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-right-width-medium.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-right-width-medium.html
new file mode 100644
index 0000000..47de799
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-right-width-medium.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-right-width: medium equals 3px</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-width">
+<link rel="match" href="reference/border-right-width-3px-ref.html">
+<meta name="assert" content="The 'thin' keyword for 'border-right-width' is 3px." />
+<style>
+  div { display: inline-block; height: 100px; }
+  .red-if-too-thin { width: 3px; background: red; }
+  .cb { position: relative; }
+  .red-if-too-thick { width: 20px; background: red; }
+  .overlap-red-if-too-thick { width: 20px; background: white; position: absolute; left: 0; }
+  .border-test { border-right-style: solid; border-right-width: medium; margin-left: -3px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div class=red-if-too-thin></div><!--
+  --><div class=cb><!--
+    --><div class=border-test></div><!--
+    --><div class=red-if-too-thick></div><!--
+    --><div class=overlap-red-if-too-thick></div><!--
+  --></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-right-width-thick.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-right-width-thick.html
new file mode 100644
index 0000000..af518a7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-right-width-thick.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-right-width: thick equals 5px</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-width">
+<link rel="match" href="reference/border-right-width-5px-ref.html">
+<meta name="assert" content="The 'thick' keyword for 'border-right-width' is 5px." />
+<style>
+  div { display: inline-block; height: 100px; }
+  .red-if-too-thin { width: 5px; background: red; }
+  .cb { position: relative; }
+  .red-if-too-thick { width: 20px; background: red; }
+  .overlap-red-if-too-thick { width: 20px; background: white; position: absolute; left: 0; }
+  .border-test { border-right-style: solid; border-right-width: thick; margin-left: -5px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div class=red-if-too-thin></div><!--
+  --><div class=cb><!--
+    --><div class=border-test></div><!--
+    --><div class=red-if-too-thick></div><!--
+    --><div class=overlap-red-if-too-thick></div><!--
+  --></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-right-width-thin.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-right-width-thin.html
new file mode 100644
index 0000000..d8483a8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-right-width-thin.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-right-width: thin equals 1px</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-width">
+<link rel="match" href="reference/border-right-width-1px-ref.html">
+<meta name="assert" content="The 'thin' keyword for 'border-right-width' is 1px." />
+<style>
+  div { display: inline-block; height: 100px; }
+  .red-if-too-thin { width: 1px; background: red; }
+  .cb { position: relative; }
+  .red-if-too-thick { width: 20px; background: red; }
+  .overlap-red-if-too-thick { width: 20px; background: white; position: absolute; left: 0; }
+  .border-test { border-right-style: solid; border-right-width: thin; margin-left: -1px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div class=red-if-too-thin></div><!--
+  --><div class=cb><!--
+    --><div class=border-test></div><!--
+    --><div class=red-if-too-thick></div><!--
+    --><div class=overlap-red-if-too-thick></div><!--
+  --></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-top-width-medium.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-top-width-medium.html
new file mode 100644
index 0000000..74c7d943
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-top-width-medium.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-top-width: medium equals 3px</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-width">
+<link rel="match" href="reference/border-top-width-3px-ref.html">
+<meta name="assert" content="The 'medium' keyword for 'border-top-width' is 3px." />
+<style>
+  .red-if-too-thin { height: 3px; background: red; }
+  .cb { position: relative; }
+  .red-if-too-thick { height: 20px; background: red; }
+  .overlap-red-if-too-thick { height: 20px; background: white; position: absolute; top: 3px; width: 100%; }
+  .border-test { border-top-style: solid; border-top-width: medium; margin-top: -3px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div class=red-if-too-thin></div>
+  <div class=cb>
+    <div class=border-test></div>
+    <div class=red-if-too-thick></div>
+    <div class=overlap-red-if-too-thick></div>
+  </div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-top-width-thick.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-top-width-thick.html
new file mode 100644
index 0000000..f67c5e2
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-top-width-thick.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-top-width: thick equals 5px</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-width">
+<link rel="match" href="reference/border-top-width-5px-ref.html">
+<meta name="assert" content="The 'thick' keyword for 'border-top-width' is 5px." />
+<style>
+  .red-if-too-thin { height: 5px; background: red; }
+  .cb { position: relative; }
+  .red-if-too-thick { height: 20px; background: red; }
+  .overlap-red-if-too-thick { height: 20px; background: white; position: absolute; top: 5px; width: 100%; }
+  .border-test { border-top-style: solid; border-top-width: thick; margin-top: -5px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div class=red-if-too-thin></div>
+  <div class=cb>
+    <div class=border-test></div>
+    <div class=red-if-too-thick></div>
+    <div class=overlap-red-if-too-thick></div>
+  </div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-top-width-thin.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-top-width-thin.html
new file mode 100644
index 0000000..38597f5a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-top-width-thin.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-top-width: thin equals 1px</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-width">
+<link rel="match" href="reference/border-top-width-1px-ref.html">
+<meta name="assert" content="The 'thin' keyword for 'border-top-width' is 1px." />
+<style>
+  .red-if-too-thin { height: 1px; background: red; }
+  .cb { position: relative; }
+  .red-if-too-thick { height: 20px; background: red; }
+  .overlap-red-if-too-thick { height: 20px; background: white; position: absolute; top: 1px; width: 100%; }
+  .border-test { border-top-style: solid; border-top-width: thin; margin-top: -1px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div class=red-if-too-thin></div>
+  <div class=cb>
+    <div class=border-test></div>
+    <div class=red-if-too-thick></div>
+    <div class=overlap-red-if-too-thick></div>
+  </div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-width-cssom.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-width-cssom.html
new file mode 100644
index 0000000..a1276e53
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/border-width-cssom.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: CSSOM for border-*-width: thin, medium, thick</title>
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-width">
+<meta name="assert" content="getComputedStyle() for 'border-*-width' with values thin, medium, thick, returns 1px, 3px, and 5px, respectively." />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+.thin { border: solid thin; }
+.medium { border: solid medium; }
+.thick { border: solid thick; }
+</style>
+</head>
+<body>
+  <div class=thin data-expected=1px></div>
+  <div class=medium data-expected=3px></div>
+  <div class=thick data-expected=5px></div>
+  <script>
+    let divs = document.querySelectorAll('div');
+    let props = ['border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'];
+    for (let div of divs) {
+      let style = getComputedStyle(div);
+      for (let prop of props) {
+        test(() => {
+          assert_equals(style.getPropertyValue(prop), div.dataset.expected);
+        }, `${prop}: ${div.className} is ${div.dataset.expected}`);
+      }
+    }
+  </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-right-width-1px-ref.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-right-width-1px-ref.html
new file mode 100644
index 0000000..b725d6ea
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-right-width-1px-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-right-width: 1px, reference</title>
+<style>
+  div { display: inline-block; height: 100px; border-left-style: solid; border-left-width: 1px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-right-width-3px-ref.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-right-width-3px-ref.html
new file mode 100644
index 0000000..068da12
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-right-width-3px-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-right-width: 3px, reference</title>
+<style>
+  div { display: inline-block; height: 100px; border-left-style: solid; border-left-width: 3px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-right-width-5px-ref.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-right-width-5px-ref.html
new file mode 100644
index 0000000..29706fa
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-right-width-5px-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-right-width: 5px, reference</title>
+<style>
+  div { display: inline-block; height: 100px; border-left-style: solid; border-left-width: 5px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-top-width-1px-ref.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-top-width-1px-ref.html
new file mode 100644
index 0000000..15b1948
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-top-width-1px-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-top-width: 1px, reference</title>
+<style>
+  div { border-top-style: solid; border-top-width: 1px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-top-width-3px-ref.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-top-width-3px-ref.html
new file mode 100644
index 0000000..3fea594
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-top-width-3px-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-top-width: 3px, reference</title>
+<style>
+  div { border-top-style: solid; border-top-width: 3px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-top-width-5px-ref.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-top-width-5px-ref.html
new file mode 100644
index 0000000..fa6db4a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/reference/border-top-width-5px-ref.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>CSS Borders: border-top-width: 5px, reference</title>
+<style>
+  div { border-top-style: solid; border-top-width: 5px; }
+</style>
+</head>
+<body>
+  <p>There should be a black line below and no red.</p>
+  <div></div>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/target-pseudo-in-has.html b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/target-pseudo-in-has.html
new file mode 100644
index 0000000..254fc5e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/selectors/invalidation/target-pseudo-in-has.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>CSS Selectors Invalidation: target pseudo in :has() argument</title>
+<link rel="help" href="https://drafts.csswg.org/selectors/#relational">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+  #parent1 { color: green; }
+  #parent1:has(:target) { color: yellowgreen; }
+</style>
+<div id="parent1">
+  <div id="fragment">parent color must be yellow green when containing :target</div>
+  <a href="#fragment">link to #fragment</a>
+  <a href="#">link to #</a>
+</div>
+<script>
+  test((t) => {
+    const fragment = document.querySelector("#fragment");
+    const toFragment = document.querySelector(`a[href="#fragment"]`);
+    const toTop = document.querySelector(`a[href="#"]`);
+
+    location.hash = "";
+
+    assert_equals(getComputedStyle(parent1).color, "rgb(0, 128, 0)", "parent should be green");
+
+    toFragment.click();
+
+    assert_true(fragment.matches(":target"));
+    assert_equals(getComputedStyle(parent1).color, "rgb(154, 205, 50)", "parent should be yellowgreen on fragment click");
+
+    toTop.click();
+
+    assert_equals(location.hash, "");
+    assert_equals(getComputedStyle(parent1).color, "rgb(0, 128, 0)", "parent should be green without :target");
+  });
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/event-timing/interactionid-press-key-as-input.html b/third_party/blink/web_tests/external/wpt/event-timing/interactionid-press-key-as-input.html
new file mode 100644
index 0000000..b1e725b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/event-timing/interactionid-press-key-as-input.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<meta charset=utf-8 />
+<meta name="timeout" content="long">
+<title>Event Timing: interactionId-press-key-as-input.</title>
+<textarea id='test'></textarea>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-actions.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<script src=resources/event-timing-test-utils.js></script>
+
+<script>
+  let observedEntries = [];
+  let map = new Map();
+          const events = ['keydown', 'keyup'];
+
+  async_test(function (t) {
+    assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.');
+
+    new PerformanceObserver(t.step_func(entryList => {
+      observedEntries = observedEntries.concat(entryList.getEntries().filter(filterAndAddToMap(events, map)));
+
+      if (observedEntries.length < 2)
+        return;
+
+      events.forEach(e => assert_greater_than(map.get(e), 0, 'Should have a non-trivial interactionId for ' + e + ' event'));
+      assert_equals(map.get('keydown'), map.get('keyup'), 'The keydown and the keyup should have the same interactionId');
+      assert_equals('t', document.getElementById('test').value);
+      t.done();
+    })).observe({ type: "event" });
+
+    addListenersAndPress(document.getElementById('test'), 't', events);
+  }, "Event Timing: compare event timing interactionId for key press as input.");
+</script>
+
+</html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/event-timing/interactionid-press-key-no-effect.html b/third_party/blink/web_tests/external/wpt/event-timing/interactionid-press-key-no-effect.html
new file mode 100644
index 0000000..ad4dc259
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/event-timing/interactionid-press-key-no-effect.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<meta charset=utf-8 />
+<meta name="timeout" content="long">
+<title>Event Timing: interactionId-press-key-no-effect.</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-actions.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<script src=resources/event-timing-test-utils.js></script>
+
+<body>
+  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec tempus
+    lacinia nisi, eget tempor orci. Nullam congue pharetra arcu, et consectetur
+    massa mollis tincidunt. Quisque odio sapien, viverra finibus lectus ac,
+    consectetur ornare quam. In hac habitasse platea dictumst. Morbi cursus est
+    odio, non fermentum ligula posuere vitae. Sed ullamcorper convallis rhoncus.
+    In condimentum neque nec metus hendrerit, et cursus ipsum aliquet.
+  </p>
+</body>
+<script>
+  let observedEntries = [];
+  let map = new Map();
+        const events = ['keydown', 'keyup'];
+
+  async_test(function (t) {
+    assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.');
+
+    new PerformanceObserver(t.step_func(entryList => {
+      observedEntries = observedEntries.concat(entryList.getEntries().filter(filterAndAddToMap(events, map)));
+
+      if (observedEntries.length < 2)
+        return;
+
+      events.forEach(e => assert_greater_than(map.get(e), 0, 'Should have a non-trivial interactionId for ' + e + ' event'));
+      assert_equals(map.get('keydown'), map.get('keyup'), 'The keydown and the keyup should have the same interactionId');
+      t.done();
+    })).observe({ type: "event" });
+
+    addListenersAndPress(document.body, 't', events);
+  }, "Event Timing: compare event timing interactionId for key press with no effect.");
+</script>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/event-timing/interactionid-tap.html b/third_party/blink/web_tests/external/wpt/event-timing/interactionid-tap.html
index 7ef295a0..abe0b85 100644
--- a/third_party/blink/web_tests/external/wpt/event-timing/interactionid-tap.html
+++ b/third_party/blink/web_tests/external/wpt/event-timing/interactionid-tap.html
@@ -14,50 +14,24 @@
 <script>
   let observedEntries = [];
   let map = new Map();
-
-  function eventsForCheck(entry) {
-    if (entry.name === 'pointerdown' || entry.name === 'pointerup') {
-      map.set(entry.name, entry.interactionId);
-      return true;
-    }
-    return false;
-  }
-
-  async function tapOn(element_id) {
-    const element = document.getElementById(element_id);
-
-    const clickHandler = (e) => {
-      mainThreadBusy(200);
-    };
-
-    element.addEventListener("pointerdown", clickHandler);
-    element.addEventListener("pointerup", clickHandler);
-
-    let actions = new test_driver.Actions()
-      .addPointer("tapPointer", "touch")
-      .pointerMove(0, 0, { origin: element })
-      .pointerDown()
-      .pointerUp();
-    await actions.send();
-  }
+      const events = ['pointerdown', 'pointerup'];
 
   async_test(function (t) {
     assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.');
 
     new PerformanceObserver(t.step_func(entryList => {
-      observedEntries = observedEntries.concat(entryList.getEntries().filter(eventsForCheck));
+      observedEntries = observedEntries.concat(entryList.getEntries().filter(filterAndAddToMap(events, map)));
 
       if (observedEntries.length < 2)
         return;
 
-      assert_greater_than(map.get('pointerdown'), 0, 'Should have a non-trivial interactionId');
-      assert_greater_than(map.get('pointerup'), 0, 'Should have a non-trivial interactionId');
-      assert_equals(map.get('pointerdown'), map.get('pointerup'), 'Pointerdown and pointerup should have the same interactionId');
+      events.forEach(e => assert_greater_than(map.get(e), 0, 'Should have a non-trivial interactionId for ' + e + ' event'));
+      assert_equals(map.get('pointerdown'), map.get('pointerup'), 'The pointerdown and the pointerup should have the same interactionId');
       t.done();
     })).observe({ type: "event" });
 
-    tapOn('test');
-  }, "Event Timing: compare event timing interactionId.");
+    addListenersAndTap(document.getElementById('test'), events);
+  }, "Event Timing: compare event timing interactionId for tap.");
 </script>
 
 </html>
\ No newline at end of file
diff --git a/third_party/blink/web_tests/external/wpt/event-timing/resources/event-timing-test-utils.js b/third_party/blink/web_tests/external/wpt/event-timing/resources/event-timing-test-utils.js
index 32ff2b23..c549e7e 100644
--- a/third_party/blink/web_tests/external/wpt/event-timing/resources/event-timing-test-utils.js
+++ b/third_party/blink/web_tests/external/wpt/event-timing/resources/event-timing-test-utils.js
@@ -275,3 +275,51 @@
 
   await observerPromise;
 }
+
+function addListeners(element, events) {
+  const clickHandler = (e) => {
+    mainThreadBusy(200);
+  };
+  events.forEach(e => { element.addEventListener(e, clickHandler); });
+}
+
+// The testdriver.js, testdriver-vendor.js and testdriver-actions.js need to be
+// included to use this function.
+async function tap(element) {
+  let actions = new test_driver.Actions()
+    .addPointer("tapPointer", "touch")
+    .pointerMove(0, 0, { origin: element })
+    .pointerDown()
+    .pointerUp();
+  await actions.send();
+}
+
+// The testdriver.js, testdriver-vendor.js need to be included to use this
+// function.
+async function pressKey(element, key) {
+  await test_driver.send_keys(element, key);
+}
+
+// The testdriver.js, testdriver-vendor.js and testdriver-actions.js need to be
+// included to use this function.
+async function addListenersAndTap(element, events) {
+  addListeners(element, events);
+  tap(element);
+}
+
+// The testdriver.js, testdriver-vendor.js need to be included to use this
+// function.
+async function addListenersAndPress(element, key, events) {
+  addListeners(element, events);
+  pressKey(element, key);
+}
+
+function filterAndAddToMap(events, map) {
+  return function (entry) {
+    if (events.includes(entry.name)) {
+      map.set(entry.name, entry.interactionId);
+      return true;
+    }
+    return false;
+  }
+}
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/script-text-modifications-csp.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/script-text-modifications-csp.html
new file mode 100644
index 0000000..a991151
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/script-text-modifications-csp.html
@@ -0,0 +1,52 @@
+<!doctype html>
+<head>
+<meta charset=utf-8>
+<title>Modify HTMLScriptElement's text after #prepare-a-script that violates CSP</title>
+<link rel=help href="https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<meta http-equiv="content-security-policy" content="script-src
+  'nonce-allow'
+  'sha256-2+5xh6b9uuIi4GaJtmHWtgR2nwRXJpBtMY4nVaOBpfc='
+">
+<!-- The hash is that of the original content of `script0`. -->
+
+<script nonce="allow">
+window.t = async_test("Modify inline script element's text " +
+                   "after prepare-a-script before evaluation (CSP)");
+
+const updatedText =
+  't.unreached_func("CSP check was done against the original text but the updated text was evaluated")();';
+
+function changeScriptText() {
+  document.querySelector('#script0').textContent = updatedText;
+}
+
+t.step_timeout(changeScriptText, 500);
+</script>
+
+<!-- This is "a style sheet that is blocking scripts" and thus ... -->
+<link rel="stylesheet" href="/common/slow.py?pipe=trickle(d1)"></link>
+
+<!-- This inline script becomes a parser-blocking script, and thus
+the step_timeout is evaluated after script0 is inserted into DOM,
+prepare-a-script'ed, but before its evaluation. -->
+<script id="script0">
+t.step(() => {
+    // When this is evaluated after the stylesheet is loaded,
+    // script0's textContent is modified by the async script above,
+    // but the evaluated script is still the original script here,
+    // not what is overwritten, because "child text content" is taken in
+    // #prepare-a-script and passed to "creating a classic script".
+    var s = document.getElementById('script0');
+    assert_equals(s.textContent, updatedText,
+                  "<script>'s textContent should be already modified");
+    t.done();
+  });
+</script>
+<script nonce="allow">
+// If this makes the test fail, it indicates `script0` (the original or updated
+// text) was not evaluated, probably blocked by CSP that was checked against the
+// updated text.
+t.unreached_func("CSP check was done against the updated text")();
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/script-text-modifications.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/script-text-modifications.html
index 0ddec6a..cb54da6 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/script-text-modifications.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/script-text-modifications.html
@@ -27,17 +27,14 @@
 <script id="script0">
 t.step(() => {
     // When this is evaluated after the stylesheet is loaded,
-    // script0's innerText is modified by the async script above,
+    // script0's textContent is modified by the async script above,
     // but the evaluated script is still the original script here,
     // not what is overwritten, because "child text content" is taken in
     // #prepare-a-script and passed to "creating a classic script".
     var s = document.getElementById('script0');
-    assert_equals(s.innerText,
+    assert_equals(s.textContent,
                   't.unreached_func("This should not be evaluated")();',
-                  "<script>'s innerText should be already modified");
-    assert_equals(s.text,
-                  't.unreached_func("This should not be evaluated")();',
-                  "<script>'s text should be already modified");
+                  "<script>'s textContent should be already modified");
     t.done();
   });
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/support/script-onerror-insertion-point-2-helper.html b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/support/script-onerror-insertion-point-2-helper.html
index 7a173981..a9ee8002 100644
--- a/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/support/script-onerror-insertion-point-2-helper.html
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/scripting-1/the-script-element/support/script-onerror-insertion-point-2-helper.html
@@ -1,2 +1,2 @@
-Some <script src="http://this is not parseable"
-             onerror="document.write('text'); parent.writeDone(document.documentElement.textContent)"></script>
+Some <script src="http://this is not parseable:-80/"
+             onerror="document.write('text'); document.close(); parent.writeDone(document.documentElement.textContent)"></script>
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/iframe-pdf.h2.window.js b/third_party/blink/web_tests/external/wpt/loading/early-hints/iframe-pdf.h2.window.js
new file mode 100644
index 0000000..11c1443
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/iframe-pdf.h2.window.js
@@ -0,0 +1,31 @@
+// META: script=/common/utils.js
+// META: script=resources/early-hints-helpers.sub.js
+
+promise_test(async (t) => {
+    if (!navigator.pdfViewerEnabled) {
+        return;
+    }
+
+    const iframe = document.createElement("iframe");
+    const resource_url = SAME_ORIGIN_RESOURCES_URL + "/empty.js?" + token();
+    const promise = new Promise((resolve) => {
+        const params = new URLSearchParams();
+        params.set("resource-url", resource_url);
+        params.set("token", token());
+        const iframe_url = SAME_ORIGIN_RESOURCES_URL + "/pdf-with-early-hints.h2.py?" + params.toString();
+
+        iframe.src = iframe_url;
+        iframe.onload = resolve;
+        document.body.appendChild(iframe);
+    });
+    await promise;
+
+    // `iframe` should not preload the hinted resource.
+    const iframe_entries = iframe.contentWindow.performance.getEntriesByName(resource_url);
+    assert_equals(iframe_entries.length, 0);
+
+    await fetchScript(resource_url);
+    const entries = performance.getEntriesByName(resource_url);
+    assert_equals(entries.length, 1);
+    assert_not_equals(entries[0].transferSize, 0);
+}, "Early hints for an iframe of which content is pdf should be ignored.");
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/example.pdf b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/example.pdf
new file mode 100644
index 0000000..7bad251b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/example.pdf
@@ -0,0 +1,50 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [ 0 0 200 300 ]
+  /Count 1
+  /Kids [ 3 0 R ]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+4 0 obj <<
+>>
+stream
+q
+0 0 0 rg
+0 290 10 10 re B*
+10 150 50 30 re B*
+0 0 1 rg
+190 290 10 10 re B*
+70 232 50 30 re B*
+0 1 0 rg
+190 0 10 10 re B*
+130 150 50 30 re B*
+1 0 0 rg
+0 0 10 10 re B*
+70 67 50 30 re B*
+Q
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000161 00000 n 
+0000000230 00000 n 
+trailer<< /Root 1 0 R /Size 5 >>
+startxref
+456
+%%EOF
diff --git a/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/pdf-with-early-hints.h2.py b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/pdf-with-early-hints.h2.py
new file mode 100644
index 0000000..0d05f2a3
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/loading/early-hints/resources/pdf-with-early-hints.h2.py
@@ -0,0 +1,26 @@
+import os
+import time
+
+def handle_headers(frame, request, response):
+    resource_url = request.GET.first(b"resource-url").decode()
+    link_header_value = "<{}>; rel=preload; as=script".format(resource_url)
+    early_hints = [
+        (b":status", b"103"),
+        (b"link", link_header_value),
+    ]
+    response.writer.write_raw_header_frame(headers=early_hints,
+                                           end_headers=True)
+
+    # Sleep to simulate a slow generation of the final response.
+    time.sleep(0.1)
+    response.status = 200
+    response.headers[b"content-type"] = "application/pdf"
+    response.write_status_headers()
+
+
+def main(request, response):
+    current_dir = os.path.dirname(os.path.realpath(__file__))
+    file_path = os.path.join(current_dir, "example.pdf")
+    with open(file_path, "rb") as f:
+        content = f.read()
+    response.writer.write_data(item=content, last=True)
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Animation/cancel.html b/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Animation/cancel.html
index 711a339..a7da9755 100644
--- a/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Animation/cancel.html
+++ b/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Animation/cancel.html
@@ -58,5 +58,76 @@
   });
 }, 'After cancelling an animation, it can still be re-used');
 
+promise_test(async t => {
+  for (const type of ["resolve", "reject"]) {
+    const anim = new Animation();
+
+    let isThenGet = false;
+    let isThenCalled = false;
+    let resolveFinished;
+    let rejectFinished;
+    const thenCalledPromise = new Promise(resolveThenCalledPromise => {
+      // Make `anim` thenable.
+      Object.defineProperty(anim, "then", {
+        get() {
+          isThenGet = true;
+          return function(resolve, reject) {
+            isThenCalled = true;
+            resolveThenCalledPromise(true);
+            resolveFinished = resolve;
+            rejectFinished = reject;
+          };
+        },
+      });
+    });
+
+    // Lazily create finished promise.
+    const finishedPromise = anim.finished;
+
+    assert_false(isThenGet, "then property shouldn't be accessed yet");
+
+    // Resolve finished promise with `anim`, that gets `then`, and
+    // calls in the thenable job.
+    anim.finish();
+
+    assert_true(isThenGet, "then property should be accessed");
+    assert_false(isThenCalled, "then property shouldn't be called yet");
+
+    // Reject finished promise.
+    // This should be ignored.
+    anim.cancel();
+
+    // Wait for the thenable job.
+    await thenCalledPromise;
+
+    assert_true(isThenCalled, "then property should be called");
+
+    const dummyPromise = new Promise(resolve => {
+      step_timeout(() => {
+        resolve("dummy");
+      }, 100);
+    });
+    const dummy = await Promise.race([finishedPromise, dummyPromise]);
+    assert_equals(dummy, "dummy", "finishedPromise shouldn't be settled yet");
+
+    if (type === "resolve") {
+      resolveFinished("hello");
+      const finished = await finishedPromise;
+      assert_equals(finished, "hello",
+                    "finishedPromise should be resolved with given value");
+    } else {
+      rejectFinished("hello");
+      try {
+        await finishedPromise;
+        assert_unreached("finishedPromise should be rejected")
+      } catch (e) {
+        assert_equals(e, "hello",
+                      "finishedPromise should be rejected with given value");
+      }
+    }
+  }
+}, "Animation.finished promise should not be rejected by cancel method once "
+ + "it is resolved with inside finish method");
+
 </script>
 </body>
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/external/wpt/css/selectors/invalidation/target-pseudo-in-has-expected.txt b/third_party/blink/web_tests/flag-specific/highdpi/external/wpt/css/selectors/invalidation/target-pseudo-in-has-expected.txt
new file mode 100644
index 0000000..9f19c5d
--- /dev/null
+++ b/third_party/blink/web_tests/flag-specific/highdpi/external/wpt/css/selectors/invalidation/target-pseudo-in-has-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL CSS Selectors Invalidation: target pseudo in :has() argument assert_equals: parent should be yellowgreen on fragment click expected "rgb(154, 205, 50)" but got "rgb(0, 128, 0)"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/flag-specific/highdpi/virtual/jxl-enabled/images/jxl/jxl-images-expected.png b/third_party/blink/web_tests/flag-specific/highdpi/virtual/jxl-enabled/images/jxl/jxl-images-expected.png
index 27c5227..3f8d79f 100644
--- a/third_party/blink/web_tests/flag-specific/highdpi/virtual/jxl-enabled/images/jxl/jxl-images-expected.png
+++ b/third_party/blink/web_tests/flag-specific/highdpi/virtual/jxl-enabled/images/jxl/jxl-images-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/fedcm-origin-trial-interfaces.html b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/fedcm-origin-trial-interfaces.html
index b6842c7..243718d8 100644
--- a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/fedcm-origin-trial-interfaces.html
+++ b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/fedcm-origin-trial-interfaces.html
@@ -10,18 +10,10 @@
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/origin-trials-helper.js"></script>
 <script>
-  test(t => {
-    if (navigator.userAgent.toLowerCase().indexOf("android") > 1) {
+    test(t => {
       OriginTrialsHelper.check_properties_exist(this,
         {
           'FederatedCredential': ['login'],
         });
-    } else {
-      // FedCM is android-only for now
-      OriginTrialsHelper.check_properties_missing_unless_runtime_flag(this,
-        {
-          'FederatedCredential': ['login'],
-        }, 'fedCmEnabled');
-    }
   }, 'FedCM API interfaces and properties in Origin-Trial enabled document.');
 </script>
diff --git a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/fedcm-third-party-origin-trial-interfaces.html b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/fedcm-third-party-origin-trial-interfaces.html
index 269a7cf1..288ebf5 100644
--- a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/fedcm-third-party-origin-trial-interfaces.html
+++ b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/fedcm-third-party-origin-trial-interfaces.html
@@ -6,18 +6,10 @@
 <script src="/resources/origin-trials-helper.js"></script>
 <script src="http://localhost:8000/origin_trials/webexposed/resources/fedcm-origin-trial.js"></script>
 <script>
-  test(t => {
-    if (navigator.userAgent.toLowerCase().indexOf("android") > 1) {
+    test(t => {
       OriginTrialsHelper.check_properties_exist(this,
         {
           'FederatedCredential': ['login'],
         });
-    } else {
-      // FedCM is android-only for now
-      OriginTrialsHelper.check_properties_missing_unless_runtime_flag(this,
-        {
-          'FederatedCredential': ['login'],
-        }, 'fedCmEnabled');
-    }
   }, 'FedCM API interfaces and properties in Origin-Trial enabled document.');
 </script>
diff --git a/third_party/blink/web_tests/platform/generic/external/wpt/css/selectors/invalidation/target-pseudo-in-has-expected.txt b/third_party/blink/web_tests/platform/generic/external/wpt/css/selectors/invalidation/target-pseudo-in-has-expected.txt
new file mode 100644
index 0000000..9f19c5d
--- /dev/null
+++ b/third_party/blink/web_tests/platform/generic/external/wpt/css/selectors/invalidation/target-pseudo-in-has-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL CSS Selectors Invalidation: target pseudo in :has() argument assert_equals: parent should be yellowgreen on fragment click expected "rgb(154, 205, 50)" but got "rgb(0, 128, 0)"
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/platform/generic/external/wpt/html/semantics/scripting-1/the-script-element/script-onerror-insertion-point-2-expected.txt b/third_party/blink/web_tests/platform/generic/external/wpt/html/semantics/scripting-1/the-script-element/script-onerror-insertion-point-2-expected.txt
deleted file mode 100644
index 5615b22f..0000000
--- a/third_party/blink/web_tests/platform/generic/external/wpt/html/semantics/scripting-1/the-script-element/script-onerror-insertion-point-2-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-FAIL Test that the insertion point is not defined in the error event of a
-  parser-inserted script that has an unparseable URL assert_equals: expected "text" but got "Some text"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/wpt_internal/webgpu/cts.https.html b/third_party/blink/web_tests/wpt_internal/webgpu/cts.https.html
index 3fb650e..245e0b3 100644
--- a/third_party/blink/web_tests/wpt_internal/webgpu/cts.https.html
+++ b/third_party/blink/web_tests/wpt_internal/webgpu/cts.https.html
@@ -2931,12 +2931,12 @@
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,min:abstract_float:*'>
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,min:f32:*'>
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,min:f16:*'>
-<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:scalar_abstract_float:*'>
-<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:scalar_f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:matching_abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:matching_f32:*'>
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:scalar_f16:*'>
-<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:vector_abstract_float:*'>
-<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:vector_f32:*'>
-<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:vector_f16:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:nonmatching_abstract_float:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:nonmatching_f32:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,mix:monmatching_f16:*'>
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:scalar_f32:*'>
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:scalar_f16:*'>
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,modf:vector_f32:*'>
@@ -2998,6 +2998,10 @@
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGather:depth_array:*'>
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGatherCompare:array:*'>
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureGatherCompare:sampled_array:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleBias:stage:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleBias:control_flow:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleBias:sampled:*'>
+<meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,textureSampleBias:arrayed:*'>
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,transpose:abstract_float:*'>
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,transpose:f32:*'>
 <meta name=variant content='?q=webgpu:shader,execution,expression,call,builtin,transpose:f16:*'>
@@ -3009,10 +3013,22 @@
 <meta name=variant content='?q=webgpu:shader,execution,memory_model,atomicity:atomicity:memType="atomic_storage";testType="intra_workgroup";*'>
 <meta name=variant content='?q=webgpu:shader,execution,memory_model,atomicity:atomicity:memType="atomic_workgroup";*'>
 <meta name=variant content='?q=webgpu:shader,execution,memory_model,barrier:workgroup_barrier_store_load:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,barrier:workgroup_barrier_load_store:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,barrier:workgroup_barrier_store_store:*'>
 <meta name=variant content='?q=webgpu:shader,execution,memory_model,coherence:corr:memType="atomic_storage";testType="inter_workgroup"'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,coherence:corr:memType="atomic_storage";testType="inter_workgroup";extraFlags="rmw_variant";*'>
 <meta name=variant content='?q=webgpu:shader,execution,memory_model,coherence:corr:memType="atomic_storage";testType="intra_workgroup";*'>
 <meta name=variant content='?q=webgpu:shader,execution,memory_model,coherence:corr:memType="atomic_workgroup";*'>
-<meta name=variant content='?q=webgpu:shader,execution,memory_model,weak:message_passing_workgroup_memory:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,coherence:coww:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,coherence:cowr:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,coherence:corw1:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,coherence:corw2:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,weak:message_passing:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,weak:store:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,weak:load_buffer:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,weak:read:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,weak:store_buffer:*'>
+<meta name=variant content='?q=webgpu:shader,execution,memory_model,weak:2_plus_2_write:*'>
 <meta name=variant content='?q=webgpu:shader,execution,robust_access:linear_memory:storageClass="storage";storageMode="read";access="read";dynamicOffset=false;containerType="array";*'>
 <meta name=variant content='?q=webgpu:shader,execution,robust_access:linear_memory:storageClass="storage";storageMode="read";access="read";dynamicOffset=false;containerType="matrix";*'>
 <meta name=variant content='?q=webgpu:shader,execution,robust_access:linear_memory:storageClass="storage";storageMode="read";access="read";dynamicOffset=false;containerType="vector";*'>
diff --git a/third_party/closure_compiler/externs/file_manager_private.js b/third_party/closure_compiler/externs/file_manager_private.js
index b3d4fd1..05c4695 100644
--- a/third_party/closure_compiler/externs/file_manager_private.js
+++ b/third_party/closure_compiler/externs/file_manager_private.js
@@ -894,8 +894,9 @@
 /**
  * Unmounts a mounted resource. |volumeId| An ID of the volume.
  * @param {string} volumeId
+ * @param {function()} callback
  */
-chrome.fileManagerPrivate.removeMount = function(volumeId) {};
+chrome.fileManagerPrivate.removeMount = function(volumeId, callback) {};
 
 /**
  * Get the list of mounted volumes. |callback|
diff --git a/third_party/content_analysis_sdk/BUILD.gn b/third_party/content_analysis_sdk/BUILD.gn
new file mode 100644
index 0000000..6e59c8c
--- /dev/null
+++ b/third_party/content_analysis_sdk/BUILD.gn
@@ -0,0 +1,54 @@
+# Copyright 2022 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")
+
+# Local content analysis SDK.
+source_set("liblcasdk") {
+  public = [ "src/browser/include/content_analysis/sdk/analysis_client.h" ]
+
+  sources = [
+    "src/browser/src/client_base.cc",
+    "src/browser/src/client_base.h",
+  ]
+
+  if (is_win) {
+    sources += [
+      "src/browser/src/client_win.cc",
+      "src/browser/src/client_win.h",
+      "src/common/utils_win.cc",
+      "src/common/utils_win.h",
+    ]
+  }
+
+  if (is_linux) {
+    sources += [
+      "src/browser/src/client_posix.cc",
+      "src/browser/src/client_posix.h",
+    ]
+  }
+
+  if (is_mac) {
+    sources += [
+      "src/browser/src/client_mac.cc",
+      "src/browser/src/client_mac.h",
+    ]
+  }
+
+  include_dirs = [
+    "src",
+    "src/browser/include",
+    "gen/third_party/content_analysis_sdk/src/proto",
+  ]
+
+  public_deps = [
+    ":proto",
+    "//third_party/protobuf:protobuf_lite",
+  ]
+}
+
+proto_library("proto") {
+  proto_in_dir = "src/proto"
+  sources = [ "src/proto/content_analysis/sdk/analysis.proto" ]
+}
diff --git a/third_party/content_analysis_sdk/LICENSE b/third_party/content_analysis_sdk/LICENSE
new file mode 100644
index 0000000..1ef68ff
--- /dev/null
+++ b/third_party/content_analysis_sdk/LICENSE
@@ -0,0 +1,28 @@
+// Copyright 2022 The Chromium Authors.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google LLC nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/third_party/content_analysis_sdk/OWNERS b/third_party/content_analysis_sdk/OWNERS
new file mode 100644
index 0000000..dd0e618
--- /dev/null
+++ b/third_party/content_analysis_sdk/OWNERS
@@ -0,0 +1,4 @@
+domfc@chromium.org
+nancylanxiao@google.com
+rogerta@chromium.org
+waldman@google.com
diff --git a/third_party/content_analysis_sdk/README.chromium b/third_party/content_analysis_sdk/README.chromium
new file mode 100644
index 0000000..00246ba
--- /dev/null
+++ b/third_party/content_analysis_sdk/README.chromium
@@ -0,0 +1,21 @@
+Name: Google Chrome Content Analysis Connector Agent SDK
+Short Name: content_analysis_sdk
+URL: https://github.com/chromium/content_analysis_sdk
+Version: 0
+License: BSD
+License File: ./LICENSE
+Security Critical: yes
+CPEPrefix: unknown
+
+Description:
+The Google Chrome Content Analysis Connector provides an official mechanism
+allowing Data Loss Prevention (DLP) agents to more deeply integrate their
+services with Google Chrome.
+
+DLP agents are background processes on managed computers that allow enterprises
+to monitor locally running applications for data exfiltration events. They can
+allow/block these activities based on customer defined DLP policies.
+
+Local Modifications:
+none
+
diff --git a/third_party/material_web_components/tsconfig_base.json b/third_party/material_web_components/tsconfig_base.json
index 8a621b6..bb6cebc 100644
--- a/third_party/material_web_components/tsconfig_base.json
+++ b/third_party/material_web_components/tsconfig_base.json
@@ -13,7 +13,6 @@
     "experimentalDecorators": true,
     "strict": true,
     "noImplicitAny": false,
-    "composite": true,
     "importHelpers": true,
     "plugins": [
       {
diff --git a/third_party/polymer/v1_0/css_strip_prefixes.py b/third_party/polymer/v1_0/css_strip_prefixes.py
index 86b87a3..bfcd426 100644
--- a/third_party/polymer/v1_0/css_strip_prefixes.py
+++ b/third_party/polymer/v1_0/css_strip_prefixes.py
@@ -66,7 +66,7 @@
         indices_to_remove.append(i)
 
   if len(indices_to_remove):
-    print 'stripping CSS from: ' + filename
+    print('stripping CSS from: ' + filename)
 
   # Process line numbers in descinding order, such that the array can be
   # modified in-place.
diff --git a/third_party/webgpu-cts/ts_sources.txt b/third_party/webgpu-cts/ts_sources.txt
index 844431c4..c1f4bde 100644
--- a/third_party/webgpu-cts/ts_sources.txt
+++ b/third_party/webgpu-cts/ts_sources.txt
@@ -340,6 +340,7 @@
 src/webgpu/shader/execution/expression/call/builtin/textureDimension.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/textureGather.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/textureGatherCompare.spec.ts
+src/webgpu/shader/execution/expression/call/builtin/textureSampleBias.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/transpose.spec.ts
 src/webgpu/shader/execution/expression/call/builtin/trunc.spec.ts
 src/webgpu/shader/execution/expression/unary/unary.ts
diff --git a/third_party/zlib/contrib/minizip/README.chromium b/third_party/zlib/contrib/minizip/README.chromium
new file mode 100644
index 0000000..1ad489a
--- /dev/null
+++ b/third_party/zlib/contrib/minizip/README.chromium
@@ -0,0 +1,15 @@
+Name: ZIP file API for reading file entries in a ZIP archive
+Short Name: minizip
+URL: https://github.com/madler/zlib/tree/master/contrib/minizip
+Version: 1.2.12
+License: Zlib
+Security Critical: yes
+
+Description:
+Minizip provides API on top of zlib that can enumerate and extract ZIP archive
+files. See minizip.md for chromium build instructions.
+
+Local Modifications:
+- Add parsing of the 'Info-ZIP Unicode Path Extra Field' as described in
+  https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT section 4.6.9.
+  (see crrev.com/1002476)
diff --git a/third_party/zlib/contrib/minizip/unzip.c b/third_party/zlib/contrib/minizip/unzip.c
index e8b2bc5c..0a1d8b4 100644
--- a/third_party/zlib/contrib/minizip/unzip.c
+++ b/third_party/zlib/contrib/minizip/unzip.c
@@ -1023,46 +1023,102 @@
         while(acc < file_info.size_file_extra)
         {
             uLong headerId;
-                                                uLong dataSize;
+            uLong dataSize;
 
             if (unz64local_getShort(&s->z_filefunc, s->filestream,&headerId) != UNZ_OK)
                 err=UNZ_ERRNO;
 
             if (unz64local_getShort(&s->z_filefunc, s->filestream,&dataSize) != UNZ_OK)
                 err=UNZ_ERRNO;
-
+            
             /* ZIP64 extra fields */
             if (headerId == 0x0001)
             {
-                                                        uLong uL;
+                uLong uL;
 
-                                                                if(file_info.uncompressed_size == MAXU32)
-                                                                {
-                                                                        if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info.uncompressed_size) != UNZ_OK)
-                                                                                        err=UNZ_ERRNO;
-                                                                }
+                if(file_info.uncompressed_size == MAXU32)
+                {
+                    if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info.uncompressed_size) != UNZ_OK)
+                        err=UNZ_ERRNO;
+                }
 
-                                                                if(file_info.compressed_size == MAXU32)
-                                                                {
-                                                                        if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info.compressed_size) != UNZ_OK)
-                                                                                  err=UNZ_ERRNO;
-                                                                }
+                if(file_info.compressed_size == MAXU32)
+                {
+                    if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info.compressed_size) != UNZ_OK)
+                        err=UNZ_ERRNO;
+                }
 
-                                                                if(file_info_internal.offset_curfile == MAXU32)
-                                                                {
-                                                                        /* Relative Header offset */
-                                                                        if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info_internal.offset_curfile) != UNZ_OK)
-                                                                                err=UNZ_ERRNO;
-                                                                }
+                if(file_info_internal.offset_curfile == MAXU32)
+                {
+                    /* Relative Header offset */
+                    if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info_internal.offset_curfile) != UNZ_OK)
+                        err=UNZ_ERRNO;
+                }
 
-                                                                if(file_info.disk_num_start == MAXU32)
-                                                                {
-                                                                        /* Disk Start Number */
-                                                                        if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK)
-                                                                                err=UNZ_ERRNO;
-                                                                }
+                if(file_info.disk_num_start == MAXU32)
+                {
+                    /* Disk Start Number */
+                    if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK)
+                        err=UNZ_ERRNO;
+                }
 
             }
+            else if (headerId == 0x7075) /* Info-ZIP Unicode Path Extra Field */
+            {
+                int version = 0;
+
+                if (unz64local_getByte(&s->z_filefunc, s->filestream, &version) != UNZ_OK)
+                {
+                    err = UNZ_ERRNO;
+                }
+                if (version != 1)
+                {
+                    if (ZSEEK64(s->z_filefunc, s->filestream,dataSize - 1, ZLIB_FILEFUNC_SEEK_CUR) != 0)
+                    {
+                        err = UNZ_ERRNO;
+                    }
+                }
+                else
+                {
+                    uLong uCrc, uHeaderCrc, fileNameSize;
+
+                    if (unz64local_getLong(&s->z_filefunc, s->filestream, &uCrc) != UNZ_OK)
+                    {
+                        err = UNZ_ERRNO;
+                    }
+                    uHeaderCrc = crc32(0, (const unsigned char *)szFileName, file_info.size_filename);
+                    fileNameSize = dataSize - (2 * sizeof (short) + 1);
+                    /* Check CRC against file name in the header. */
+                    if (uHeaderCrc != uCrc)
+                    {
+                        if (ZSEEK64(s->z_filefunc, s->filestream, fileNameSize, ZLIB_FILEFUNC_SEEK_CUR) != 0)
+                        {
+                            err = UNZ_ERRNO;
+                        }
+                    }
+                    else
+                    {
+                        uLong uSizeRead;
+
+                        if (fileNameSize < fileNameBufferSize)
+                        {
+                             *(szFileName + fileNameSize) = '\0';
+                            uSizeRead = fileNameSize;
+                        }
+                        else
+                        {
+                            uSizeRead = fileNameBufferSize;
+                        }
+                        if ((fileNameSize > 0) && (fileNameBufferSize > 0))
+                        {
+                            if (ZREAD64(s->z_filefunc, s->filestream, szFileName, uSizeRead) != uSizeRead)
+                            {
+                                err = UNZ_ERRNO;
+                            }
+                        }
+                    }
+                }
+            }
             else
             {
                 if (ZSEEK64(s->z_filefunc, s->filestream,dataSize,ZLIB_FILEFUNC_SEEK_CUR)!=0)
diff --git a/tools/android/python_utils/PRESUBMIT.py b/tools/android/python_utils/PRESUBMIT.py
index 30387f2..79a8f9a8 100644
--- a/tools/android/python_utils/PRESUBMIT.py
+++ b/tools/android/python_utils/PRESUBMIT.py
@@ -13,6 +13,10 @@
 
 
 def CheckChange(input_api, output_api):
+    # These tests contain too many Linux assumptions (pwd command, output when
+    # files are missing) for them to run on Windows, so exit early.
+    if input_api.is_windows:
+        return []
     """Presubmit checks to run on upload and on commit of a CL."""
     checks = input_api.canned_checks.GetUnitTestsRecursively(
         input_api,
diff --git a/tools/boilerplate.py b/tools/boilerplate.py
index 11a1361..31dcba1 100755
--- a/tools/boilerplate.py
+++ b/tools/boilerplate.py
@@ -38,6 +38,7 @@
     'gn': '#',
     'gni': '#',
     'mojom': '//',
+    'ts': '//',
     'typemap': '#',
     "swift": "//",
 }
diff --git a/tools/clang/scripts/build.py b/tools/clang/scripts/build.py
index c35e207..1e4ae2c 100755
--- a/tools/clang/scripts/build.py
+++ b/tools/clang/scripts/build.py
@@ -833,7 +833,7 @@
 
     RunCommand(['cmake'] + instrument_args + [os.path.join(LLVM_DIR, 'llvm')],
                msvc_arch='x64')
-    RunCommand(['ninja'], msvc_arch='x64')
+    RunCommand(['ninja', 'clang'], msvc_arch='x64')
     print('Instrumented compiler built.')
 
     # Train by building some C++ code.
diff --git a/tools/json_data_generator/generator.py b/tools/json_data_generator/generator.py
index 5e92765..9e9b881 100644
--- a/tools/json_data_generator/generator.py
+++ b/tools/json_data_generator/generator.py
@@ -104,4 +104,4 @@
             sys.path.remove(template_helper_dir)
 
     def _ToHeaderGuard(self, path: str):
-        return re.sub(r'[\/\.\-]+', '_', path.upper())
+        return re.sub(r'[\\\/\.\-]+', '_', path.upper())
diff --git a/tools/json_schema_compiler/util.h b/tools/json_schema_compiler/util.h
index 963d7ec..8f8cce1 100644
--- a/tools/json_schema_compiler/util.h
+++ b/tools/json_schema_compiler/util.h
@@ -147,13 +147,13 @@
 // This template is used for types generated by tools/json_schema_compiler.
 template <class T>
 void AddItemToList(const std::unique_ptr<T>& from, base::ListValue* out) {
-  out->Append(from->ToValue());
+  out->GetList().Append(base::Value::FromUniquePtrValue(from->ToValue()));
 }
 
 // This template is used for types generated by tools/json_schema_compiler.
 template <class T>
 void AddItemToList(const T& from, base::ListValue* out) {
-  out->Append(from.ToValue());
+  out->GetList().Append(base::Value::FromUniquePtrValue(from.ToValue()));
 }
 
 // Set |out| to the the contents of |from|. Requires PopulateItem to be
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index dfb9f52..dbab5ab 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -58,22 +58,17 @@
       'lacros64-archive-rel': 'chromeos_amd64-generic_lacros_rel',
       'linux-archive-dbg': 'debug_bot_reclient',
       'linux-archive-rel': 'release_bot_reclient',
-      'linux-archive-tagged': 'official_optimize_reclient',
       'linux-official': 'official_optimize_reclient',
       'mac-archive-dbg': 'debug_bot',
       'mac-archive-rel': 'release_bot_mac_strip_minimal_symbols',
-      'mac-archive-tagged': 'official_optimize_goma',
       'mac-arm64-archive-dbg': 'debug_bot_arm64',
       'mac-arm64-archive-rel': 'release_bot_mac_strip_minimal_symbols_arm64',
-      'mac-arm64-archive-tagged': 'arm64_official_optimize_goma',
       'mac-official': 'official_optimize_goma',
       'win-archive-dbg': 'debug_bot_reclient',
       'win-archive-rel': 'release_bot_minimal_symbols_enable_archive_compression_reclient',
-      'win-archive-tagged': 'official_optimize_reclient',
       'win-official': 'official_optimize_goma',
       'win32-archive-dbg': 'debug_bot_x86_reclient',
       'win32-archive-rel': 'release_bot_x86_minimal_symbols_enable_archive_compression_reclient',
-      'win32-archive-tagged': 'x86_official_optimize_reclient',
       'win32-official': 'x86_official_optimize_goma',
     },
 
@@ -1769,10 +1764,6 @@
       'angle_deqp_tests', 'shared_release_trybot', 'x86',
     ],
 
-    'arm64_official_optimize_goma': [
-      'arm64', 'official_optimize_goma',
-    ],
-
     'asan_clang_fuzzer_static_v8_heap_minimal_symbols_release': [
       'asan', 'fuzzer', 'static', 'v8_heap', 'minimal_symbols', 'release_bot',
     ],
@@ -3467,10 +3458,6 @@
     'x86_official_optimize_goma_trybot': [
       'official_optimize_goma_trybot', 'x86',
     ],
-
-    'x86_official_optimize_reclient': [
-      'official_optimize_reclient', 'x86',
-    ],
   },
 
   # This is a dict mapping a given 'mixin' name to a dict of settings that
diff --git a/tools/mb/mb_config_expectations/chromium.json b/tools/mb/mb_config_expectations/chromium.json
index e43d0ed..d85088b 100644
--- a/tools/mb/mb_config_expectations/chromium.json
+++ b/tools/mb/mb_config_expectations/chromium.json
@@ -66,13 +66,6 @@
       "use_remoteexec": true
     }
   },
-  "linux-archive-tagged": {
-    "gn_args": {
-      "is_official_build": true,
-      "use_rbe": true,
-      "use_remoteexec": true
-    }
-  },
   "linux-official": {
     "gn_args": {
       "is_official_build": true,
@@ -98,12 +91,6 @@
       "use_goma": true
     }
   },
-  "mac-archive-tagged": {
-    "gn_args": {
-      "is_official_build": true,
-      "use_goma": true
-    }
-  },
   "mac-arm64-archive-dbg": {
     "gn_args": {
       "is_component_build": true,
@@ -124,13 +111,6 @@
       "use_goma": true
     }
   },
-  "mac-arm64-archive-tagged": {
-    "gn_args": {
-      "is_official_build": true,
-      "target_cpu": "arm64",
-      "use_goma": true
-    }
-  },
   "mac-official": {
     "gn_args": {
       "is_official_build": true,
@@ -157,13 +137,6 @@
       "use_remoteexec": true
     }
   },
-  "win-archive-tagged": {
-    "gn_args": {
-      "is_official_build": true,
-      "use_rbe": true,
-      "use_remoteexec": true
-    }
-  },
   "win-official": {
     "gn_args": {
       "is_official_build": true,
@@ -192,14 +165,6 @@
       "use_remoteexec": true
     }
   },
-  "win32-archive-tagged": {
-    "gn_args": {
-      "is_official_build": true,
-      "target_cpu": "x86",
-      "use_rbe": true,
-      "use_remoteexec": true
-    }
-  },
   "win32-official": {
     "gn_args": {
       "is_official_build": true,
diff --git a/tools/metrics/histograms/README.md b/tools/metrics/histograms/README.md
index 361f523..4284a88 100644
--- a/tools/metrics/histograms/README.md
+++ b/tools/metrics/histograms/README.md
@@ -294,14 +294,15 @@
 #### Count Histograms: Choosing Number of Buckets
 
 Choose the smallest number of buckets that give you the granularity you need. By
-default, count histogram bucket sizes scale exponentially, so you can get fine
-granularity when the numbers are small yet still reasonable resolution for
-larger numbers. The macros default to 50 buckets (or 100 buckets for histograms
-with wide ranges), which is appropriate for most purposes. Because histograms
-pre-allocate all the buckets, the number of buckets selected directly dictates
-how much memory is used. Do not exceed 100 buckets without good reason (and
-consider whether [sparse histograms](#When-To-Use-Sparse-Histograms) might work
-better for you in that case—they do not pre-allocate their buckets).
+default, count histogram bucket sizes increase exponentially with respect to the
+value (i.e., exponential binning), so you can get fine granularity when the
+values are small yet still reasonable resolution when the values are larger. The
+macros default to 50 buckets (or 100 buckets for histograms with wide ranges),
+which is appropriate for most purposes. Because histograms pre-allocate all the
+buckets, the number of buckets selected directly dictates how much memory is
+used. Do not exceed 100 buckets without good reason (and consider whether
+[sparse histograms](#When-To-Use-Sparse-Histograms) might work better for you in
+that case—they do not pre-allocate their buckets).
 
 ### Timing Histograms
 
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 7299a8f..5ba54ee0 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -42659,6 +42659,12 @@
   </int>
 </enum>
 
+<enum name="FwupdReturnValue">
+  <int value="0" label="Success"/>
+  <int value="1" label="Error"/>
+  <int value="2" label="Nothing to do"/>
+</enum>
+
 <enum name="GaiaCookieStateOnSignedInNavigation">
   <int value="0" label="Gaia cookie is present"/>
   <int value="1" label="Gaia cookie is missing from Google-associated domain"/>
@@ -46684,6 +46690,22 @@
   <int value="2" label="Both devices, pointing and keyboard, detected."/>
 </enum>
 
+<enum name="HidType">
+  <summary>
+    Possible device types of HIDs interfaced with in the OOBE HID detection
+    screen
+  </summary>
+  <int value="0" label="Touchscreen"/>
+  <int value="1" label="USB Keyboard"/>
+  <int value="2" label="USB Pointer"/>
+  <int value="3" label="Serial Keyboard"/>
+  <int value="4" label="Serial Pointer"/>
+  <int value="5" label="Bluetooth Keyboard"/>
+  <int value="6" label="Bluetooth Pointer"/>
+  <int value="7" label="Unknown Keyboard"/>
+  <int value="8" label="Unknown Pointer"/>
+</enum>
+
 <enum name="HintCacheStoreEntryType">
   <summary>
     Possible store entry types contained within the HintCacheStore.
@@ -54745,6 +54767,7 @@
   <int value="-1906427432" label="WallpaperFastRefresh:disabled"/>
   <int value="-1905470520" label="FirmwareUpdaterApp:enabled"/>
   <int value="-1903365454" label="SyncPseudoUSSPreferences:disabled"/>
+  <int value="-1902142840" label="CalendarModelDebugMode:enabled"/>
   <int value="-1899715534" label="GamepadPollingInterval:enabled"/>
   <int value="-1899652563" label="ReportFeedUserActions:enabled"/>
   <int value="-1899409297"
@@ -54851,6 +54874,7 @@
   <int value="-1840608422" label="AdvancedPpdAttributes:disabled"/>
   <int value="-1839874877" label="WebXROrientationSensorDevice:enabled"/>
   <int value="-1839540507" label="OneGoogleBarModalOverlays:enabled"/>
+  <int value="-1839504558" label="PreloadCookies:disabled"/>
   <int value="-1839496458" label="disable-file-manager-touch-mode"/>
   <int value="-1838482444" label="disable-settings-window"/>
   <int value="-1837401779" label="EnableFileManagerFormatDialog:enabled"/>
@@ -55673,6 +55697,7 @@
       label="AutofillEnableInfoBarAccountIndicationFooterForSingleAccountUsers:disabled"/>
   <int value="-1301167148" label="WebViewZeroCopyVideo:disabled"/>
   <int value="-1298273481" label="http2-grease-settings"/>
+  <int value="-1298067767" label="CalendarModelDebugMode:disabled"/>
   <int value="-1297079591" label="EnableRemovingAllThirdPartyCookies:disabled"/>
   <int value="-1295288468" label="MemoriesDebug:enabled"/>
   <int value="-1294944922" label="Canvas2dStaysGPUOnReadback:disabled"/>
@@ -56320,6 +56345,7 @@
   <int value="-884864731" label="WebPaymentsSingleAppUiSkip:enabled"/>
   <int value="-883694393" label="SyncPseudoUSSSupervisedUsers:disabled"/>
   <int value="-883608641" label="enable-cros-action-recorder"/>
+  <int value="-882996598" label="PreloadCookies:enabled"/>
   <int value="-882434910" label="EnableAggregatedMlSearchRanking:enabled"/>
   <int value="-881447505" label="ash-disable-shelf-model-synchronization"/>
   <int value="-881054479" label="WebAssemblyStreaming:disabled"/>
@@ -65069,6 +65095,12 @@
   <int value="6" label="Unknown"/>
 </enum>
 
+<enum name="MultitouchEvent">
+  <int value="0" label="Released"/>
+  <int value="1" label="Canceled"/>
+  <int value="2" label="Held"/>
+</enum>
+
 <enum name="NaClAppTypeEnum">
   <int value="0" label="PNaCl Open Web"/>
   <int value="1" label="PNaCl Hosted App"/>
diff --git a/tools/metrics/histograms/metadata/accessibility/histograms.xml b/tools/metrics/histograms/metadata/accessibility/histograms.xml
index 9a3f0143..f5df1ff 100644
--- a/tools/metrics/histograms/metadata/accessibility/histograms.xml
+++ b/tools/metrics/histograms/metadata/accessibility/histograms.xml
@@ -1192,7 +1192,7 @@
 </histogram>
 
 <histogram name="Accessibility.LiveCaption.LoadSodaErrorCode"
-    enum="WinGetLastError" expires_after="2022-07-26">
+    enum="WinGetLastError" expires_after="2022-12-26">
   <owner>abigailbklein@google.com</owner>
   <owner>evliu@google.com</owner>
   <owner>chrome-a11y-core@google.com</owner>
@@ -1203,6 +1203,22 @@
   </summary>
 </histogram>
 
+<histogram name="Accessibility.LiveCaption.LoadSodaErrorMacOsVersion"
+    units="version" expires_after="2022-12-26">
+  <owner>abigailbklein@google.com</owner>
+  <owner>evliu@google.com</owner>
+  <owner>chrome-a11y-core@google.com</owner>
+  <summary>
+    The MacOS operating system version of the client that failed to load the
+    Speech On-Device API (SODA). Logs the the system's MacOS major and minor
+    version numbers combined into an integer value. For example, for MacOS
+    Sierra this returns 1012, and for MacOS Big Sur it returns 1100. Note that
+    the accuracy returned by this function is as granular as the major version
+    number of Darwin. This is logged once for each media stream if the SODA
+    binary failed to load on MacOS.
+  </summary>
+</histogram>
+
 <histogram name="Accessibility.LiveCaption.LoadSodaResult"
     enum="LoadSodaResult" expires_after="2022-09-25">
   <owner>abigailbklein@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/android/histograms.xml b/tools/metrics/histograms/metadata/android/histograms.xml
index adee600..545f9bd 100644
--- a/tools/metrics/histograms/metadata/android/histograms.xml
+++ b/tools/metrics/histograms/metadata/android/histograms.xml
@@ -105,6 +105,41 @@
   <variant name=".VrServicesUpgrade"/>
 </variants>
 
+<variants name="TaskType">
+  <variant name="BackgroundSyncOneShot" summary="BackgroundSyncOneShot"/>
+  <variant name="ChromeMinidumpUploading" summary="ChromeMinidumpUploading"/>
+  <variant name="ComponentUpdate" summary="ComponentUpdate"/>
+  <variant name="DeprecatedDownloadResumption"
+      summary="DeprecatedDownloadResumption"/>
+  <variant name="DeprecatedExploreSitesRefresh"
+      summary="DeprecatedExploreSitesRefresh"/>
+  <variant name="DownloadAutoResumption" summary="DownloadAutoResumption"/>
+  <variant name="DownloadCleanup" summary="DownloadCleanup"/>
+  <variant name="DownloadLater" summary="DownloadLater"/>
+  <variant name="DownloadService" summary="DownloadService"/>
+  <variant name="ExploreSitesRefresh" summary="ExploreSitesRefresh"/>
+  <variant name="FeedRefresh" summary="FeedRefresh"/>
+  <variant name="FeedV2Refresh" summary="FeedV2Refresh"/>
+  <variant name="Gcm" summary="Gcm"/>
+  <variant name="NotificationScheduler" summary="NotificationScheduler"/>
+  <variant name="NotificationService" summary="NotificationService"/>
+  <variant name="NotificationTrigger" summary="NotificationTrigger"/>
+  <variant name="OfflinePages" summary="OfflinePages"/>
+  <variant name="OfflinePagesPrefetch" summary="OfflinePagesPrefetch"/>
+  <variant name="OfflinePagesPrefetchNotification"
+      summary="OfflinePagesPrefetchNotification"/>
+  <variant name="Omaha" summary="Omaha"/>
+  <variant name="PeriodicBackgroundSyncChromeWakeup"
+      summary="PeriodicBackgroundSyncChromeWakeup"/>
+  <variant name="QueryTile" summary="QueryTile"/>
+  <variant name="Test" summary="Test"/>
+  <variant name="WebApkUpdate" summary="WebApkUpdate"/>
+  <variant name="WebviewComponentUpdate" summary="WebviewComponentUpdate"/>
+  <variant name="WebviewMinidumpUploading" summary="WebviewMinidumpUploading"/>
+  <variant name="WebviewVariationsSeedFetch"
+      summary="WebviewVariationsSeedFetch"/>
+</variants>
+
 <variants name="ThumbnailProvider_ClientType">
   <variant name=".DownloadHome" summary="Download home"/>
   <variant name=".NTPSnippets" summary="NTP snippets"/>
@@ -492,6 +527,17 @@
   </summary>
 </histogram>
 
+<histogram name="Android.BackgroundTaskScheduler.TaskFinished.{TaskType}"
+    units="ms" expires_after="2022-11-01">
+  <owner>shaktisahu@chromium.org</owner>
+  <owner>nyquist@chromium.org</owner>
+  <summary>
+    Records the time (uptimeMillis) taken by a background task of type
+    {TaskType}. Recorded when the task finished callback is invoked.
+  </summary>
+  <token key="TaskType" variants="TaskType"/>
+</histogram>
+
 <histogram name="Android.BackgroundTaskScheduler.TaskLoadedNative"
     enum="BackgroundTaskId" expires_after="2022-09-11">
   <owner>fgorski@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/apps/histograms.xml b/tools/metrics/histograms/metadata/apps/histograms.xml
index df0aff4..368f459 100644
--- a/tools/metrics/histograms/metadata/apps/histograms.xml
+++ b/tools/metrics/histograms/metadata/apps/histograms.xml
@@ -1808,6 +1808,7 @@
     <variant name="Close" summary="close launcher"/>
     <variant name="HideAppsPage"
         summary="switch away from apps page to another page"/>
+    <variant name="Open" summary="open launcher"/>
     <variant name="OpenAppsPage" summary="open launcher to apps page"/>
     <variant name="ShowAppsPage"
         summary="switch to apps page from another page"/>
@@ -2351,6 +2352,17 @@
   </summary>
 </histogram>
 
+<histogram name="Apps.MediaApp.Load.OtherOpenWindowCount" units="windows"
+    expires_after="2023-05-11">
+  <owner>tapted@chromium.org</owner>
+  <owner>patricialor@chromium.org</owner>
+  <summary>
+    The number of other media app windows that exist when a new media app window
+    is created. Counts windows open with any supported file type, or in the
+    &quot;zero state&quot; (with no open file).
+  </summary>
+</histogram>
+
 <histogram name="Apps.NewUserFirstLauncherAction{TabletOrClamshell}"
     enum="AppListLaunchedFrom" expires_after="M107">
   <owner>andrewxu@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/compositing/histograms.xml b/tools/metrics/histograms/metadata/compositing/histograms.xml
index 889643b..2081d67f 100644
--- a/tools/metrics/histograms/metadata/compositing/histograms.xml
+++ b/tools/metrics/histograms/metadata/compositing/histograms.xml
@@ -1336,6 +1336,61 @@
   </token>
 </histogram>
 
+<histogram name="Graphics.Smoothness.PercentDroppedFrames3.{Thread}{Sequence}"
+    units="%" expires_after="2023-05-11">
+  <owner>sadrul@chromium.org</owner>
+  <owner>behdadb@chromium.org</owner>
+  <owner>graphics-dev@chromium.org</owner>
+  <summary>
+    Tracks the percent of dropped frames for a particular sequence of frames.
+    This metric aggregates data {Sequence}
+
+    PercentDroppedFrames is measured by tracking the number of frames which were
+    not displayed on screen out of the total number of frames expected to be
+    produced and displayed. In other words, the lower this number is, the
+    smoother experience.
+
+    Note that this metric is reported only when there are sufficient number of
+    frames (&gt;= 100). If there are sequences with fewer frames, then these are
+    aggregated until there are enough frames to produce the metric.
+
+    This is a new implementation of the older
+    Graphics.Smoothness.PercentDroppedFrames.AllInteractions metric.
+  </summary>
+  <token key="Thread">
+    <variant name=""/>
+    <variant name="CompositorThread."/>
+    <variant name="MainThread."/>
+  </token>
+  <token key="Sequence">
+    <variant name="AllAnimations"
+        summary="reported from all types of animations (e.g. comositor-driven
+                 animations, main-thread driven animations, and raf-driven
+                 animations, etc.)."/>
+    <variant name="AllInteractions"
+        summary="reported for all supported combinations of interaction types
+                 (e.g. scrolling, pinching, etc.) and input device types
+                 (e.g. touchscreen, touchpad, mousewheel, etc.)."/>
+    <variant name="AllSequences"
+        summary="from all animations and all interactions."/>
+    <variant name="CanvasAnimation"
+        summary="reported for all canvas animations."/>
+    <variant name="CompositorAnimation"
+        summary="reported for all compositor thread driven animations."/>
+    <variant name="JSAnimation"
+        summary="reported for all JS driven animations."/>
+    <variant name="MainThreadAnimation"
+        summary="reported for all main thread driven animations."/>
+    <variant name="PinchZoom"
+        summary="reported for all pinch to zoom interctions."/>
+    <variant name="RAF" summary="reported for all raf driven animations."/>
+    <variant name="ScrollbarScroll"
+        summary="reported for all scrollbar scrolling."/>
+    <variant name="TouchScroll" summary="reported for all touch scrolling."/>
+    <variant name="WheelScroll" summary="reported for all wheel scrolling."/>
+  </token>
+</histogram>
+
 <histogram base="true" name="Graphics.Smoothness.PercentMissedDeadlineFrames"
     units="%" expires_after="2022-09-30">
   <owner>sadrul@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/input/histograms.xml b/tools/metrics/histograms/metadata/input/histograms.xml
index 47b12ec..50307ed 100644
--- a/tools/metrics/histograms/metadata/input/histograms.xml
+++ b/tools/metrics/histograms/metadata/input/histograms.xml
@@ -1060,6 +1060,16 @@
   </summary>
 </histogram>
 
+<histogram name="InputMethod.VirtualKeyboard.MultitouchEvent"
+    enum="MultitouchEvent" expires_after="2022-11-01">
+  <owner>michellegc@google.com</owner>
+  <owner>essential-inputs-team@google.com</owner>
+  <summary>
+    The number of times each type of multitouch behavior is triggered. Recorded
+    when a key is pressed while another key is still held down.
+  </summary>
+</histogram>
+
 <histogram name="InputMethod.VirtualKeyboard.Paprika.Actions"
     enum="IMEPaprikaActions" expires_after="M110">
   <owner>jopalmer@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
index a79d7fe..b8a5a268 100644
--- a/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
+++ b/tools/metrics/histograms/metadata/new_tab_page/histograms.xml
@@ -1105,8 +1105,8 @@
   </summary>
 </histogram>
 
-<histogram base="true" name="NewTabPage.Modules.FreImpression"
-    enum="BooleanEnabled" expires_after="2022-09-15">
+<histogram name="NewTabPage.Modules.FreImpression" enum="BooleanEnabled"
+    expires_after="2022-09-15">
   <owner>danpeng@google.com</owner>
   <owner>pauladedeji@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
@@ -1118,8 +1118,8 @@
   </summary>
 </histogram>
 
-<histogram base="true" name="NewTabPage.Modules.FreLoaded"
-    enum="BooleanEnabled" expires_after="2022-09-15">
+<histogram name="NewTabPage.Modules.FreLoaded" enum="BooleanEnabled"
+    expires_after="2022-09-15">
   <owner>danpeng@google.com</owner>
   <owner>pauladedeji@chromium.org</owner>
   <owner>chrome-desktop-ntp@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/oobe/histograms.xml b/tools/metrics/histograms/metadata/oobe/histograms.xml
index 62e885c..8fe505ec 100644
--- a/tools/metrics/histograms/metadata/oobe/histograms.xml
+++ b/tools/metrics/histograms/metadata/oobe/histograms.xml
@@ -168,6 +168,18 @@
   </summary>
 </histogram>
 
+<histogram name="OOBE.HidDetectionScreen.HidConnected" enum="HidType"
+    expires_after="2023-05-06">
+  <owner>gordonseto@google.com</owner>
+  <owner>cros-connectivity@google.com</owner>
+  <summary>
+    Records the type of a connected human interface device (HID). This metric is
+    emitted each time a HID is connected during the HID detection screen in
+    OOBE. It does not include devices connected before the screen is shown or
+    after the screen is hidden.
+  </summary>
+</histogram>
+
 <histogram name="OOBE.MarketingOptInScreen.BackendConnector"
     enum="MarketingOptInBackendConnectorEvent" expires_after="2022-11-30">
   <owner>rrsilva@google.com</owner>
diff --git a/tools/metrics/histograms/metadata/others/histograms.xml b/tools/metrics/histograms/metadata/others/histograms.xml
index e9cdb80..2e78f3b 100644
--- a/tools/metrics/histograms/metadata/others/histograms.xml
+++ b/tools/metrics/histograms/metadata/others/histograms.xml
@@ -11926,6 +11926,36 @@
   </summary>
 </histogram>
 
+<histogram name="SiteIsolatedCodeCache.JS.Hit" units="Boolean"
+    expires_after="2022-11-10">
+  <owner>yhirano@chromium.org</owner>
+  <owner>loading-dev@chromium.org</owner>
+  <summary>
+    Represents whether fetching from the code cache succeeded. Recorded when the
+    code cache result gets available.
+  </summary>
+</histogram>
+
+<histogram name="SiteIsolatedCodeCache.JS.MemoryBackedCodeCachePotentialImpact"
+    units="Milliseconds" expires_after="2022-11-10">
+  <owner>yhirano@chromium.org</owner>
+  <owner>loading-dev@chromium.org</owner>
+  <summary>
+    The amount of time that could be saved if we had a memory-backed code cache.
+    Recorded when the code cache result gets available.
+  </summary>
+</histogram>
+
+<histogram name="SiteIsolatedCodeCache.JS.PotentialMemoryBackedCodeCacheHit"
+    units="Boolean" expires_after="2022-11-10">
+  <owner>yhirano@chromium.org</owner>
+  <owner>loading-dev@chromium.org</owner>
+  <summary>
+    Represents whether fetching from the memory-backed code cache would succeed.
+    Recorded when the code cache result gets available.
+  </summary>
+</histogram>
+
 <histogram name="SiteIsolatedCodeCache.WASM.Behaviour"
     enum="SiteIsolatedCodeCacheWASMBehaviour" expires_after="2020-04-19">
   <owner>bbudge@chromium.org</owner>
@@ -14454,7 +14484,7 @@
 </histogram>
 
 <histogram name="WebUITabStrip.LoadCompletedTime" units="ms"
-    expires_after="2022-05-21">
+    expires_after="2022-10-16">
   <owner>yuhengh@chromium.org</owner>
   <owner>tluk@chromium.org</owner>
   <owner>romanarora@chromium.org</owner>
@@ -14468,7 +14498,7 @@
 </histogram>
 
 <histogram name="WebUITabStrip.LoadDocumentTime" units="ms"
-    expires_after="2022-05-21">
+    expires_after="2022-10-16">
   <owner>yuhengh@chromium.org</owner>
   <owner>tluk@chromium.org</owner>
   <owner>romanarora@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/platform/histograms.xml b/tools/metrics/histograms/metadata/platform/histograms.xml
index ac4140b..e90afa49 100644
--- a/tools/metrics/histograms/metadata/platform/histograms.xml
+++ b/tools/metrics/histograms/metadata/platform/histograms.xml
@@ -403,6 +403,36 @@
   </summary>
 </histogram>
 
+<histogram name="Platform.Fwupd.ActivateStatus" enum="FwupdReturnValue"
+    expires_after="2023-05-01">
+  <owner>campello@google.com</owner>
+  <owner>chromeos-fwupd@google.com</owner>
+  <summary>
+    Indicates the return value of a firmware activation operation via fwupd.
+    Reported once per activation operation.
+  </summary>
+</histogram>
+
+<histogram name="Platform.Fwupd.UpdateDuration" units="seconds"
+    expires_after="2023-05-01">
+  <owner>campello@google.com</owner>
+  <owner>chromeos-fwupd@google.com</owner>
+  <summary>
+    Time elapsed during the firmware update operation. Sent after every
+    sucessful update firmware operation via fwupd.
+  </summary>
+</histogram>
+
+<histogram name="Platform.Fwupd.UpdateStatus" enum="FwupdReturnValue"
+    expires_after="2023-05-01">
+  <owner>campello@google.com</owner>
+  <owner>chromeos-fwupd@google.com</owner>
+  <summary>
+    Indicates the return value of a firmware update operation via fwupd.
+    Reported once per update operation.
+  </summary>
+</histogram>
+
 <histogram name="Platform.IntelMaxMicroArchitecture"
     enum="IntelMaxMicroArchitecture" expires_after="2022-10-09">
   <owner>fbarchard@chromium.org</owner>
diff --git a/tools/metrics/histograms/metadata/tab/histograms.xml b/tools/metrics/histograms/metadata/tab/histograms.xml
index 34df32d7..308d354 100644
--- a/tools/metrics/histograms/metadata/tab/histograms.xml
+++ b/tools/metrics/histograms/metadata/tab/histograms.xml
@@ -3057,7 +3057,7 @@
 </histogram>
 
 <histogram name="TabStrip.Tab.{Framework}.ActivationAction"
-    enum="TabActivationTypes" expires_after="2022-05-21">
+    enum="TabActivationTypes" expires_after="2022-10-16">
   <owner>yuhengh@chromium.org</owner>
   <owner>tluk@chromium.org</owner>
   <owner>romanarora@chromium.org</owner>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 10641151..d9ef964 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -16628,22 +16628,12 @@
       Records whether the users' request to start a Presentation with a
       &quot;cast:&quot; presentation URL succeed.
     </summary>
-    <aggregation>
-      <history>
-        <statistics/>
-      </history>
-    </aggregation>
   </metric>
   <metric name="RemotePlayback" enum="Boolean">
     <summary>
       Records whether the users' request to start a Presentation through
       RemotePlayback succeed.
     </summary>
-    <aggregation>
-      <history>
-        <statistics/>
-      </history>
-    </aggregation>
   </metric>
 </event>
 
diff --git a/tools/perf/core/minidump_unittest.py b/tools/perf/core/minidump_unittest.py
index 1d5cadeb..d113be68 100644
--- a/tools/perf/core/minidump_unittest.py
+++ b/tools/perf/core/minidump_unittest.py
@@ -6,6 +6,7 @@
 
 import logging
 import os
+import platform
 import sys
 import time
 
@@ -64,10 +65,16 @@
   # still read-only, so skip the test in that case.
   @decorators.Disabled(
       'chromeos-local',
-      'mac',  # https://crbug.com/1271097
       'win7'  # https://crbug.com/1084931
   )
   def testSymbolizeMinidump(self):
+    # This test currently does not work properly on ARM-based Macs, and there
+    # isn't currently a way to distinguish between architectures in the Disabled
+    # decorator.
+    if sys.platform == 'darwin' and platform.machine() == 'aarch64':
+      logging.warning('Short-circuiting test due to running on ARM-based Mac')
+      return
+
     # Wait for the browser to restart fully before crashing
     self._LoadPageThenWait('var sam = "car";', 'sam')
     self._browser.tabs.New().Navigate('chrome://gpucrash', timeout=10)
@@ -110,10 +117,16 @@
   # still read-only, so skip the test in that case.
   @decorators.Disabled(
       'chromeos-local',
-      'mac',  # https://crbug.com/1271097
       'win7'  # https://crbug.com/1084931
   )
   def testMultipleCrashMinidumps(self):
+    # This test currently does not work properly on ARM-based Macs, and there
+    # isn't currently a way to distinguish between architectures in the Disabled
+    # decorator.
+    if sys.platform == 'darwin' and platform.machine() == 'aarch64':
+      logging.warning('Short-circuiting test due to running on ARM-based Mac')
+      return
+
     # Wait for the browser to restart fully before crashing
     self._LoadPageThenWait('var cat = "dog";', 'cat')
     self._browser.tabs.New().Navigate('chrome://gpucrash', timeout=10)
@@ -204,7 +217,6 @@
   # still read-only, so skip the test in that case.
   @decorators.Disabled(
       'chromeos-local',
-      'mac',  # https://crbug.com/1271097
       'win7'  # https://crbug.com/1084931
   )
   def testMinidumpFromRendererHang(self):
@@ -214,6 +226,13 @@
     and GPU processes in such cases so we can get minidumps for diagnosing the
     root cause.
     """
+    # This test currently does not work properly on ARM-based Macs, and there
+    # isn't currently a way to distinguish between architectures in the Disabled
+    # decorator.
+    if sys.platform == 'darwin' and platform.machine() == 'aarch64':
+      logging.warning('Short-circuiting test due to running on ARM-based Mac')
+      return
+
     self._LoadPageThenWait('var cat = "dog";', 'cat')
     try:
       self._browser.tabs[-1].Navigate('chrome://hang', timeout=10)
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index 06f34427..7a88e84 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -13,16 +13,16 @@
             "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux_arm/49b4b5dcbc312d8d2c3751cf29238b8efeb4e494/trace_processor_shell"
         },
         "mac": {
-            "hash": "4cefd62893a9c1f47a57c4b0f4f3e1a16024f931",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/c69474dfc2a2002dd20ca6a62d65bb33757371f8/trace_processor_shell"
+            "hash": "74f09cd383409435cefcfcc2efa2a84f22a6ee73",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/mac/14feb311906a9adf5ada506bc925c31637ba8983/trace_processor_shell"
         },
         "mac_arm64": {
             "hash": "e1ad4861384b06d911a65f035317914b8cc975c6",
             "full_remote_path": "perfetto-luci-artifacts/v25.0/mac-arm64/trace_processor_shell"
         },
         "linux": {
-            "hash": "9d3f67f2c963b514c56233a2b64ffab2c99e640f",
-            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/478630351306440b9bfb971ee7914704fecacd75/trace_processor_shell"
+            "hash": "934cf34f7a8806c89e45154e37de015add18768e",
+            "full_remote_path": "chromium-telemetry/perfetto_binaries/trace_processor_shell/linux/14feb311906a9adf5ada506bc925c31637ba8983/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index fa66d8a..ec68aea2 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -379,6 +379,7 @@
 crbug.com/1277985 [ android-pixel-2 ] system_health.common_mobile/browse:media:googleplaystore:2019 [ Skip ]
 crbug.com/1297656 [ android ] system_health.common_mobile/background:tools:gmail:2019 [ Skip ]
 crbug.com/1302665 [ android ] system_health.common_mobile/browse:social:pinterest_infinite_scroll:2021 [ Skip ]
+crbug.com/1321927 [ android-pixel-4 ] system_health.common_mobile/browse:media:googleplaystore:2019 [ Skip ]
 
 # Benchmark: system_health.memory_desktop
 crbug.com/649392 system_health.memory_desktop/play:media:google_play_music [ Skip ]
@@ -517,6 +518,7 @@
 crbug.com/1297656 [ android ] system_health.memory_mobile/background:tools:gmail:2019 [ Skip ]
 crbug.com/1302665 [ android ] system_health.memory_mobile/browse:social:pinterest_infinite_scroll:2021 [ Skip ]
 crbug.com/1319392 [ android-go ] system_health.memory_mobile/browse:news:washingtonpost:2019 [ Skip ]
+crbug.com/1321927 [ android-pixel-2 ] system_health.memory_mobile/browse:media:googleplaystore:2019 [ Skip ]
 
 # Benchmark: system_health.pcscan
 crbug.com/v8/11180 [ android ] system_health.pcscan/browse:news:cnn:2021 [ Skip ]
@@ -616,6 +618,8 @@
 crbug.com/1277994 [ android-go ] v8.browsing_mobile/browse:shopping:flipkart:2019 [ Skip ]
 crbug.com/1277994 [ android-go ] v8.browsing_mobile/browse:shopping:amazon:2019 [ Skip ]
 crbug.com/1302665 [ android ] v8.browsing_mobile/browse:social:pinterest_infinite_scroll:2021 [ Skip ]
+crbug.com/1321927 [ android-pixel-2 ] v8.browsing_mobile/browse:media:googleplaystore:2019 [ Skip ]
+crbug.com/1321927 [ android-pixel-4 ] v8.browsing_mobile/browse:media:googleplaystore:2019 [ Skip ]
 
 # Benchmark: v8.browsing_mobile-future (keep in sync with v8.browsing_mobile above)
 crbug.com/958034 [ android-go android-webview ] v8.browsing_mobile-future/* [ Skip ]
@@ -633,6 +637,8 @@
 crbug.com/1241400 [ android-webview ] v8.browsing_mobile-future/browse:news:businessinsider:2021 [ Skip ]
 crbug.com/1259411 [ android-pixel-2 ] v8.browsing_mobile-future/browse:news:businessinsider:2021 [ Skip ]
 crbug.com/1302665 [ android ] v8.browsing_mobile-future/browse:social:pinterest_infinite_scroll:2021 [ Skip ]
+crbug.com/1321927 [ android-pixel-2 ] v8.browsing_mobile-future/browse:media:googleplaystore:2019 [ Skip ]
+crbug.com/1321927 [ android-pixel-4 ] v8.browsing_mobile-future/browse:media:googleplaystore:2019 [ Skip ]
 
 # Benchmark: v8.runtime_stats.top_25
 crbug.com/954229 [ mac ] v8.runtime_stats.top_25/* [ Skip ]
diff --git a/tools/polymer/generate_gn_v3.py b/tools/polymer/generate_gn_v3.py
index 74546b3..75204f3d 100755
--- a/tools/polymer/generate_gn_v3.py
+++ b/tools/polymer/generate_gn_v3.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # 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.
@@ -30,49 +30,50 @@
 
 
 def main(created_by, input_files):
-    targets = ''
+  targets = ''
 
-    def _target_name(target_file):
-      return target_file[:-len('.js')]
+  def _target_name(target_file):
+    return target_file[:-len('.js')]
 
-    def _extract_imports(input_file):
-      path_to_acorn = path.join('node_modules', 'acorn', 'bin', 'acorn');
-      ast = node.RunNode([path_to_acorn, '--module', input_file])
-      imports = map(lambda n: n['source']['raw'][1:-1],
-          filter(lambda n: n['type'] ==
-              'ImportDeclaration', json.loads(ast)['body']))
-      return set(imports)
+  def _extract_imports(input_file):
+    path_to_acorn = path.join('node_modules', 'acorn', 'bin', 'acorn')
+    ast = node.RunNode([path_to_acorn, '--module', input_file])
+    imports = map(
+        lambda n: n['source']['raw'][1:-1],
+        filter(lambda n: n['type'] == 'ImportDeclaration',
+               json.loads(ast)['body']))
+    return set(imports)
 
-    for input_file in sorted(input_files, key=_target_name):
-      input_base = path.basename(input_file)
-      imports = _extract_imports(input_file)
-      dependencies = []
-      externs = ''
+  for input_file in sorted(input_files, key=_target_name):
+    input_base = path.basename(input_file)
+    imports = _extract_imports(input_file)
+    dependencies = []
+    externs = ''
 
-      for i in sorted(imports):
-        import_dir, import_base = path.split(i.encode('ascii'))
+    for i in sorted(imports):
+      import_dir, import_base = path.split(i)
 
-        # Redirect dependencies to minified Polymer to the non-minified version.
-        if import_base == 'polymer_bundled.min.js':
-          import_base = 'polymer_bundled.js'
+      # Redirect dependencies to minified Polymer to the non-minified version.
+      if import_base == 'polymer_bundled.min.js':
+        import_base = 'polymer_bundled.js'
 
-        target = ':' + _target_name(import_base)
-        dependencies.append(import_dir + target)
+      target = ':' + _target_name(import_base)
+      dependencies.append(import_dir + target)
 
-      targets += '\njs_library("%s") {' % _target_name(input_base)
-      if dependencies:
-        targets += '\n  deps = ['
-        targets += '\n    "%s",' % '",\n    "'.join(dependencies)
-        targets += '\n  ]'
-      targets += externs
-      targets += '\n}\n'
+    targets += '\njs_library("%s") {' % _target_name(input_base)
+    if dependencies:
+      targets += '\n  deps = ['
+      targets += '\n    "%s",' % '",\n    "'.join(dependencies)
+      targets += '\n  ]'
+    targets += externs
+    targets += '\n}\n'
 
-    targets = targets.strip()
+  targets = targets.strip()
 
-    if targets:
-      current_year = date.today().year
-      print(_COMPILED_RESOURCES_TEMPLATE % (current_year, created_by,
-                                            _COMPILE_JS, targets))
+  if targets:
+    current_year = date.today().year
+    print(_COMPILED_RESOURCES_TEMPLATE %
+          (current_year, created_by, _COMPILE_JS, targets))
 
 
 if __name__ == '__main__':
diff --git a/tools/typescript/PRESUBMIT.py b/tools/typescript/PRESUBMIT.py
index 05046d6..507860c 100644
--- a/tools/typescript/PRESUBMIT.py
+++ b/tools/typescript/PRESUBMIT.py
@@ -17,7 +17,7 @@
   return input_api.canned_checks.RunUnitTests(input_api,
                                               output_api,
                                               tests,
-                                              skip_shebang_check=True)
+                                              run_on_python2=False)
 
 
 def _CheckChangeOnUploadOrCommit(input_api, output_api):
diff --git a/tools/typescript/tests/project5/bar.ts b/tools/typescript/tests/project5/bar.ts
new file mode 100644
index 0000000..019fc39
--- /dev/null
+++ b/tools/typescript/tests/project5/bar.ts
@@ -0,0 +1,7 @@
+// Copyright 2022 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.
+
+export function foo(): string {
+  return 'foo';
+}
diff --git a/tools/typescript/tests/project5/tsconfig_base.json b/tools/typescript/tests/project5/tsconfig_base.json
new file mode 100644
index 0000000..1aed0062
--- /dev/null
+++ b/tools/typescript/tests/project5/tsconfig_base.json
@@ -0,0 +1,6 @@
+{
+  "extends": "../../tsconfig_base.json",
+  "compilerOptions": {
+    "composite": true
+  }
+}
diff --git a/tools/typescript/ts_library.py b/tools/typescript/ts_library.py
index e0e8671e..3708925 100644
--- a/tools/typescript/ts_library.py
+++ b/tools/typescript/ts_library.py
@@ -8,6 +8,7 @@
 import os
 import re
 import sys
+import io
 
 _CWD = os.getcwd()
 _HERE_DIR = os.path.dirname(__file__)
@@ -27,6 +28,18 @@
   return
 
 
+def _validate_tsconfig_json(tsconfig_file):
+  with io.open(tsconfig_file, encoding='utf-8', mode='r') as f:
+    tsconfig = json.loads(f.read())
+
+    if 'compilerOptions' in tsconfig and \
+        'composite' in tsconfig['compilerOptions']:
+      return False, f'Invalid |composite| flag detected in {tsconfig_file}.' + \
+          ' Use the dedicated |composite=true| attribute in ts_library() ' + \
+          'instead.'
+  return True, None
+
+
 def main(argv):
   parser = argparse.ArgumentParser()
   parser.add_argument('--deps', nargs='*')
@@ -51,6 +64,13 @@
       if args.tsconfig_base is not None \
       else os.path.relpath(TSCONFIG_BASE_PATH, args.gen_dir)
 
+  tsconfig_base_file = os.path.normpath(
+      os.path.join(args.gen_dir, tsconfig['extends']))
+
+  is_tsconfig_valid, error = _validate_tsconfig_json(tsconfig_base_file)
+  if not is_tsconfig_valid:
+    raise AssertionError(error)
+
   tsconfig['compilerOptions'] = collections.OrderedDict()
   tsconfig['compilerOptions']['rootDir'] = root_dir
   tsconfig['compilerOptions']['outDir'] = out_dir
diff --git a/tools/typescript/ts_library_test.py b/tools/typescript/ts_library_test.py
index 7415ba5..d9fac64 100755
--- a/tools/typescript/ts_library_test.py
+++ b/tools/typescript/ts_library_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
 # 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.
@@ -216,6 +216,31 @@
     else:
       self.fail('Failed to detect type error')
 
+  # Test error case where the project's tsconfig file is failing validation.
+  def testTsConfigValidationError(self):
+    self._out_folder = tempfile.mkdtemp(dir=_HERE_DIR)
+    root_dir = os.path.join(_HERE_DIR, 'tests', 'project5')
+    gen_dir = os.path.join(self._out_folder, 'project5')
+    try:
+      ts_library.main([
+          '--root_dir',
+          root_dir,
+          '--gen_dir',
+          gen_dir,
+          '--out_dir',
+          gen_dir,
+          '--in_files',
+          'bar.ts',
+          '--tsconfig_base',
+          os.path.relpath(os.path.join(root_dir, 'tsconfig_base.json'),
+                          gen_dir),
+      ])
+    except AssertionError as err:
+      self.assertTrue(
+          str(err).startswith('Invalid |composite| flag detected in '))
+    else:
+      self.fail('Failed to detect error')
+
 
 if __name__ == '__main__':
   unittest.main()
diff --git a/ui/accessibility/ax_position.h b/ui/accessibility/ax_position.h
index 9eaf0df8..e1e956b2 100644
--- a/ui/accessibility/ax_position.h
+++ b/ui/accessibility/ax_position.h
@@ -1644,14 +1644,8 @@
   // When blink is asked to set selection, it expects a text position to be
   // anchored to the text node (otherwise a generic tree position is assumed
   // and the offset is interpreted as a child index).
-  //
-  // Using just AsLeafTextPosition() for sanitizing does not work on plain
-  // text-fields: an attempt to select the text beyond the first line results
-  // in a wrong selection which looks as if the text offset was counted through
-  // the first line only.
-  // TODO(nektar): Make this work in plain text fields too.
   AXPositionInstance AsDomSelectionPosition() const {
-    if (IsNullPosition())
+    if (IsNullPosition() || GetAnchor()->data().IsAtomicTextField())
       return Clone();
 
     AXPositionInstance text_position = AsLeafTextPosition();
diff --git a/ui/accessibility/platform/inspect/ax_script_instruction.cc b/ui/accessibility/platform/inspect/ax_script_instruction.cc
index 52162e8..34122ff 100644
--- a/ui/accessibility/platform/inspect/ax_script_instruction.cc
+++ b/ui/accessibility/platform/inspect/ax_script_instruction.cc
@@ -13,9 +13,11 @@
 
 namespace ui {
 
-const char kPrintTree[] = "print tree";
-const char kWaitFor[] = "wait for ";
-const size_t kWaitForLength = sizeof(kWaitFor) / sizeof(kWaitFor[0]) - 1;
+constexpr char kPrintTree[] = "print tree";
+constexpr char kWaitFor[] = "wait for ";
+constexpr size_t kWaitForLength = sizeof(kWaitFor) / sizeof(kWaitFor[0]) - 1;
+constexpr char kPress[] = "press ";
+constexpr size_t kPressLength = sizeof(kPress) / sizeof(kPress[0]) - 1;
 
 AXScriptInstruction::AXScriptInstruction(const std::string& instruction)
     : instruction_(instruction) {}
@@ -23,8 +25,11 @@
 bool AXScriptInstruction::IsEvent() const {
   return !IsComment() && EventNameStartIndex() != std::string::npos;
 }
+bool AXScriptInstruction::IsKeyEvent() const {
+  return base::StartsWith(instruction_, kPress);
+}
 bool AXScriptInstruction::IsScript() const {
-  return !IsComment() && !IsEvent() && !IsPrintTree();
+  return !IsComment() && !IsEvent() && !IsKeyEvent() && !IsPrintTree();
 }
 bool AXScriptInstruction::IsComment() const {
   return base::StartsWith(instruction_, "//");
@@ -43,6 +48,11 @@
   return instruction_.substr(kWaitForLength);
 }
 
+std::string AXScriptInstruction::AsDomKeyString() const {
+  DCHECK(IsKeyEvent());
+  return instruction_.substr(kPressLength);
+}
+
 std::string AXScriptInstruction::AsComment() const {
   DCHECK(IsComment());
   return instruction_;
diff --git a/ui/accessibility/platform/inspect/ax_script_instruction.h b/ui/accessibility/platform/inspect/ax_script_instruction.h
index 783d076..f2039bb 100644
--- a/ui/accessibility/platform/inspect/ax_script_instruction.h
+++ b/ui/accessibility/platform/inspect/ax_script_instruction.h
@@ -21,11 +21,16 @@
   explicit AXScriptInstruction(const std::string& instruction);
 
   bool IsEvent() const;
+  bool IsKeyEvent() const;
   bool IsScript() const;
   bool IsComment() const;
   bool IsPrintTree() const;
 
   AXPropertyNode AsScript() const;
+  // Returns a character string containing either
+  // - a key name from http://www.w3.org/TR/DOM-Level-3-Events-key/, or
+  // - a single Unicode character (represented in UTF-8).
+  std::string AsDomKeyString() const;
   std::string AsEvent() const;
   std::string AsComment() const;
 
diff --git a/ui/accessibility/platform/inspect/ax_script_instruction_unittest.cc b/ui/accessibility/platform/inspect/ax_script_instruction_unittest.cc
index 4e483824..477e6573 100644
--- a/ui/accessibility/platform/inspect/ax_script_instruction_unittest.cc
+++ b/ui/accessibility/platform/inspect/ax_script_instruction_unittest.cc
@@ -12,13 +12,15 @@
 TEST(AXScriptInstructionTest, Parse) {
   AXScriptInstruction script("textbox.AXRole");
   EXPECT_TRUE(script.IsScript());
-  EXPECT_FALSE(script.IsEvent());
+  EXPECT_FALSE(script.IsKeyEvent());
+  EXPECT_FALSE(script.IsKeyEvent());
   EXPECT_FALSE(script.IsComment());
   EXPECT_FALSE(script.IsPrintTree());
   EXPECT_EQ(script.AsScript().ToString(), "textbox.AXRole");
 
   AXScriptInstruction event("wait for AXTitleChange");
   EXPECT_TRUE(event.IsEvent());
+  EXPECT_FALSE(event.IsKeyEvent());
   EXPECT_FALSE(event.IsScript());
   EXPECT_FALSE(event.IsComment());
   EXPECT_FALSE(event.IsPrintTree());
@@ -26,6 +28,7 @@
 
   AXScriptInstruction event2("wait for AXTitleChange on AXButton");
   EXPECT_TRUE(event2.IsEvent());
+  EXPECT_FALSE(event2.IsKeyEvent());
   EXPECT_FALSE(event2.IsScript());
   EXPECT_FALSE(event2.IsComment());
   EXPECT_FALSE(event2.IsPrintTree());
@@ -33,6 +36,7 @@
 
   AXScriptInstruction printTree("print tree");
   EXPECT_FALSE(printTree.IsEvent());
+  EXPECT_FALSE(printTree.IsKeyEvent());
   EXPECT_FALSE(printTree.IsScript());
   EXPECT_FALSE(printTree.IsComment());
   EXPECT_TRUE(printTree.IsPrintTree());
@@ -40,9 +44,26 @@
   AXScriptInstruction comment("// wait for AXTitleChange");
   EXPECT_TRUE(comment.IsComment());
   EXPECT_FALSE(comment.IsEvent());
+  EXPECT_FALSE(comment.IsKeyEvent());
   EXPECT_FALSE(comment.IsScript());
   EXPECT_FALSE(comment.IsPrintTree());
   EXPECT_EQ(comment.AsComment(), "// wait for AXTitleChange");
+
+  AXScriptInstruction comment2("// press Enter");
+  EXPECT_TRUE(comment2.IsComment());
+  EXPECT_FALSE(comment2.IsEvent());
+  EXPECT_FALSE(comment2.IsKeyEvent());
+  EXPECT_FALSE(comment2.IsScript());
+  EXPECT_FALSE(comment2.IsPrintTree());
+  EXPECT_EQ(comment2.AsComment(), "// press Enter");
+
+  AXScriptInstruction keypress("press Enter");
+  EXPECT_TRUE(keypress.IsKeyEvent());
+  EXPECT_FALSE(keypress.IsComment());
+  EXPECT_FALSE(keypress.IsEvent());
+  EXPECT_FALSE(keypress.IsScript());
+  EXPECT_FALSE(keypress.IsPrintTree());
+  EXPECT_EQ(keypress.AsDomKeyString(), "Enter");
 }
 
 }  // namespace ui
diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn
index b87e5e6..8c2b4e7a 100644
--- a/ui/base/BUILD.gn
+++ b/ui/base/BUILD.gn
@@ -138,6 +138,8 @@
     "models/dialog_model_field.cc",
     "models/dialog_model_field.h",
     "models/dialog_model_host.h",
+    "models/dialog_model_menu_model_adapter.cc",
+    "models/dialog_model_menu_model_adapter.h",
     "models/image_model.cc",
     "models/image_model.h",
     "models/list_model.h",
diff --git a/ui/base/models/dialog_model.cc b/ui/base/models/dialog_model.cc
index 30b1acd5..8985208 100644
--- a/ui/base/models/dialog_model.cc
+++ b/ui/base/models/dialog_model.cc
@@ -108,6 +108,14 @@
   AddField(std::make_unique<DialogModelSeparator>(GetPassKey(), this));
 }
 
+void DialogModel::AddMenuItem(ImageModel icon,
+                              std::u16string label,
+                              base::RepeatingCallback<void(int)> callback) {
+  AddField(std::make_unique<DialogModelMenuItem>(
+      GetPassKey(), this, std::move(icon), std::move(label),
+      std::move(callback)));
+}
+
 void DialogModel::AddTextfield(std::u16string label,
                                std::u16string text,
                                const DialogModelTextfield::Params& params) {
diff --git a/ui/base/models/dialog_model.h b/ui/base/models/dialog_model.h
index 3dca06b..2378b565b4 100644
--- a/ui/base/models/dialog_model.h
+++ b/ui/base/models/dialog_model.h
@@ -9,6 +9,7 @@
 #include <string>
 
 #include "base/callback.h"
+#include "base/callback_forward.h"
 #include "base/component_export.h"
 #include "base/memory/raw_ptr.h"
 #include "base/types/pass_key.h"
@@ -220,6 +221,15 @@
       return *this;
     }
 
+    // Adds a menu item. See DialogModel::AddMenuItem().
+    Builder& AddMenuItem(ImageModel icon,
+                         std::u16string label,
+                         base::RepeatingCallback<void(int)> callback) {
+      model_->AddMenuItem(std::move(icon), std::move(label),
+                          std::move(callback));
+      return *this;
+    }
+
     // Adds a separator. See DialogModel::AddSeparator().
     Builder& AddSeparator() {
       model_->AddSeparator();
@@ -278,6 +288,11 @@
                    const DialogModelCombobox::Params& params =
                        DialogModelCombobox::Params());
 
+  // Adds a menu item at the end of the dialog model.
+  void AddMenuItem(ImageModel icon,
+                   std::u16string label,
+                   base::RepeatingCallback<void(int)> callback);
+
   // Adds a separator at the end of the dialog model.
   void AddSeparator();
 
diff --git a/ui/base/models/dialog_model_field.cc b/ui/base/models/dialog_model_field.cc
index 3193573..0f33cc3 100644
--- a/ui/base/models/dialog_model_field.cc
+++ b/ui/base/models/dialog_model_field.cc
@@ -89,6 +89,16 @@
   return AsTextfield();
 }
 
+const DialogModelMenuItem* DialogModelField::AsMenuItem(
+    base::PassKey<DialogModelHost>) const {
+  return AsMenuItem();
+}
+
+DialogModelMenuItem* DialogModelField::AsMenuItem(
+    base::PassKey<DialogModelHost>) {
+  return const_cast<DialogModelMenuItem*>(AsMenuItem());
+}
+
 DialogModelCustomField* DialogModelField::AsCustomField(
     base::PassKey<DialogModelHost>) {
   return AsCustomField();
@@ -114,6 +124,11 @@
   return static_cast<DialogModelCombobox*>(this);
 }
 
+const DialogModelMenuItem* DialogModelField::AsMenuItem() const {
+  DCHECK_EQ(type_, kMenuItem);
+  return static_cast<const DialogModelMenuItem*>(this);
+}
+
 DialogModelTextfield* DialogModelField::AsTextfield() {
   DCHECK_EQ(type_, kTextfield);
   return static_cast<DialogModelTextfield*>(this);
@@ -238,6 +253,25 @@
     callback_.Run();
 }
 
+DialogModelMenuItem::DialogModelMenuItem(
+    base::PassKey<DialogModel> pass_key,
+    DialogModel* model,
+    ImageModel icon,
+    std::u16string label,
+    base::RepeatingCallback<void(int)> callback)
+    : DialogModelField(pass_key, model, kMenuItem, -1, {}),
+      icon_(std::move(icon)),
+      label_(std::move(label)),
+      callback_(std::move(callback)) {}
+
+DialogModelMenuItem::~DialogModelMenuItem() = default;
+
+void DialogModelMenuItem::OnActivated(base::PassKey<DialogModelHost> pass_key,
+                                      int event_flags) {
+  DCHECK(callback_);
+  callback_.Run(event_flags);
+}
+
 DialogModelSeparator::DialogModelSeparator(base::PassKey<DialogModel> pass_key,
                                            DialogModel* model)
     : DialogModelField(pass_key, model, kSeparator, -1, {}) {}
diff --git a/ui/base/models/dialog_model_field.h b/ui/base/models/dialog_model_field.h
index e17aa3e..f69e0c1 100644
--- a/ui/base/models/dialog_model_field.h
+++ b/ui/base/models/dialog_model_field.h
@@ -15,6 +15,7 @@
 #include "base/types/pass_key.h"
 #include "ui/base/accelerators/accelerator.h"
 #include "ui/base/models/combobox_model.h"
+#include "ui/base/models/image_model.h"
 
 namespace ui {
 
@@ -25,6 +26,7 @@
 class DialogModelCombobox;
 class DialogModelCustomField;
 class DialogModelHost;
+class DialogModelMenuItem;
 class DialogModelTextfield;
 class Event;
 
@@ -113,6 +115,7 @@
     kCheckbox,
     kCombobox,
     kCustom,
+    kMenuItem,
     kSeparator,
     kTextfield
   };
@@ -133,6 +136,8 @@
   DialogModelBodyText* AsBodyText(base::PassKey<DialogModelHost>);
   DialogModelCheckbox* AsCheckbox(base::PassKey<DialogModelHost>);
   DialogModelCombobox* AsCombobox(base::PassKey<DialogModelHost>);
+  DialogModelMenuItem* AsMenuItem(base::PassKey<DialogModelHost>);
+  const DialogModelMenuItem* AsMenuItem(base::PassKey<DialogModelHost>) const;
   DialogModelTextfield* AsTextfield(base::PassKey<DialogModelHost>);
   DialogModelCustomField* AsCustomField(base::PassKey<DialogModelHost>);
 
@@ -149,6 +154,7 @@
   DialogModelBodyText* AsBodyText();
   DialogModelCheckbox* AsCheckbox();
   DialogModelCombobox* AsCombobox();
+  const DialogModelMenuItem* AsMenuItem() const;
   DialogModelTextfield* AsTextfield();
   DialogModelCustomField* AsCustomField();
 
@@ -351,6 +357,37 @@
   base::RepeatingClosure callback_;
 };
 
+// Field class representing a menu item:
+//
+//     <icon> <label>
+// Ex: [icon] Open URL
+class COMPONENT_EXPORT(UI_BASE) DialogModelMenuItem : public DialogModelField {
+ public:
+  // Note that this is constructed through a DialogModel which adds it to model
+  // fields.
+  DialogModelMenuItem(base::PassKey<DialogModel> pass_key,
+                      DialogModel* model,
+                      ImageModel icon,
+                      std::u16string label,
+                      base::RepeatingCallback<void(int)> callback);
+  DialogModelMenuItem(const DialogModelMenuItem&) = delete;
+  DialogModelMenuItem& operator=(const DialogModelMenuItem&) = delete;
+  ~DialogModelMenuItem() override;
+
+  // Methods with base::PassKey<DialogModelHost> are only intended to be called
+  // by the DialogModelHost implementation.
+  const ImageModel& icon(base::PassKey<DialogModelHost>) const { return icon_; }
+  const std::u16string& label(base::PassKey<DialogModelHost>) const {
+    return label_;
+  }
+  void OnActivated(base::PassKey<DialogModelHost>, int event_flags);
+
+ private:
+  const ImageModel icon_;
+  const std::u16string label_;
+  base::RepeatingCallback<void(int)> callback_;
+};
+
 // Field class representing a separator.
 class COMPONENT_EXPORT(UI_BASE) DialogModelSeparator : public DialogModelField {
  public:
diff --git a/ui/base/models/dialog_model_menu_model_adapter.cc b/ui/base/models/dialog_model_menu_model_adapter.cc
new file mode 100644
index 0000000..666cb90
--- /dev/null
+++ b/ui/base/models/dialog_model_menu_model_adapter.cc
@@ -0,0 +1,128 @@
+// 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 "ui/base/models/dialog_model_menu_model_adapter.h"
+
+#include "ui/base/models/dialog_model.h"
+
+namespace ui {
+
+DialogModelMenuModelAdapter::DialogModelMenuModelAdapter(
+    std::unique_ptr<DialogModel> model)
+    : model_(std::move(model)) {}
+
+DialogModelMenuModelAdapter::~DialogModelMenuModelAdapter() = default;
+
+void DialogModelMenuModelAdapter::Close() {
+  // TODO(pbos): Implement, or document why menus can't be closed through this
+  // interface.
+  NOTREACHED();
+}
+
+void DialogModelMenuModelAdapter::OnFieldAdded(DialogModelField* field) {
+  NOTREACHED();
+}
+
+bool DialogModelMenuModelAdapter::HasIcons() const {
+  const auto& fields = model_->fields(GetPassKey());
+  for (const auto& field : fields) {
+    if (field->type(GetPassKey()) != DialogModelField::kMenuItem)
+      continue;
+    if (!field->AsMenuItem(GetPassKey())->icon(GetPassKey()).IsEmpty())
+      return true;
+  }
+
+  return false;
+}
+
+int DialogModelMenuModelAdapter::GetItemCount() const {
+  return static_cast<int>(model_->fields(GetPassKey()).size());
+}
+
+MenuModel::ItemType DialogModelMenuModelAdapter::GetTypeAt(int index) const {
+  return GetField(index)->type(GetPassKey()) == DialogModelField::kSeparator
+             ? TYPE_SEPARATOR
+             : TYPE_COMMAND;
+}
+
+MenuSeparatorType DialogModelMenuModelAdapter::GetSeparatorTypeAt(
+    int index) const {
+  NOTREACHED();
+  return MenuSeparatorType::NORMAL_SEPARATOR;
+}
+
+int DialogModelMenuModelAdapter::GetCommandIdAt(int index) const {
+  // TODO(pbos): Figure out what this should be. Combobox seems to offset by
+  // 1000. Dunno why.
+  return index + 1234;
+}
+
+std::u16string DialogModelMenuModelAdapter::GetLabelAt(int index) const {
+  return GetField(index)->AsMenuItem(GetPassKey())->label(GetPassKey());
+}
+
+bool DialogModelMenuModelAdapter::IsItemDynamicAt(int index) const {
+  return false;
+}
+
+bool DialogModelMenuModelAdapter::GetAcceleratorAt(
+    int index,
+    ui::Accelerator* accelerator) const {
+  // TODO(pbos): Add support for accelerators.
+  return false;
+}
+
+bool DialogModelMenuModelAdapter::IsItemCheckedAt(int index) const {
+  // TODO(pbos): Add support for checkbox items.
+  return false;
+}
+
+int DialogModelMenuModelAdapter::GetGroupIdAt(int index) const {
+  NOTREACHED();
+  return -1;
+}
+
+ImageModel DialogModelMenuModelAdapter::GetIconAt(int index) const {
+  return GetField(index)->AsMenuItem(GetPassKey())->icon(GetPassKey());
+}
+
+ButtonMenuItemModel* DialogModelMenuModelAdapter::GetButtonMenuItemAt(
+    int index) const {
+  NOTREACHED();
+  return nullptr;
+}
+
+bool DialogModelMenuModelAdapter::IsEnabledAt(int index) const {
+  DCHECK_LT(index, GetItemCount());
+  return GetField(index)->type(GetPassKey()) != DialogModelField::kSeparator;
+}
+
+MenuModel* DialogModelMenuModelAdapter::GetSubmenuModelAt(int index) const {
+  NOTREACHED();
+  return nullptr;
+}
+
+void DialogModelMenuModelAdapter::ActivatedAt(int index) {
+  // If this flags investigate why the ActivatedAt(index, event_flags) isn't
+  // being called.
+  NOTREACHED();
+}
+
+void DialogModelMenuModelAdapter::ActivatedAt(int index, int event_flags) {
+  DialogModelMenuItem* menu_item = GetField(index)->AsMenuItem(GetPassKey());
+  menu_item->OnActivated(GetPassKey(), event_flags);
+}
+
+const DialogModelField* DialogModelMenuModelAdapter::GetField(int index) const {
+  DCHECK_GE(index, 0);
+  DCHECK_LT(index, GetItemCount());
+  return model_->fields(GetPassKey())[index].get();
+}
+
+DialogModelField* DialogModelMenuModelAdapter::GetField(int index) {
+  return const_cast<DialogModelField*>(
+      const_cast<const DialogModelMenuModelAdapter*>(this)->GetField(index));
+}
+
+}  // namespace ui
\ No newline at end of file
diff --git a/ui/base/models/dialog_model_menu_model_adapter.h b/ui/base/models/dialog_model_menu_model_adapter.h
new file mode 100644
index 0000000..c4ad50b
--- /dev/null
+++ b/ui/base/models/dialog_model_menu_model_adapter.h
@@ -0,0 +1,55 @@
+// 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 UI_BASE_MODELS_DIALOG_MODEL_MENU_MODEL_ADAPTER_H_
+#define UI_BASE_MODELS_DIALOG_MODEL_MENU_MODEL_ADAPTER_H_
+
+#include <memory>
+
+#include "ui/base/models/dialog_model_host.h"
+#include "ui/base/models/menu_model.h"
+
+namespace ui {
+
+class DialogModel;
+
+class COMPONENT_EXPORT(UI_BASE) DialogModelMenuModelAdapter final
+    : public DialogModelHost,
+      public MenuModel {
+ public:
+  explicit DialogModelMenuModelAdapter(std::unique_ptr<DialogModel> model);
+  ~DialogModelMenuModelAdapter() override;
+
+  // DialogModelHost:
+  void Close() override;
+  void OnFieldAdded(DialogModelField* field) override;
+
+  // MenuModel:
+  bool HasIcons() const override;
+  int GetItemCount() const override;
+  ItemType GetTypeAt(int index) const override;
+  ui::MenuSeparatorType GetSeparatorTypeAt(int index) const override;
+  int GetCommandIdAt(int index) const override;
+  std::u16string GetLabelAt(int index) const override;
+  bool IsItemDynamicAt(int index) const override;
+  bool GetAcceleratorAt(int index, ui::Accelerator* accelerator) const override;
+  bool IsItemCheckedAt(int index) const override;
+  int GetGroupIdAt(int index) const override;
+  ImageModel GetIconAt(int index) const override;
+  ButtonMenuItemModel* GetButtonMenuItemAt(int index) const override;
+  bool IsEnabledAt(int index) const override;
+  MenuModel* GetSubmenuModelAt(int index) const override;
+  void ActivatedAt(int index) override;
+  void ActivatedAt(int index, int event_flags) override;
+
+ private:
+  const DialogModelField* GetField(int index) const;
+  DialogModelField* GetField(int index);
+
+  std::unique_ptr<DialogModel> model_;
+};
+
+}  // namespace ui
+
+#endif  // UI_BASE_MODELS_DIALOG_MODEL_MENU_MODEL_ADAPTER_H_
diff --git a/ui/display/mac/screen_mac.mm b/ui/display/mac/screen_mac.mm
index 7f711bd1..9056259 100644
--- a/ui/display/mac/screen_mac.mm
+++ b/ui/display/mac/screen_mac.mm
@@ -148,6 +148,8 @@
     }
     display.set_color_spaces(display_color_spaces);
   }
+  display_color_spaces.SetSDRMaxLuminanceNits(
+      gfx::ColorSpace::kDefaultSDRWhiteLevelV2);
 
   if (enable_hdr) {
     display.set_color_depth(Display::kHDR10BitsPerPixel);
diff --git a/ui/display/win/screen_win.cc b/ui/display/win/screen_win.cc
index 85f7ce0..0b661cb7 100644
--- a/ui/display/win/screen_win.cc
+++ b/ui/display/win/screen_win.cc
@@ -207,7 +207,7 @@
 // and |sdr_white_level| with default buffer formats for Windows.
 gfx::DisplayColorSpaces CreateDisplayColorSpaces(
     const gfx::ColorSpace& color_space,
-    float sdr_white_level = gfx::ColorSpace::kDefaultScrgbLinearSdrWhiteLevel) {
+    float sdr_white_level) {
   gfx::DisplayColorSpaces display_color_spaces(color_space);
   // When alpha is not needed, specify BGRX_8888 to get
   // DXGI_ALPHA_MODE_IGNORE. This saves significant power (see
@@ -262,7 +262,8 @@
   // Adjust white level to a default value irrespective of whether the color
   // space is scRGB linear (defaults to 80 nits) or PQ (defaults to 100 nits).
   const auto& color_space = GetForcedDisplayColorProfile();
-  auto display_color_spaces = CreateDisplayColorSpaces(color_space);
+  auto display_color_spaces = CreateDisplayColorSpaces(
+      color_space, gfx::ColorSpace::kDefaultSDRWhiteLevelV2);
   // Use the forced color profile's buffer format for all content usages.
   if (color_space.GetTransferID() == gfx::ColorSpace::TransferID::PQ) {
     display_color_spaces.SetOutputBufferFormats(
@@ -296,11 +297,13 @@
   if (HasForceDisplayColorProfile()) {
     color_spaces = GetForcedDisplayColorSpaces();
   } else if (hdr_enabled_on_any_display) {
-    const float sdr_white_level = display_info.sdr_white_level();
+    float sdr_white_level = display_info.sdr_white_level();
     float hdr_max_luminance_relative = 0.f;
     if (dxgi_output_desc) {
       hdr_max_luminance_relative =
           dxgi_output_desc->max_luminance / sdr_white_level;
+      if (!dxgi_output_desc->hdr_enabled)
+        sdr_white_level = gfx::ColorSpace::kDefaultSDRWhiteLevelV2;
     }
     hdr_max_luminance_relative = std::max(hdr_max_luminance_relative,
                                           kMinHDRCapableMaxLuminanceRelative);
@@ -308,7 +311,8 @@
                                                hdr_max_luminance_relative);
   } else {
     color_spaces = CreateDisplayColorSpaces(
-        color_profile_reader->GetDisplayColorSpace(display.id()));
+        color_profile_reader->GetDisplayColorSpace(display.id()),
+        gfx::ColorSpace::kDefaultSDRWhiteLevelV2);
   }
   if (color_spaces.SupportsHDR()) {
     // These are (ab)used by pages via media query APIs to detect HDR support.
diff --git a/ui/file_manager/file_manager/background/js/volume_manager_impl.js b/ui/file_manager/file_manager/background/js/volume_manager_impl.js
index fdb81aa..b6ff594 100644
--- a/ui/file_manager/file_manager/background/js/volume_manager_impl.js
+++ b/ui/file_manager/file_manager/background/js/volume_manager_impl.js
@@ -160,8 +160,8 @@
 
     try {
       console.warn('Getting volumes');
-      let volumeMetadataList = await new Promise(
-          resolve => chrome.fileManagerPrivate.getVolumeMetadataList(resolve));
+      let volumeMetadataList =
+          await promisify(chrome.fileManagerPrivate.getVolumeMetadataList);
       if (!volumeMetadataList) {
         console.warn('Cannot get volumes');
         finishInitialization();
@@ -323,9 +323,8 @@
 
   /** @override */
   async mountArchive(fileUrl, password) {
-    const path = await new Promise(resolve => {
-      chrome.fileManagerPrivate.addMount(fileUrl, password, resolve);
-    });
+    const path =
+        await promisify(chrome.fileManagerPrivate.addMount, fileUrl, password);
     console.debug(`Mounting '${path}'`);
     const key = this.makeRequestKey_('mount', path);
     return this.startRequest_(key);
@@ -340,22 +339,16 @@
   /** @override */
   async unmount({volumeId}) {
     console.warn(`Unmounting '${volumeId}'`);
-    chrome.fileManagerPrivate.removeMount(volumeId);
     const key = this.makeRequestKey_('unmount', volumeId);
-    await this.startRequest_(key);
+    const request = this.startRequest_(key);
+    await promisify(chrome.fileManagerPrivate.removeMount, volumeId);
+    await request;
   }
 
   /** @override */
   configure(volumeInfo) {
-    return new Promise((fulfill, reject) => {
-      chrome.fileManagerPrivate.configureVolume(volumeInfo.volumeId, () => {
-        if (chrome.runtime.lastError) {
-          reject(chrome.runtime.lastError.message);
-        } else {
-          fulfill();
-        }
-      });
-    });
+    return promisify(
+        chrome.fileManagerPrivate.configureVolume, volumeInfo.volumeId);
   }
 
   /** @override */
diff --git a/ui/file_manager/file_manager/background/js/volume_manager_unittest.m.js b/ui/file_manager/file_manager/background/js/volume_manager_unittest.m.js
index 051c0675..2853f3f8 100644
--- a/ui/file_manager/file_manager/background/js/volume_manager_unittest.m.js
+++ b/ui/file_manager/file_manager/background/js/volume_manager_unittest.m.js
@@ -53,13 +53,14 @@
         callback(
             chrome.fileManagerPrivate.fileSystemMap_[options.volumeId].root);
       },
-      removeMount: function(volumeId) {
+      removeMount: function(volumeId, callback) {
         const event = {
           eventType: 'unmount',
           status: 'success',
           volumeMetadata: {volumeId: volumeId}
         };
         mockChrome.fileManagerPrivate.onMountCompleted.dispatchEvent(event);
+        callback();
       },
       onDriveConnectionStatusChanged: {
         addListener: function(listener) {
diff --git a/ui/gfx/color_conversion_sk_filter_cache.cc b/ui/gfx/color_conversion_sk_filter_cache.cc
index ac85700..49c0934 100644
--- a/ui/gfx/color_conversion_sk_filter_cache.cc
+++ b/ui/gfx/color_conversion_sk_filter_cache.cc
@@ -22,7 +22,7 @@
 namespace {
 
 const base::Feature kToneMappingV2{"ToneMappingV2",
-                                   base::FEATURE_DISABLED_BY_DEFAULT};
+                                   base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Additional YUV information to skia renderer to draw 9- and 10- bits color.
 struct YUVInput {
diff --git a/ui/gfx/color_space.cc b/ui/gfx/color_space.cc
index 241d42f7..ec01231 100644
--- a/ui/gfx/color_space.cc
+++ b/ui/gfx/color_space.cc
@@ -69,11 +69,15 @@
   if (sdr_white_level == 0.f)
     sdr_white_level = ColorSpace::kDefaultSDRWhiteLevel;
 
-  // The reference white level for HLG is 100 nits. We want to setup the
-  // returned transfer function such that output values are scaled by the white
-  // level; Skia uses the |f| transfer function parameter for this.
+  // The kHLG constant will evaluate to values in the range [0, 12].
   skcms_TransferFunction fn = SkNamedTransferFn::kHLG;
-  fn.f = ColorSpace::kDefaultSDRWhiteLevel / sdr_white_level - 1;
+
+  // The value of k is equal to kHLG evaluated at 0.75 (3.77) , divided by kHLG
+  // evaluated at 1 (12), multiplied by 203 nits. This value is selected such
+  // that a signal of 0.75 will map to the same value that a PQ signal for 203
+  // nits will map to.
+  constexpr float k = 63.84549817071231f;
+  fn.f = k / sdr_white_level - 1;
   return fn;
 }
 
@@ -733,11 +737,11 @@
       break;
     case TransferID::HLG:
       transfer_fn = GetHLGSkTransferFunction(
-          sdr_white_level.value_or(transfer_params_[0]));
+          sdr_white_level.value_or(kDefaultSDRWhiteLevelV2));
       break;
     case TransferID::PQ:
       transfer_fn = GetPQSkTransferFunction(
-          sdr_white_level.value_or(transfer_params_[0]));
+          sdr_white_level.value_or(kDefaultSDRWhiteLevelV2));
       break;
     default:
       if (!GetTransferFunction(&transfer_fn, sdr_white_level)) {
diff --git a/ui/gfx/color_space.h b/ui/gfx/color_space.h
index 5af363b7..7053160 100644
--- a/ui/gfx/color_space.h
+++ b/ui/gfx/color_space.h
@@ -262,6 +262,13 @@
                       RangeID::LIMITED);
   }
 
+  // The default number of nits for SDR white. This is used for transformations
+  // between color spaces that do not specify an SDR white for tone mapping
+  // (e.g, in 2D canvas).
+  // TODO(https://crbug.com/1286076): Replace both kDefaultSDRWhiteLevel and
+  // kDefaultScrgbLinearSdrWhiteLevel with this constant.
+  static constexpr float kDefaultSDRWhiteLevelV2 = 203.f;
+
   // On macOS and on ChromeOS, sRGB's (1,1,1) always coincides with PQ's 100
   // nits (which may not be 100 physical nits). On Windows, sRGB's (1,1,1)
   // maps to scRGB linear's (1,1,1) when the SDR white level is set to 80 nits.
diff --git a/ui/gfx/color_space_unittest.cc b/ui/gfx/color_space_unittest.cc
index 6f3e7363..355e729e 100644
--- a/ui/gfx/color_space_unittest.cc
+++ b/ui/gfx/color_space_unittest.cc
@@ -235,59 +235,45 @@
   }
 }
 
-TEST(ColorSpace, PQToSkColorSpace) {
-  ColorSpace color_space;
-  ColorSpace roundtrip_color_space;
-  float roundtrip_sdr_white_level;
-  const float kEpsilon = 1.e-5f;
+TEST(ColorSpace, PQAndHLGToSkColorSpace) {
+  const float kEpsilon = 1.0e-2f;
+  const auto hlg = ColorSpace::CreateHLG();
+  const auto pq = ColorSpace::CreateHDR10();
 
-  // We expect that when a white point is specified, the conversion from
-  // ColorSpace -> SkColorSpace -> ColorSpace be the identity. Because of
-  // rounding error, this will not quite be the case.
-  color_space = ColorSpace::CreateHDR10(50.f);
-  roundtrip_color_space = ColorSpace(*color_space.ToSkColorSpace());
-  EXPECT_TRUE(
-      roundtrip_color_space.GetSDRWhiteLevel(&roundtrip_sdr_white_level));
-  EXPECT_NEAR(50.f, roundtrip_sdr_white_level, kEpsilon);
-  EXPECT_EQ(ColorSpace::TransferID::PQ, roundtrip_color_space.GetTransferID());
+  // For each test case, `pq_signal` maps to `pq_nits`.
+  constexpr size_t kNumCases = 3;
+  float pq_signal[kNumCases] = {
+      0.508078421517399f,
+      0.5806888810416109f,
+      0.6765848107833876,
+  };
+  float pq_nits[kNumCases] = {
+      100,
+      203,
+      500,
+  };
+  const float kPQSignalFor203Nits = pq_signal[1];
+  const float kHLGSignalFor203Nits = 0.75f;
 
-  // When no white level is specified, we should get an SkColorSpace that
-  // specifies the default white level. Of note is that in the roundtrip, the
-  // value of kDefaultSDRWhiteLevel gets baked in.
-  color_space = ColorSpace::CreateHDR10();
-  roundtrip_color_space = ColorSpace(*color_space.ToSkColorSpace());
-  EXPECT_TRUE(
-      roundtrip_color_space.GetSDRWhiteLevel(&roundtrip_sdr_white_level));
-  EXPECT_NEAR(ColorSpace::kDefaultSDRWhiteLevel, roundtrip_sdr_white_level,
-              kEpsilon);
-}
+  for (size_t i = 0; i < kNumCases; ++i) {
+    const float sdr_white_level = pq_nits[i];
+    sk_sp<SkColorSpace> sk_hlg = hlg.ToSkColorSpace(sdr_white_level);
+    sk_sp<SkColorSpace> sk_pq = pq.ToSkColorSpace(sdr_white_level);
 
-TEST(ColorSpace, HLGToSkColorSpace) {
-  ColorSpace color_space;
-  ColorSpace roundtrip_color_space;
-  float roundtrip_sdr_white_level;
-  const float kEpsilon = 1.0e-3f;
+    // The PQ signal that maps to `sdr_white_level` nits should map to 1.
+    skcms_TransferFunction pq_fn = {0};
+    sk_pq->transferFn(&pq_fn);
+    EXPECT_NEAR(1.f, skcms_TransferFunction_eval(&pq_fn, pq_signal[i]),
+                kEpsilon);
 
-  // We expect that when a white point is specified, the conversion from
-  // ColorSpace -> SkColorSpace -> ColorSpace be the identity. Because of
-  // rounding error, this will not quite be the case.
-  constexpr float kSDRWhiteLevel = 50.0f;
-  color_space = ColorSpace::CreateHLG().GetWithSDRWhiteLevel(kSDRWhiteLevel);
-  roundtrip_color_space = ColorSpace(*color_space.ToSkColorSpace());
-  EXPECT_TRUE(
-      roundtrip_color_space.GetSDRWhiteLevel(&roundtrip_sdr_white_level));
-  EXPECT_FLOAT_EQ(kSDRWhiteLevel, roundtrip_sdr_white_level);
-  EXPECT_EQ(ColorSpace::TransferID::HLG, roundtrip_color_space.GetTransferID());
-
-  // When no white level is specified, we should get an SkColorSpace that
-  // specifies the default white level. Of note is that in the roundtrip, the
-  // value of kDefaultSDRWhiteLevel gets baked in.
-  color_space = ColorSpace::CreateHLG();
-  roundtrip_color_space = ColorSpace(*color_space.ToSkColorSpace());
-  EXPECT_TRUE(
-      roundtrip_color_space.GetSDRWhiteLevel(&roundtrip_sdr_white_level));
-  EXPECT_NEAR(ColorSpace::kDefaultSDRWhiteLevel, roundtrip_sdr_white_level,
-              kEpsilon);
+    // The HLG signal value of 0.75 should always map to the same value that
+    // the PQ signal for 203 nits maps to.
+    skcms_TransferFunction hlg_fn = {0};
+    sk_hlg->transferFn(&hlg_fn);
+    EXPECT_NEAR(skcms_TransferFunction_eval(&pq_fn, kPQSignalFor203Nits),
+                skcms_TransferFunction_eval(&hlg_fn, kHLGSignalFor203Nits),
+                kEpsilon);
+  }
 }
 
 TEST(ColorSpace, MixedInvalid) {
diff --git a/ui/ozone/platform/wayland/BUILD.gn b/ui/ozone/platform/wayland/BUILD.gn
index 21dc835..686a83a 100644
--- a/ui/ozone/platform/wayland/BUILD.gn
+++ b/ui/ozone/platform/wayland/BUILD.gn
@@ -14,6 +14,15 @@
 
 assert(is_linux || is_chromeos_lacros)
 
+source_set("common") {
+  sources = [
+    "common/wayland_overlay_config.cc",
+    "common/wayland_overlay_config.h",
+  ]
+
+  deps = [ "//ui/gfx" ]
+}
+
 source_set("wayland") {
   sources = [
     "client_native_pixmap_factory_wayland.cc",
@@ -207,6 +216,7 @@
   defines = [ "OZONE_IMPLEMENTATION" ]
 
   deps = [
+    ":common",
     "//base",
     "//build:chromeos_buildflags",
     "//build/config/linux/libdrm",
@@ -525,6 +535,7 @@
   ]
 
   deps = [
+    ":common",
     ":test_support",
     ":wayland",
     "//build:chromeos_buildflags",
diff --git a/ui/ozone/platform/wayland/common/wayland_overlay_config.cc b/ui/ozone/platform/wayland/common/wayland_overlay_config.cc
new file mode 100644
index 0000000..310fa6b0
--- /dev/null
+++ b/ui/ozone/platform/wayland/common/wayland_overlay_config.cc
@@ -0,0 +1,38 @@
+// Copyright 2022 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 "ui/ozone/platform/wayland/common/wayland_overlay_config.h"
+
+namespace wl {
+
+WaylandOverlayConfig::WaylandOverlayConfig() = default;
+
+WaylandOverlayConfig::WaylandOverlayConfig(WaylandOverlayConfig&& other) =
+    default;
+
+WaylandOverlayConfig::WaylandOverlayConfig(const gfx::OverlayPlaneData& data,
+                                           std::unique_ptr<gfx::GpuFence> fence,
+                                           BufferId buffer_id,
+                                           float scale_factor)
+    : z_order(data.z_order),
+      transform(data.plane_transform),
+      buffer_id(buffer_id),
+      surface_scale_factor(scale_factor),
+      bounds_rect(data.display_bounds),
+      crop_rect(data.crop_rect),
+      damage_region(data.damage_rect),
+      enable_blend(data.enable_blend),
+      opacity(data.opacity),
+      access_fence_handle(fence ? fence->GetGpuFenceHandle().Clone()
+                                : gfx::GpuFenceHandle()),
+      priority_hint(data.priority_hint),
+      rounded_clip_bounds(data.rounded_corners),
+      background_color(data.color) {}
+
+WaylandOverlayConfig& WaylandOverlayConfig::operator=(
+    WaylandOverlayConfig&& other) = default;
+
+WaylandOverlayConfig::~WaylandOverlayConfig() = default;
+
+}  // namespace wl
diff --git a/ui/ozone/platform/wayland/common/wayland_overlay_config.h b/ui/ozone/platform/wayland/common/wayland_overlay_config.h
new file mode 100644
index 0000000..41dbe497
--- /dev/null
+++ b/ui/ozone/platform/wayland/common/wayland_overlay_config.h
@@ -0,0 +1,87 @@
+// Copyright 2022 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 UI_OZONE_PLATFORM_WAYLAND_COMMON_WAYLAND_OVERLAY_CONFIG_H_
+#define UI_OZONE_PLATFORM_WAYLAND_COMMON_WAYLAND_OVERLAY_CONFIG_H_
+
+#include <memory>
+
+#include "ui/gfx/gpu_fence.h"
+#include "ui/gfx/gpu_fence_handle.h"
+#include "ui/gfx/overlay_plane_data.h"
+#include "ui/gfx/overlay_priority_hint.h"
+#include "ui/gfx/overlay_transform.h"
+
+namespace wl {
+
+using BufferId = uint32_t;
+
+struct WaylandOverlayConfig {
+  WaylandOverlayConfig();
+  WaylandOverlayConfig(WaylandOverlayConfig&& other);
+  WaylandOverlayConfig(const gfx::OverlayPlaneData& data,
+                       std::unique_ptr<gfx::GpuFence> fence,
+                       BufferId buffer_id,
+                       float scale_factor);
+  WaylandOverlayConfig& operator=(WaylandOverlayConfig&& other);
+
+  ~WaylandOverlayConfig();
+
+  // Specifies the stacking order of this overlay plane, relative to primary
+  // plane.
+  int z_order = 0;
+
+  // Specifies how the buffer is to be transformed during composition.
+  gfx::OverlayTransform transform =
+      gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE;
+
+  // A unique id for the buffer, which is used to identify imported wl_buffers
+  // on the browser process.
+  uint32_t buffer_id = 0;
+
+  // Scale factor of the GPU side surface with respect to a display where the
+  // surface is located at.
+  float surface_scale_factor = 1.f;
+
+  // Specifies where it is supposed to be on the display in physical pixels.
+  // This, after scaled by buffer_scale sets the destination rectangle of
+  // Wayland Viewport.
+  gfx::RectF bounds_rect;
+
+  // Specifies the region within the buffer to be placed inside |bounds_rect|.
+  // This sets the source rectangle of Wayland Viewport.
+  gfx::RectF crop_rect = {1.f, 1.f};
+
+  // Describes the changed region of the buffer. Optional to hint a partial
+  // swap.
+  gfx::Rect damage_region;
+
+  // Specifies if alpha blending, with premultiplied alpha should be applied at
+  // scanout.
+  bool enable_blend = false;
+
+  // Opacity of the overlay independent of buffer alpha.
+  // Valid values are [0.0, 1.0f].
+  float opacity = 1.f;
+
+  // Specifies a GpuFenceHandle to be waited on before content of the buffer can
+  // be accessed by the display controller for overlay, or by the gpu for
+  // compositing.
+  gfx::GpuFenceHandle access_fence_handle;
+
+  // Specifies priority of this overlay if delegated composition is supported
+  // and enabled.
+  gfx::OverlayPriorityHint priority_hint = gfx::OverlayPriorityHint::kNone;
+
+  // Specifies rounded clip bounds of the overlay if delegated composition is
+  // supported and enabled.
+  gfx::RRectF rounded_clip_bounds;
+
+  // Optional: background color of this overlay plane.
+  absl::optional<SkColor> background_color;
+};
+
+}  // namespace wl
+
+#endif  // COMPONENTS_VIZ_COMMON_QUADS_COMPOSITOR_FRAME_H_
diff --git a/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.cc b/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.cc
index c09d4d1..35378d1 100644
--- a/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.cc
+++ b/ui/ozone/platform/wayland/gpu/gbm_pixmap_wayland.cc
@@ -23,7 +23,6 @@
 #include "ui/gfx/native_pixmap_handle.h"
 #include "ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h"
 #include "ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h"
-#include "ui/ozone/public/overlay_plane.h"
 #include "ui/ozone/public/ozone_platform.h"
 
 namespace ui {
@@ -179,13 +178,12 @@
   DCHECK(surfaceless);
 
   DCHECK(acquire_fences.empty() || acquire_fences.size() == 1u);
-  surfaceless->QueueOverlayPlane(
-      OverlayPlane(this,
-                   acquire_fences.empty() ? nullptr
-                                          : std::make_unique<gfx::GpuFence>(
-                                                std::move(acquire_fences[0])),
-                   overlay_plane_data),
-      buffer_id_);
+  surfaceless->QueueWaylandOverlayConfig(
+      {overlay_plane_data,
+       acquire_fences.empty()
+           ? nullptr
+           : std::make_unique<gfx::GpuFence>(std::move(acquire_fences[0])),
+       buffer_id_, surfaceless->surface_scale_factor()});
   return true;
 }
 
diff --git a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc
index 0501524..752200e2c 100644
--- a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc
+++ b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.cc
@@ -127,9 +127,9 @@
       std::make_unique<PendingFrame>(next_frame_id()));
 }
 
-void GbmSurfacelessWayland::QueueOverlayPlane(OverlayPlane plane,
-                                              BufferId buffer_id) {
-  unsubmitted_frames_.back()->planes.emplace_back(buffer_id, std::move(plane));
+void GbmSurfacelessWayland::QueueWaylandOverlayConfig(
+    wl::WaylandOverlayConfig config) {
+  unsubmitted_frames_.back()->configs.emplace_back(std::move(config));
 }
 
 bool GbmSurfacelessWayland::ScheduleOverlayPlane(
@@ -217,9 +217,12 @@
 
   base::OnceClosure fence_wait_task;
   std::vector<std::unique_ptr<gfx::GpuFence>> fences;
-  for (auto& plane : frame->planes) {
-    if (plane.second.gpu_fence)
-      fences.push_back(std::move(plane.second.gpu_fence));
+  for (auto& config : frame->configs) {
+    if (!config.access_fence_handle.is_null()) {
+      fences.push_back(std::make_unique<gfx::GpuFence>(
+          std::move(config.access_fence_handle)));
+      config.access_fence_handle = gfx::GpuFenceHandle();
+    }
   }
 
   fence_wait_task = base::BindOnce(&WaitForGpuFences, std::move(fences));
@@ -320,6 +323,7 @@
   // Solid color overlays are non-backed. Thus, queue them directly.
   // TODO(msisov): reconsider this once Linux Wayland compositors also support
   // creation of non-backed solid color wl_buffers.
+  in_flight_color_buffers.reserve(non_backed_overlays.size());
   for (auto& overlay_data : non_backed_overlays) {
     // This mustn't happen, but let's be explicit here and fail scheduling if
     // it is not a solid color overlay.
@@ -336,8 +340,9 @@
       schedule_planes_succeeded = false;
       return;
     }
-    surfaceless->QueueOverlayPlane(OverlayPlane(nullptr, nullptr, overlay_data),
-                                   buf_id);
+    in_flight_color_buffers.push_back(buf_id);
+    surfaceless->QueueWaylandOverlayConfig(
+        {overlay_data, nullptr, buf_id, surfaceless->surface_scale_factor()});
   }
 
   schedule_planes_succeeded = true;
@@ -367,19 +372,8 @@
       return;
     }
 
-    std::vector<ui::ozone::mojom::WaylandOverlayConfigPtr> overlay_configs;
-    for (auto& plane : submitted_frame->planes) {
-      overlay_configs.push_back(
-          ui::ozone::mojom::WaylandOverlayConfig::From(plane.second));
-      overlay_configs.back()->buffer_id = plane.first;
-      // The current scale factor of the surface, which is used to determine
-      // the size in pixels of resources allocated by the GPU process.
-      overlay_configs.back()->surface_scale_factor = surface_scale_factor_;
-      plane.second.gpu_fence.reset();
-    }
-
     buffer_manager_->CommitOverlays(widget_, submitted_frame->frame_id,
-                                    std::move(overlay_configs));
+                                    std::move(submitted_frame->configs));
     submitted_frames_.push_back(std::move(submitted_frame));
   }
 }
@@ -417,11 +411,11 @@
 
   auto submitted_frame = std::move(submitted_frames_.front());
   submitted_frames_.erase(submitted_frames_.begin());
-  for (auto& plane : submitted_frame->planes) {
+  for (auto& buf : submitted_frame->in_flight_color_buffers) {
     // Let the holder mark this buffer as free to reuse.
-    solid_color_buffers_holder_->OnSubmission(plane.first, buffer_manager_);
+    solid_color_buffers_holder_->OnSubmission(buf, buffer_manager_);
   }
-  submitted_frame->planes.clear();
+  submitted_frame->in_flight_color_buffers.clear();
   submitted_frame->overlays.clear();
 
   // Check if the fence has retired.
diff --git a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h
index 295e37b..16136a1b 100644
--- a/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h
+++ b/ui/ozone/platform/wayland/gpu/gbm_surfaceless_wayland.h
@@ -12,8 +12,8 @@
 #include "base/memory/weak_ptr.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/gl/gl_surface_egl.h"
+#include "ui/ozone/platform/wayland/common/wayland_overlay_config.h"
 #include "ui/ozone/platform/wayland/gpu/wayland_surface_gpu.h"
-#include "ui/ozone/public/overlay_plane.h"
 #include "ui/ozone/public/swap_completion_callback.h"
 
 namespace ui {
@@ -35,7 +35,9 @@
   GbmSurfacelessWayland(const GbmSurfacelessWayland&) = delete;
   GbmSurfacelessWayland& operator=(const GbmSurfacelessWayland&) = delete;
 
-  void QueueOverlayPlane(OverlayPlane plane, BufferId buffer_id);
+  float surface_scale_factor() const { return surface_scale_factor_; }
+
+  void QueueWaylandOverlayConfig(wl::WaylandOverlayConfig config);
 
   // gl::GLSurface:
   bool ScheduleOverlayPlane(
@@ -135,7 +137,7 @@
     explicit PendingFrame(uint32_t frame_id);
     ~PendingFrame();
 
-    // Queues overlay configs to |planes|.
+    // Queues overlay configs to |configs|.
     void ScheduleOverlayPlanes(GbmSurfacelessWayland* surfaceless);
     void Flush();
 
@@ -149,13 +151,15 @@
     std::vector<gfx::OverlayPlaneData> non_backed_overlays;
     SwapCompletionCallback completion_callback;
     PresentationCallback presentation_callback;
+
     // Merged release fence fd. This is taken as the union of all release
     // fences for a particular OnSubmission.
     bool schedule_planes_succeeded = false;
 
-    // Contains |buffer_id|s to OverlayPlanes, used for committing overlays and
-    // wait for OnSubmission's.
-    std::vector<std::pair<BufferId, OverlayPlane>> planes;
+    std::vector<BufferId> in_flight_color_buffers;
+    // Contains |buffer_id|s to gl::GLSurfaceOverlay, used for committing
+    // overlays and wait for OnSubmission's.
+    std::vector<wl::WaylandOverlayConfig> configs;
   };
 
   void MaybeSubmitFrames();
diff --git a/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.cc b/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.cc
index fe63c16..ebaf402 100644
--- a/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.cc
+++ b/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.cc
@@ -17,8 +17,8 @@
 #include "ui/gfx/linux/drm_util_linux.h"
 #include "ui/gfx/overlay_priority_hint.h"
 #include "ui/gl/gl_surface_egl.h"
+#include "ui/ozone/platform/wayland/common/wayland_overlay_config.h"
 #include "ui/ozone/platform/wayland/gpu/wayland_surface_gpu.h"
-#include "ui/ozone/platform/wayland/mojom/wayland_overlay_config.mojom.h"
 #include "ui/ozone/public/overlay_plane.h"
 
 #if defined(WAYLAND_GBM)
@@ -27,38 +27,6 @@
 #include "ui/ozone/platform/wayland/gpu/drm_render_node_path_finder.h"
 #endif
 
-namespace mojo {
-// static
-ui::ozone::mojom::WaylandOverlayConfigPtr
-TypeConverter<ui::ozone::mojom::WaylandOverlayConfigPtr,
-              ui::OverlayPlane>::Convert(const ui::OverlayPlane& input) {
-  ui::ozone::mojom::WaylandOverlayConfigPtr wayland_overlay_config{
-      ui::ozone::mojom::WaylandOverlayConfig::New()};
-  wayland_overlay_config->z_order = input.overlay_plane_data.z_order;
-  wayland_overlay_config->transform = input.overlay_plane_data.plane_transform;
-  wayland_overlay_config->bounds_rect = input.overlay_plane_data.display_bounds;
-  wayland_overlay_config->crop_rect = input.overlay_plane_data.crop_rect;
-  wayland_overlay_config->enable_blend = input.overlay_plane_data.enable_blend;
-  wayland_overlay_config->opacity = input.overlay_plane_data.opacity;
-  wayland_overlay_config->damage_region = input.overlay_plane_data.damage_rect;
-  wayland_overlay_config->access_fence_handle =
-      !input.gpu_fence || input.gpu_fence->GetGpuFenceHandle().is_null()
-          ? gfx::GpuFenceHandle()
-          : input.gpu_fence->GetGpuFenceHandle().Clone();
-  wayland_overlay_config->priority_hint =
-      input.overlay_plane_data.priority_hint;
-
-  wayland_overlay_config->rounded_clip_bounds =
-      input.overlay_plane_data.rounded_corners;
-
-  // Solid color quads are created as wl_buffers. Though, some overlays may
-  // have background data passed.
-  if (!input.overlay_plane_data.is_solid_color)
-    wayland_overlay_config->background_color = input.overlay_plane_data.color;
-  return wayland_overlay_config;
-}
-}  // namespace mojo
-
 namespace ui {
 
 WaylandBufferManagerGpu::WaylandBufferManagerGpu() {
@@ -277,23 +245,23 @@
                                            const gfx::Rect& bounds_rect,
                                            float surface_scale_factor,
                                            const gfx::Rect& damage_region) {
-  std::vector<ui::ozone::mojom::WaylandOverlayConfigPtr> overlay_configs;
   // This surface only commits one buffer per frame, use INT32_MIN to attach
   // the buffer to root_surface of wayland window.
-  overlay_configs.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-      INT32_MIN, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, buffer_id,
-      surface_scale_factor, gfx::RectF(bounds_rect),
-      gfx::RectF(1.f, 1.f) /* no crop */, damage_region, false,
-      1.0f /*opacity*/, gfx::GpuFenceHandle(), gfx::OverlayPriorityHint::kNone,
-      gfx::RRectF(), absl::nullopt));
-
+  std::vector<wl::WaylandOverlayConfig> overlay_configs;
+  overlay_configs.emplace_back(
+      gfx::OverlayPlaneData(
+          INT32_MIN, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE,
+          gfx::RectF(bounds_rect), gfx::RectF(1.f, 1.f) /* no crop */, false,
+          damage_region, 1.0f /*opacity*/, gfx::OverlayPriorityHint::kNone,
+          gfx::RRectF(), gfx::ColorSpace(), absl::nullopt),
+      nullptr, buffer_id, surface_scale_factor);
   CommitOverlays(widget, frame_id, std::move(overlay_configs));
 }
 
 void WaylandBufferManagerGpu::CommitOverlays(
     gfx::AcceleratedWidget widget,
     uint32_t frame_id,
-    std::vector<ozone::mojom::WaylandOverlayConfigPtr> overlays) {
+    std::vector<wl::WaylandOverlayConfig> overlays) {
   DCHECK(gpu_thread_runner_);
   if (!gpu_thread_runner_->BelongsToCurrentThread()) {
     // Do the mojo call on the GpuMainThread.
@@ -524,7 +492,7 @@
 void WaylandBufferManagerGpu::CommitOverlaysTask(
     gfx::AcceleratedWidget widget,
     uint32_t frame_id,
-    std::vector<ozone::mojom::WaylandOverlayConfigPtr> overlays) {
+    std::vector<wl::WaylandOverlayConfig> overlays) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(gpu_sequence_checker_);
   DCHECK(remote_host_);
 
diff --git a/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h b/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h
index 510b7edc..1843b14 100644
--- a/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h
+++ b/ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h
@@ -16,7 +16,6 @@
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "mojo/public/cpp/bindings/remote.h"
-#include "mojo/public/cpp/bindings/type_converter.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/ozone/platform/wayland/common/wayland_util.h"
@@ -32,7 +31,6 @@
 class WaylandConnection;
 class WaylandSurfaceGpu;
 class WaylandWindow;
-struct OverlayPlane;
 
 // Forwards calls through an associated mojo connection to WaylandBufferManager
 // on the browser process side.
@@ -129,10 +127,9 @@
                     const gfx::Rect& damage_region);
   // Send overlay configurations for a frame to a WaylandWindow identified by
   // |widget|.
-  void CommitOverlays(
-      gfx::AcceleratedWidget widget,
-      uint32_t frame_id,
-      std::vector<ozone::mojom::WaylandOverlayConfigPtr> overlays);
+  void CommitOverlays(gfx::AcceleratedWidget widget,
+                      uint32_t frame_id,
+                      std::vector<wl::WaylandOverlayConfig> overlays);
 
   // Asks Wayland to destroy a wl_buffer.
   void DestroyBuffer(uint32_t buffer_id);
@@ -222,10 +219,9 @@
   void CreateSolidColorBufferTask(SkColor color,
                                   const gfx::Size& size,
                                   uint32_t buf_id);
-  void CommitOverlaysTask(
-      gfx::AcceleratedWidget widget,
-      uint32_t frame_id,
-      std::vector<ozone::mojom::WaylandOverlayConfigPtr> overlays);
+  void CommitOverlaysTask(gfx::AcceleratedWidget widget,
+                          uint32_t frame_id,
+                          std::vector<wl::WaylandOverlayConfig> overlays);
   void DestroyBufferTask(uint32_t buffer_id);
 
 #if defined(WAYLAND_GBM)
@@ -313,15 +309,4 @@
 
 }  // namespace ui
 
-// This is a specialization of mojo::TypeConverter and has to be in the mojo
-// namespace.
-namespace mojo {
-template <>
-struct TypeConverter<ui::ozone::mojom::WaylandOverlayConfigPtr,
-                     ui::OverlayPlane> {
-  static ui::ozone::mojom::WaylandOverlayConfigPtr Convert(
-      const ui::OverlayPlane& input);
-};
-}  // namespace mojo
-
 #endif  // UI_OZONE_PLATFORM_WAYLAND_GPU_WAYLAND_BUFFER_MANAGER_GPU_H_
diff --git a/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.cc b/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.cc
index e212f46..c73126f 100644
--- a/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.cc
+++ b/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.cc
@@ -16,6 +16,7 @@
 #include "base/trace_event/trace_event.h"
 #include "ui/gfx/gpu_fence_handle.h"
 #include "ui/gfx/linux/drm_util_linux.h"
+#include "ui/ozone/platform/wayland/common/wayland_overlay_config.h"
 #include "ui/ozone/platform/wayland/host/surface_augmenter.h"
 #include "ui/ozone/platform/wayland/host/wayland_buffer_backing.h"
 #include "ui/ozone/platform/wayland/host/wayland_buffer_backing_dmabuf.h"
@@ -27,7 +28,6 @@
 #include "ui/ozone/platform/wayland/host/wayland_shm.h"
 #include "ui/ozone/platform/wayland/host/wayland_window.h"
 #include "ui/ozone/platform/wayland/host/wayland_zwp_linux_dmabuf.h"
-#include "ui/ozone/platform/wayland/mojom/wayland_overlay_config.mojom.h"
 
 namespace ui {
 
@@ -257,7 +257,7 @@
 void WaylandBufferManagerHost::CommitOverlays(
     gfx::AcceleratedWidget widget,
     uint32_t frame_id,
-    std::vector<ui::ozone::mojom::WaylandOverlayConfigPtr> overlays) {
+    std::vector<wl::WaylandOverlayConfig> overlays) {
   DCHECK(base::CurrentUIThread::IsSet());
 
   TRACE_EVENT0("wayland", "WaylandBufferManagerHost::CommitOverlays");
@@ -277,7 +277,7 @@
     return;
 
   for (auto& overlay : overlays) {
-    if (!ValidateOverlayData(*overlay)) {
+    if (!ValidateOverlayData(overlay)) {
       TerminateGpuProcess();
       return;
     }
@@ -405,7 +405,7 @@
 }
 
 bool WaylandBufferManagerHost::ValidateOverlayData(
-    const ui::ozone::mojom::WaylandOverlayConfig& overlay_data) {
+    const wl::WaylandOverlayConfig& overlay_data) {
   if (!ValidateBufferExistence(overlay_data.buffer_id))
     return false;
 
diff --git a/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h b/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h
index 5fd2b4e2..3243f26 100644
--- a/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h
+++ b/ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h
@@ -107,10 +107,9 @@
   // Called by the GPU and asks to configure the surface/subsurfaces and attach
   // wl_buffers to WaylandWindow with the specified |widget|. Calls OnSubmission
   // and OnPresentation on successful swap and pixels presented.
-  void CommitOverlays(
-      gfx::AcceleratedWidget widget,
-      uint32_t frame_id,
-      std::vector<ui::ozone::mojom::WaylandOverlayConfigPtr> overlays) override;
+  void CommitOverlays(gfx::AcceleratedWidget widget,
+                      uint32_t frame_id,
+                      std::vector<wl::WaylandOverlayConfig> overlays) override;
 
   // Ensures a WaylandBufferHandle of |buffer_id| is created for the
   // |requestor|, with its wl_buffer object requested via Wayland. Returns said
@@ -150,8 +149,7 @@
                            uint32_t buffer_id);
   bool ValidateDataFromGpu(const gfx::Size& size, uint32_t buffer_id);
   bool ValidateBufferExistence(uint32_t buffer_id);
-  bool ValidateOverlayData(
-      const ui::ozone::mojom::WaylandOverlayConfig& overlay_data);
+  bool ValidateOverlayData(const wl::WaylandOverlayConfig& overlay_data);
 
   // Terminates the GPU process on invalid data received
   void TerminateGpuProcess();
diff --git a/ui/ozone/platform/wayland/host/wayland_frame_manager.cc b/ui/ozone/platform/wayland/host/wayland_frame_manager.cc
index 1430d12b0..a120c6ff1 100644
--- a/ui/ozone/platform/wayland/host/wayland_frame_manager.cc
+++ b/ui/ozone/platform/wayland/host/wayland_frame_manager.cc
@@ -43,9 +43,9 @@
 WaylandFrame::WaylandFrame(
     uint32_t frame_id,
     WaylandSurface* root_surface,
-    ui::ozone::mojom::WaylandOverlayConfigPtr root_config,
-    base::circular_deque<std::pair<WaylandSubsurface*,
-                                   ui::ozone::mojom::WaylandOverlayConfigPtr>>
+    wl::WaylandOverlayConfig root_config,
+    base::circular_deque<
+        std::pair<WaylandSubsurface*, wl::WaylandOverlayConfig>>
         subsurfaces_to_overlays)
     : frame_id(frame_id),
       root_surface(root_surface),
@@ -56,9 +56,9 @@
 
 WaylandFrame::WaylandFrame(
     WaylandSurface* root_surface,
-    ui::ozone::mojom::WaylandOverlayConfigPtr root_config,
-    base::circular_deque<std::pair<WaylandSubsurface*,
-                                   ui::ozone::mojom::WaylandOverlayConfigPtr>>
+    wl::WaylandOverlayConfig root_config,
+    base::circular_deque<
+        std::pair<WaylandSubsurface*, wl::WaylandOverlayConfig>>
         subsurfaces_to_overlays)
     : root_surface(root_surface),
       root_config(std::move(root_config)),
@@ -81,18 +81,17 @@
 
   // Request for buffer handle creation at record time.
   for (auto& subsurface_to_overlay : frame->subsurfaces_to_overlays) {
-    if (!subsurface_to_overlay.second.is_null() &&
-        subsurface_to_overlay.second->buffer_id) {
+    if (subsurface_to_overlay.second.buffer_id) {
       auto* handle = connection_->buffer_manager_host()->EnsureBufferHandle(
           subsurface_to_overlay.first->wayland_surface(),
-          subsurface_to_overlay.second->buffer_id);
+          subsurface_to_overlay.second.buffer_id);
       if (!handle)
         return;
     }
   }
-  if (frame->root_config && frame->root_config->buffer_id) {
+  if (frame->root_config.buffer_id) {
     auto* handle = connection_->buffer_manager_host()->EnsureBufferHandle(
-        frame->root_surface, frame->root_config->buffer_id);
+        frame->root_surface, frame->root_config.buffer_id);
     if (!handle)
       return;
   }
@@ -104,6 +103,7 @@
 void WaylandFrameManager::MaybeProcessPendingFrame() {
   if (pending_frames_.empty())
     return;
+
   auto* frame = pending_frames_.front().get();
   DCHECK(frame) << "This WaylandFrame is already in playback.";
   if (!frame)
@@ -112,15 +112,14 @@
   // Ensure wl_buffer existence.
   WaylandBufferHandle* handle_pending_creation = nullptr;
   for (auto& subsurface_to_overlay : frame->subsurfaces_to_overlays) {
-    if (!subsurface_to_overlay.second.is_null() &&
-        subsurface_to_overlay.second->buffer_id) {
+    if (subsurface_to_overlay.second.buffer_id) {
       auto* handle = connection_->buffer_manager_host()->EnsureBufferHandle(
           subsurface_to_overlay.first->wayland_surface(),
-          subsurface_to_overlay.second->buffer_id);
+          subsurface_to_overlay.second.buffer_id);
       // Buffer is gone while this frame is pending, remove this config.
       if (!handle) {
         frame->buffer_lost = true;
-        subsurface_to_overlay.second.reset();
+        subsurface_to_overlay.second = wl::WaylandOverlayConfig();
       } else if (!handle->wl_buffer() && !handle_pending_creation) {
         // Found the first not-ready buffer, let handle invoke
         // MaybeProcessPendingFrame() when wl_buffer is created.
@@ -128,12 +127,12 @@
       }
     }
   }
-  if (frame->root_config && frame->root_config->buffer_id) {
+  if (frame->root_config.buffer_id) {
     auto* handle = connection_->buffer_manager_host()->EnsureBufferHandle(
-        frame->root_surface, frame->root_config->buffer_id);
+        frame->root_surface, frame->root_config.buffer_id);
     if (!handle) {
       frame->buffer_lost = true;
-      frame->root_config.reset();
+      frame->root_config = wl::WaylandOverlayConfig();
     } else if (!handle->wl_buffer() && !handle_pending_creation) {
       handle_pending_creation = handle;
     }
@@ -181,12 +180,12 @@
 
   auto* root_surface = frame->root_surface;
   auto& root_config = frame->root_config;
-  bool empty_frame = !root_config || !root_config->buffer_id;
+  bool empty_frame = !root_config.buffer_id;
 
   if (!empty_frame) {
     window_->UpdateVisualSize(
-        gfx::ToRoundedSize(root_config->bounds_rect.size()),
-        root_config->surface_scale_factor);
+        gfx::ToRoundedSize(root_config.bounds_rect.size()),
+        root_config.surface_scale_factor);
   }
 
   // Configure subsurfaces. Traverse the deque backwards s.t. we can set
@@ -196,8 +195,8 @@
        base::Reversed(frame->subsurfaces_to_overlays)) {
     DCHECK(subsurface);
     auto* surface = subsurface->wayland_surface();
-    if (empty_frame || config.is_null() ||
-        wl_fixed_from_double(config->opacity) == 0) {
+    if (empty_frame || !config.buffer_id ||
+        wl_fixed_from_double(config.opacity) == 0) {
       subsurface->Hide();
       // Mutter sometimes does not call buffer.release if wl_surface role is
       // destroyed, causing graphics freeze. Manually release buffer from the
@@ -213,8 +212,8 @@
       }
     } else {
       subsurface->ConfigureAndShowSurface(
-          config->bounds_rect, root_config->bounds_rect,
-          root_config->surface_scale_factor, nullptr, reference_above);
+          config.bounds_rect, root_config.bounds_rect,
+          root_config.surface_scale_factor, nullptr, reference_above);
       ApplySurfaceConfigure(frame.get(), surface, config, true);
       reference_above = subsurface;
       surface->Commit(false);
@@ -235,7 +234,7 @@
          frame->pending_feedback || frame->feedback.has_value());
   root_surface->Commit(true);
 
-  frame->root_config.reset();
+  frame->root_config = wl::WaylandOverlayConfig();
   frame->subsurfaces_to_overlays.clear();
 
   // Empty frames do not expect feedbacks so don't push to |submitted_frames_|.
@@ -250,10 +249,10 @@
 void WaylandFrameManager::ApplySurfaceConfigure(
     WaylandFrame* frame,
     WaylandSurface* surface,
-    ui::ozone::mojom::WaylandOverlayConfigPtr& config,
+    wl::WaylandOverlayConfig& config,
     bool set_opaque_region) {
   DCHECK(surface);
-  if (!config->buffer_id)
+  if (!config.buffer_id)
     return;
 
   static const wl_callback_listener frame_listener = {
@@ -263,32 +262,32 @@
       &WaylandFrameManager::FeedbackPresented,
       &WaylandFrameManager::FeedbackDiscarded};
 
-  surface->SetBufferTransform(config->transform);
-  surface->SetSurfaceBufferScale(ceil(config->surface_scale_factor));
-  surface->SetViewportSource(config->crop_rect);
-  surface->SetViewportDestination(config->bounds_rect.size());
-  surface->SetOpacity(config->opacity);
-  surface->SetBlending(config->enable_blend);
-  surface->SetRoundedClipBounds(config->rounded_clip_bounds);
-  surface->SetOverlayPriority(config->priority_hint);
-  surface->SetBackgroundColor(config->background_color);
+  surface->SetBufferTransform(config.transform);
+  surface->SetSurfaceBufferScale(ceil(config.surface_scale_factor));
+  surface->SetViewportSource(config.crop_rect);
+  surface->SetViewportDestination(config.bounds_rect.size());
+  surface->SetOpacity(config.opacity);
+  surface->SetBlending(config.enable_blend);
+  surface->SetRoundedClipBounds(config.rounded_clip_bounds);
+  surface->SetOverlayPriority(config.priority_hint);
+  surface->SetBackgroundColor(config.background_color);
   if (set_opaque_region) {
     std::vector<gfx::Rect> region_px = {
-        gfx::Rect(gfx::ToRoundedSize(config->bounds_rect.size()))};
-    surface->SetOpaqueRegion(config->enable_blend ? nullptr : &region_px);
+        gfx::Rect(gfx::ToRoundedSize(config.bounds_rect.size()))};
+    surface->SetOpaqueRegion(config.enable_blend ? nullptr : &region_px);
   }
 
   WaylandBufferHandle* buffer_handle =
       connection_->buffer_manager_host()->GetBufferHandle(surface,
-                                                          config->buffer_id);
+                                                          config.buffer_id);
   DCHECK(buffer_handle);
   bool will_attach = surface->AttachBuffer(buffer_handle);
   // If we don't attach a released buffer, graphics freeze will occur.
   DCHECK(will_attach || !buffer_handle->released(surface));
 
-  surface->UpdateBufferDamageRegion(config->damage_region);
-  if (!config->access_fence_handle.is_null())
-    surface->SetAcquireFence(std::move(config->access_fence_handle));
+  surface->UpdateBufferDamageRegion(config.damage_region);
+  if (!config.access_fence_handle.is_null())
+    surface->SetAcquireFence(std::move(config.access_fence_handle));
 
   if (will_attach) {
     // Setup frame callback if wayland_surface will commit this buffer.
@@ -337,7 +336,6 @@
   // Send instructions across wayland protocol, but do not commit yet, let the
   // caller decide whether the commit should flush.
   surface->ApplyPendingState();
-  return;
 }
 
 // static
diff --git a/ui/ozone/platform/wayland/host/wayland_frame_manager.h b/ui/ozone/platform/wayland/host/wayland_frame_manager.h
index 789fc5ed..1780de0 100644
--- a/ui/ozone/platform/wayland/host/wayland_frame_manager.h
+++ b/ui/ozone/platform/wayland/host/wayland_frame_manager.h
@@ -16,7 +16,7 @@
 #include "ui/gfx/gpu_fence_handle.h"
 #include "ui/gfx/presentation_feedback.h"
 #include "ui/ozone/platform/wayland/common/wayland_object.h"
-#include "ui/ozone/platform/wayland/mojom/wayland_overlay_config.mojom.h"
+#include "ui/ozone/platform/wayland/common/wayland_overlay_config.h"
 
 namespace ui {
 
@@ -33,21 +33,19 @@
 struct WaylandFrame {
  public:
   // A frame originated from gpu process, and hence, requires acknowledgements.
-  WaylandFrame(
-      uint32_t frame_id,
-      WaylandSurface* root_surface,
-      ui::ozone::mojom::WaylandOverlayConfigPtr root_config,
-      base::circular_deque<std::pair<WaylandSubsurface*,
-                                     ui::ozone::mojom::WaylandOverlayConfigPtr>>
-          subsurfaces_to_overlays = {});
+  WaylandFrame(uint32_t frame_id,
+               WaylandSurface* root_surface,
+               wl::WaylandOverlayConfig root_config,
+               base::circular_deque<
+                   std::pair<WaylandSubsurface*, wl::WaylandOverlayConfig>>
+                   subsurfaces_to_overlays = {});
 
   // A frame that does not require acknowledgements.
-  WaylandFrame(
-      WaylandSurface* root_surface,
-      ui::ozone::mojom::WaylandOverlayConfigPtr root_config,
-      base::circular_deque<std::pair<WaylandSubsurface*,
-                                     ui::ozone::mojom::WaylandOverlayConfigPtr>>
-          subsurfaces_to_overlays = {});
+  WaylandFrame(WaylandSurface* root_surface,
+               wl::WaylandOverlayConfig root_config,
+               base::circular_deque<
+                   std::pair<WaylandSubsurface*, wl::WaylandOverlayConfig>>
+                   subsurfaces_to_overlays = {});
 
   WaylandFrame() = delete;
   WaylandFrame(const WaylandFrame&) = delete;
@@ -59,9 +57,8 @@
 
   uint32_t frame_id;
   WaylandSurface* root_surface;
-  ui::ozone::mojom::WaylandOverlayConfigPtr root_config;
-  base::circular_deque<
-      std::pair<WaylandSubsurface*, ui::ozone::mojom::WaylandOverlayConfigPtr>>
+  wl::WaylandOverlayConfig root_config;
+  base::circular_deque<std::pair<WaylandSubsurface*, wl::WaylandOverlayConfig>>
       subsurfaces_to_overlays;
 
   base::flat_map<WaylandSurface*, WaylandBufferHandle*> submitted_buffers;
@@ -127,7 +124,7 @@
   // Configures |surface| but does not commit wl_surface states yet.
   void ApplySurfaceConfigure(WaylandFrame* frame,
                              WaylandSurface* surface,
-                             ui::ozone::mojom::WaylandOverlayConfigPtr& config,
+                             wl::WaylandOverlayConfig& config,
                              bool needs_opaque_region);
 
   void MaybeProcessSubmittedFrames();
diff --git a/ui/ozone/platform/wayland/host/wayland_window.cc b/ui/ozone/platform/wayland/host/wayland_window.cc
index 377a150..fe0cd60e 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window.cc
@@ -30,6 +30,7 @@
 #include "ui/gfx/overlay_priority_hint.h"
 #include "ui/ozone/common/bitmap_cursor.h"
 #include "ui/ozone/common/features.h"
+#include "ui/ozone/platform/wayland/common/wayland_overlay_config.h"
 #include "ui/ozone/platform/wayland/host/wayland_connection.h"
 #include "ui/ozone/platform/wayland/host/wayland_cursor_position.h"
 #include "ui/ozone/platform/wayland/host/wayland_data_drag_controller.h"
@@ -51,10 +52,9 @@
 using mojom::CursorType;
 using mojom::DragOperation;
 
-bool OverlayStackOrderCompare(
-    const ui::ozone::mojom::WaylandOverlayConfigPtr& i,
-    const ui::ozone::mojom::WaylandOverlayConfigPtr& j) {
-  return i->z_order < j->z_order;
+bool OverlayStackOrderCompare(const wl::WaylandOverlayConfig& i,
+                              const wl::WaylandOverlayConfig& j) {
+  return i.z_order < j.z_order;
 }
 
 }  // namespace
@@ -251,18 +251,20 @@
 
 void WaylandWindow::OnChannelDestroyed() {
   frame_manager_->ClearStates();
-  base::circular_deque<
-      std::pair<WaylandSubsurface*, ui::ozone::mojom::WaylandOverlayConfigPtr>>
+  base::circular_deque<std::pair<WaylandSubsurface*, wl::WaylandOverlayConfig>>
       subsurfaces_to_overlays;
   subsurfaces_to_overlays.reserve(wayland_subsurfaces_.size() +
                                   (primary_subsurface() ? 1 : 0));
   if (primary_subsurface())
-    subsurfaces_to_overlays.emplace_back(primary_subsurface(), nullptr);
+    subsurfaces_to_overlays.emplace_back(primary_subsurface(),
+                                         wl::WaylandOverlayConfig());
   for (auto& subsurface : wayland_subsurfaces_)
-    subsurfaces_to_overlays.emplace_back(subsurface.get(), nullptr);
+    subsurfaces_to_overlays.emplace_back(subsurface.get(),
+                                         wl::WaylandOverlayConfig());
 
-  frame_manager_->RecordFrame(std::make_unique<WaylandFrame>(
-      root_surface(), nullptr, std::move(subsurfaces_to_overlays)));
+  frame_manager_->RecordFrame(
+      std::make_unique<WaylandFrame>(root_surface(), wl::WaylandOverlayConfig(),
+                                     std::move(subsurfaces_to_overlays)));
 }
 
 void WaylandWindow::Close() {
@@ -786,7 +788,7 @@
 
 bool WaylandWindow::CommitOverlays(
     uint32_t frame_id,
-    std::vector<ui::ozone::mojom::WaylandOverlayConfigPtr>& overlays) {
+    std::vector<wl::WaylandOverlayConfig>& overlays) {
   if (overlays.empty())
     return true;
 
@@ -794,15 +796,14 @@
   std::sort(overlays.begin(), overlays.end(), OverlayStackOrderCompare);
 
   // Find the location where z_oder becomes non-negative.
-  ozone::mojom::WaylandOverlayConfigPtr value =
-      ozone::mojom::WaylandOverlayConfig::New();
+  wl::WaylandOverlayConfig value;
   auto split = std::lower_bound(overlays.begin(), overlays.end(), value,
                                 OverlayStackOrderCompare);
-  DCHECK(split == overlays.end() || (*split)->z_order >= 0);
+  DCHECK(split == overlays.end() || (*split).z_order >= 0);
   size_t num_primary_planes =
-      (split != overlays.end() && (*split)->z_order == 0) ? 1 : 0;
+      (split != overlays.end() && (*split).z_order == 0) ? 1 : 0;
   size_t num_background_planes =
-      (overlays.front()->z_order == INT32_MIN) ? 1 : 0;
+      (overlays.front().z_order == INT32_MIN) ? 1 : 0;
 
   size_t above = (overlays.end() - split) - num_primary_planes;
   size_t below = (split - overlays.begin()) - num_background_planes;
@@ -812,9 +813,9 @@
   if (!ArrangeSubsurfaceStack(above, below))
     return false;
 
-  gfx::SizeF visual_size = (*overlays.begin())->bounds_rect.size();
-  float buffer_scale = (*overlays.begin())->surface_scale_factor;
-  auto& rounded_clip_bounds = (*overlays.begin())->rounded_clip_bounds;
+  gfx::SizeF visual_size = (*overlays.begin()).bounds_rect.size();
+  float buffer_scale = (*overlays.begin()).surface_scale_factor;
+  auto& rounded_clip_bounds = (*overlays.begin()).rounded_clip_bounds;
 
   if (!wayland_overlay_delegation_enabled_) {
     DCHECK_EQ(overlays.size(), 1u);
@@ -823,15 +824,15 @@
     return true;
   }
 
-  base::circular_deque<
-      std::pair<WaylandSubsurface*, ui::ozone::mojom::WaylandOverlayConfigPtr>>
+  base::circular_deque<std::pair<WaylandSubsurface*, wl::WaylandOverlayConfig>>
       subsurfaces_to_overlays;
   subsurfaces_to_overlays.reserve(
       std::max(overlays.size() - num_background_planes,
                wayland_subsurfaces_.size() + 1));
 
   subsurfaces_to_overlays.emplace_back(
-      primary_subsurface(), num_primary_planes ? std::move(*split) : nullptr);
+      primary_subsurface(),
+      num_primary_planes ? std::move(*split) : wl::WaylandOverlayConfig());
 
   {
     // Iterate through |subsurface_stack_below_|, setup subsurfaces and place
@@ -845,7 +846,8 @@
       } else {
         // If there're more subsurfaces requested that we don't need at the
         // moment, hide them.
-        subsurfaces_to_overlays.emplace_front(*iter, nullptr);
+        subsurfaces_to_overlays.emplace_front(*iter,
+                                              wl::WaylandOverlayConfig());
       }
     }
 
@@ -860,22 +862,24 @@
       } else {
         // If there're more subsurfaces requested that we don't need at the
         // moment, hide them.
-        subsurfaces_to_overlays.emplace_back(*iter, nullptr);
+        subsurfaces_to_overlays.emplace_back(*iter, wl::WaylandOverlayConfig());
       }
     }
   }
 
   // Configuration of the root_surface
-  ui::ozone::mojom::WaylandOverlayConfigPtr root_config;
+  wl::WaylandOverlayConfig root_config;
   if (num_background_planes) {
     root_config = std::move(overlays.front());
   } else {
-    root_config = ui::ozone::mojom::WaylandOverlayConfig::New(
-        INT32_MIN, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE,
-        root_surface()->buffer_id(), buffer_scale, gfx::RectF(visual_size),
-        gfx::RectF(), gfx::Rect(), root_surface()->use_blending(),
-        root_surface()->opacity(), gfx::GpuFenceHandle(),
-        gfx::OverlayPriorityHint::kNone, rounded_clip_bounds, absl::nullopt);
+    root_config = wl::WaylandOverlayConfig(
+        gfx::OverlayPlaneData(
+            INT32_MIN, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE,
+            gfx::RectF(visual_size), gfx::RectF(),
+            root_surface()->use_blending(), gfx::Rect(),
+            root_surface()->opacity(), gfx::OverlayPriorityHint::kNone,
+            rounded_clip_bounds, gfx::ColorSpace(), absl::nullopt),
+        nullptr, root_surface()->buffer_id(), buffer_scale);
   }
 
   frame_manager_->RecordFrame(std::make_unique<WaylandFrame>(
diff --git a/ui/ozone/platform/wayland/host/wayland_window.h b/ui/ozone/platform/wayland/host/wayland_window.h
index 43f3b7f..cc252e7 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.h
+++ b/ui/ozone/platform/wayland/host/wayland_window.h
@@ -26,12 +26,17 @@
 #include "ui/gfx/native_widget_types.h"
 #include "ui/ozone/platform/wayland/common/wayland_object.h"
 #include "ui/ozone/platform/wayland/host/wayland_surface.h"
-#include "ui/ozone/platform/wayland/mojom/wayland_overlay_config.mojom-forward.h"
 #include "ui/platform_window/platform_window.h"
 #include "ui/platform_window/platform_window_delegate.h"
 #include "ui/platform_window/platform_window_init_properties.h"
 #include "ui/platform_window/wm/wm_drag_handler.h"
 
+namespace wl {
+
+struct WaylandOverlayConfig;
+
+}  // namespace wl
+
 namespace ui {
 
 class BitmapCursor;
@@ -97,9 +102,8 @@
   // subsurface_stack_above_.size() >= above and
   // subsurface_stack_below_.size() >= below.
   bool ArrangeSubsurfaceStack(size_t above, size_t below);
-  bool CommitOverlays(
-      uint32_t frame_id,
-      std::vector<ui::ozone::mojom::WaylandOverlayConfigPtr>& overlays);
+  bool CommitOverlays(uint32_t frame_id,
+                      std::vector<wl::WaylandOverlayConfig>& overlays);
 
   // Set whether this window has pointer focus and should dispatch mouse events.
   void SetPointerFocus(bool focus);
diff --git a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
index 2f18f9d..910a10a5 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
@@ -34,8 +34,10 @@
 #include "ui/events/event.h"
 #include "ui/gfx/geometry/insets.h"
 #include "ui/gfx/geometry/rect.h"
+#include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/geometry/transform.h"
 #include "ui/gfx/native_widget_types.h"
+#include "ui/gfx/overlay_plane_data.h"
 #include "ui/gfx/overlay_priority_hint.h"
 #include "ui/gfx/overlay_transform.h"
 #include "ui/ozone/common/bitmap_cursor.h"
@@ -2741,14 +2743,10 @@
   SendConfigureEvent(mock_surface->xdg_surface(), 100, 100, 1, states.get());
 
   // Commit a frame with only background.
-  std::vector<ui::ozone::mojom::WaylandOverlayConfigPtr> overlays;
-  ui::ozone::mojom::WaylandOverlayConfigPtr background{
-      ui::ozone::mojom::WaylandOverlayConfig::New()};
-  background->z_order = INT32_MIN;
-  background->transform = gfx::OVERLAY_TRANSFORM_NONE;
-  background->buffer_id = buffer_id1;
-  background->surface_scale_factor = 1;
-  background->opacity = 1.f;
+  std::vector<wl::WaylandOverlayConfig> overlays;
+  wl::WaylandOverlayConfig background;
+  background.z_order = INT32_MIN;
+  background.buffer_id = buffer_id1;
   overlays.push_back(std::move(background));
   buffer_manager_gpu_->CommitOverlays(window->GetWidget(), 1u,
                                       std::move(overlays));
@@ -2775,13 +2773,9 @@
 
   // Commit a frame with only the primary_plane.
   overlays.clear();
-  ui::ozone::mojom::WaylandOverlayConfigPtr primary{
-      ui::ozone::mojom::WaylandOverlayConfig::New()};
-  primary->z_order = 0;
-  primary->transform = gfx::OVERLAY_TRANSFORM_NONE;
-  primary->buffer_id = buffer_id2;
-  primary->surface_scale_factor = 1;
-  primary->opacity = 1.f;
+  wl::WaylandOverlayConfig primary;
+  primary.z_order = 0;
+  primary.buffer_id = buffer_id2;
   overlays.push_back(std::move(primary));
   buffer_manager_gpu_->CommitOverlays(window->GetWidget(), 2u,
                                       std::move(overlays));
diff --git a/ui/ozone/platform/wayland/mojom/BUILD.gn b/ui/ozone/platform/wayland/mojom/BUILD.gn
index 0eba88d9..4f7d822 100644
--- a/ui/ozone/platform/wayland/mojom/BUILD.gn
+++ b/ui/ozone/platform/wayland/mojom/BUILD.gn
@@ -16,4 +16,22 @@
     "//ui/gfx/geometry/mojom",
     "//ui/gfx/mojom",
   ]
+
+  cpp_typemaps = [
+    {
+      types = [
+        {
+          mojom = "wl.mojom.WaylandOverlayConfig"
+          cpp = "::wl::WaylandOverlayConfig"
+          move_only = true
+        },
+      ]
+      traits_sources = [ "wayland_overlay_config_mojom_traits.cc" ]
+      traits_headers = [ "wayland_overlay_config_mojom_traits.h" ]
+      traits_deps = [
+        "//components/crash/core/common:crash_key_lib",
+        "//ui/ozone/platform/wayland:common",
+      ]
+    },
+  ]
 }
diff --git a/ui/ozone/platform/wayland/mojom/DEPS b/ui/ozone/platform/wayland/mojom/DEPS
new file mode 100644
index 0000000..ad262d0
--- /dev/null
+++ b/ui/ozone/platform/wayland/mojom/DEPS
@@ -0,0 +1,6 @@
+specific_include_rules = {
+  "wayland_overlay_config_mojom_traits.*" : [
+    "+components/crash/core/common/crash_key.h",
+    "+skia/public/mojom/skcolor_mojom_traits.h",
+  ],
+}
diff --git a/ui/ozone/platform/wayland/mojom/OWNERS b/ui/ozone/platform/wayland/mojom/OWNERS
index 08850f4..4f50f747 100644
--- a/ui/ozone/platform/wayland/mojom/OWNERS
+++ b/ui/ozone/platform/wayland/mojom/OWNERS
@@ -1,2 +1,5 @@
 per-file *.mojom=set noparent
 per-file *.mojom=file://ipc/SECURITY_OWNERS
+per-file *_mojom_traits*.*=set noparent
+per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
+
diff --git a/ui/ozone/platform/wayland/mojom/wayland_buffer_manager.mojom b/ui/ozone/platform/wayland/mojom/wayland_buffer_manager.mojom
index fb8f6dc..52cbbec 100644
--- a/ui/ozone/platform/wayland/mojom/wayland_buffer_manager.mojom
+++ b/ui/ozone/platform/wayland/mojom/wayland_buffer_manager.mojom
@@ -83,7 +83,7 @@
   // following |widget| and |frame_id|.
   CommitOverlays(gfx.mojom.AcceleratedWidget widget,
                  uint32 frame_id,
-                 array<WaylandOverlayConfig> overlays);
+                 array<wl.mojom.WaylandOverlayConfig> overlays);
 };
 
 
diff --git a/ui/ozone/platform/wayland/mojom/wayland_overlay_config.mojom b/ui/ozone/platform/wayland/mojom/wayland_overlay_config.mojom
index b3c5a2a..c3fb2a1 100644
--- a/ui/ozone/platform/wayland/mojom/wayland_overlay_config.mojom
+++ b/ui/ozone/platform/wayland/mojom/wayland_overlay_config.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 ui.ozone.mojom;
+module wl.mojom;
 
 import "ui/gfx/geometry/mojom/geometry.mojom";
 import "ui/gfx/mojom/gpu_fence_handle.mojom";
diff --git a/ui/ozone/platform/wayland/mojom/wayland_overlay_config_mojom_traits.cc b/ui/ozone/platform/wayland/mojom/wayland_overlay_config_mojom_traits.cc
new file mode 100644
index 0000000..2a8f04e
--- /dev/null
+++ b/ui/ozone/platform/wayland/mojom/wayland_overlay_config_mojom_traits.cc
@@ -0,0 +1,67 @@
+// Copyright 2022 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 "ui/ozone/platform/wayland/mojom/wayland_overlay_config_mojom_traits.h"
+
+#include "components/crash/core/common/crash_key.h"
+
+namespace mojo {
+
+namespace {
+
+void SetDeserializationCrashKeyString(base::StringPiece str) {
+  static crash_reporter::CrashKeyString<128> key("wayland_deserialization");
+  key.Set(str);
+}
+
+}  // namespace
+
+// static
+bool StructTraits<wl::mojom::WaylandOverlayConfigDataView,
+                  wl::WaylandOverlayConfig>::
+    Read(wl::mojom::WaylandOverlayConfigDataView data,
+         wl::WaylandOverlayConfig* out) {
+  out->z_order = data.z_order();
+
+  if (!data.ReadTransform(&out->transform))
+    return false;
+
+  out->buffer_id = data.buffer_id();
+
+  if (data.surface_scale_factor() <= 0) {
+    SetDeserializationCrashKeyString("Invalid surface scale factor.");
+    return false;
+  }
+
+  out->surface_scale_factor = data.surface_scale_factor();
+
+  if (!data.ReadBoundsRect(&out->bounds_rect))
+    return false;
+  if (!data.ReadCropRect(&out->crop_rect))
+    return false;
+  if (!data.ReadDamageRegion(&out->damage_region))
+    return false;
+
+  out->enable_blend = data.enable_blend();
+
+  if (data.opacity() < 0 || data.opacity() > 1.f) {
+    SetDeserializationCrashKeyString("Invalid opacity value.");
+    return false;
+  }
+
+  out->opacity = data.opacity();
+
+  if (!data.ReadAccessFenceHandle(&out->access_fence_handle))
+    return false;
+  if (!data.ReadPriorityHint(&out->priority_hint))
+    return false;
+  if (!data.ReadRoundedClipBounds(&out->rounded_clip_bounds))
+    return false;
+  if (!data.ReadBackgroundColor(&out->background_color))
+    return false;
+
+  return true;
+}
+
+}  // namespace mojo
diff --git a/ui/ozone/platform/wayland/mojom/wayland_overlay_config_mojom_traits.h b/ui/ozone/platform/wayland/mojom/wayland_overlay_config_mojom_traits.h
new file mode 100644
index 0000000..933edd9
--- /dev/null
+++ b/ui/ozone/platform/wayland/mojom/wayland_overlay_config_mojom_traits.h
@@ -0,0 +1,84 @@
+// Copyright 2022 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 UI_OZONE_PLATFORM_WAYLAND_MOJOM_WAYLAND_OVERLAY_CONFIG_MOJOM_TRAITS_H_
+#define UI_OZONE_PLATFORM_WAYLAND_MOJOM_WAYLAND_OVERLAY_CONFIG_MOJOM_TRAITS_H_
+
+#include "skia/public/mojom/skcolor_mojom_traits.h"
+#include "ui/gfx/mojom/gpu_fence_handle_mojom_traits.h"
+#include "ui/gfx/mojom/overlay_priority_hint_mojom_traits.h"
+#include "ui/gfx/mojom/overlay_transform_mojom_traits.h"
+#include "ui/gfx/mojom/rrect_f_mojom_traits.h"
+#include "ui/ozone/platform/wayland/common/wayland_overlay_config.h"
+#include "ui/ozone/platform/wayland/mojom/wayland_overlay_config.mojom-shared.h"
+
+namespace mojo {
+
+template <>
+struct StructTraits<wl::mojom::WaylandOverlayConfigDataView,
+                    wl::WaylandOverlayConfig> {
+  static int z_order(const wl::WaylandOverlayConfig& input) {
+    return input.z_order;
+  }
+
+  static const gfx::OverlayTransform& transform(
+      const wl::WaylandOverlayConfig& input) {
+    return input.transform;
+  }
+
+  static uint32_t buffer_id(const wl::WaylandOverlayConfig& input) {
+    return input.buffer_id;
+  }
+
+  static float surface_scale_factor(const wl::WaylandOverlayConfig& input) {
+    return input.surface_scale_factor;
+  }
+
+  static const gfx::RectF& bounds_rect(const wl::WaylandOverlayConfig& input) {
+    return input.bounds_rect;
+  }
+
+  static const gfx::RectF& crop_rect(const wl::WaylandOverlayConfig& input) {
+    return input.crop_rect;
+  }
+
+  static const gfx::Rect& damage_region(const wl::WaylandOverlayConfig& input) {
+    return input.damage_region;
+  }
+
+  static bool enable_blend(const wl::WaylandOverlayConfig& input) {
+    return input.enable_blend;
+  }
+
+  static float opacity(const wl::WaylandOverlayConfig& input) {
+    return input.opacity;
+  }
+
+  static gfx::GpuFenceHandle access_fence_handle(
+      const wl::WaylandOverlayConfig& input) {
+    return input.access_fence_handle.Clone();
+  }
+
+  static const gfx::OverlayPriorityHint& priority_hint(
+      const wl::WaylandOverlayConfig& input) {
+    return input.priority_hint;
+  }
+
+  static const gfx::RRectF& rounded_clip_bounds(
+      const wl::WaylandOverlayConfig& input) {
+    return input.rounded_clip_bounds;
+  }
+
+  static const absl::optional<SkColor>& background_color(
+      const wl::WaylandOverlayConfig& input) {
+    return input.background_color;
+  }
+
+  static bool Read(wl::mojom::WaylandOverlayConfigDataView data,
+                   wl::WaylandOverlayConfig* out);
+};
+
+}  // namespace mojo
+
+#endif  // UI_OZONE_PLATFORM_WAYLAND_MOJOM_WAYLAND_OVERLAY_CONFIG_MOJOM_TRAITS_H_
diff --git a/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc b/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc
index c2e0c55..65402687 100644
--- a/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc
+++ b/ui/ozone/platform/wayland/wayland_buffer_manager_unittest.cc
@@ -14,6 +14,8 @@
 #include "mojo/public/cpp/system/platform_handle.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/geometry/rect_conversions.h"
+#include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/geometry/rounded_corners_f.h"
 #include "ui/gfx/geometry/rrect_f.h"
 #include "ui/gfx/geometry/transform.h"
@@ -21,6 +23,7 @@
 #include "ui/gfx/linux/drm_util_linux.h"
 #include "ui/gfx/overlay_priority_hint.h"
 #include "ui/gfx/presentation_feedback.h"
+#include "ui/ozone/platform/wayland/common/wayland_overlay_config.h"
 #include "ui/ozone/platform/wayland/gpu/wayland_buffer_manager_gpu.h"
 #include "ui/ozone/platform/wayland/gpu/wayland_surface_gpu.h"
 #include "ui/ozone/platform/wayland/host/wayland_buffer_manager_host.h"
@@ -241,6 +244,26 @@
     return new_window;
   }
 
+  wl::WaylandOverlayConfig CreateBasicWaylandOverlayConfig(
+      int z_order,
+      uint32_t buffer_id,
+      const gfx::Rect& bounds_rect) {
+    return CreateBasicWaylandOverlayConfig(z_order, buffer_id,
+                                           gfx::RectF(bounds_rect));
+  }
+
+  wl::WaylandOverlayConfig CreateBasicWaylandOverlayConfig(
+      int z_order,
+      uint32_t buffer_id,
+      const gfx::RectF& bounds_rect) {
+    wl::WaylandOverlayConfig config;
+    config.z_order = z_order;
+    config.buffer_id = buffer_id;
+    config.bounds_rect = bounds_rect;
+    config.damage_region = gfx::ToEnclosedRect(bounds_rect);
+    return config;
+  }
+
   MockTerminateGpuCallback callback_;
   WaylandBufferManagerHost* manager_host_;
   // Error message that is received when the manager_host destroys the channel.
@@ -463,21 +486,12 @@
   // Can't commit for non-existing buffer id.
   SetTerminateCallbackExpectationAndDestroyChannel(&callback_, true /*fail*/);
 
-  std::vector<ui::ozone::mojom::WaylandOverlayConfigPtr> overlay_configs;
-  overlay_configs.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-      INT32_MIN, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, 1u,
-      kDefaultScale, gfx::RectF(window_->GetBounds()), gfx::RectF(),
-      window_->GetBounds(), false, 1.0f, gfx::GpuFenceHandle(),
-      gfx::OverlayPriorityHint::kNone, gfx::RRectF(),
-      absl::nullopt /* background_color */));
-
+  std::vector<wl::WaylandOverlayConfig> overlay_configs;
+  overlay_configs.emplace_back(
+      CreateBasicWaylandOverlayConfig(INT32_MIN, 1u, window_->GetBounds()));
   // Non-existing buffer id
-  overlay_configs.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-      0, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, 2u, kDefaultScale,
-      gfx::RectF(window_->GetBounds()), gfx::RectF(), window_->GetBounds(),
-      false, 1.0f, gfx::GpuFenceHandle(), gfx::OverlayPriorityHint::kNone,
-      gfx::RRectF(), absl::nullopt /* background_color */));
-
+  overlay_configs.emplace_back(
+      CreateBasicWaylandOverlayConfig(0, 2u, window_->GetBounds()));
   buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), 1u,
                                       std::move(overlay_configs));
 
@@ -495,17 +509,11 @@
   // Re-using the same buffer id across multiple surfaces is allowed.
   SetTerminateCallbackExpectationAndDestroyChannel(&callback_, false /*fail*/);
 
-  std::vector<ui::ozone::mojom::WaylandOverlayConfigPtr> overlay_configs;
-  overlay_configs.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-      0, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, 1u, kDefaultScale,
-      gfx::RectF(window_->GetBounds()), gfx::RectF(), window_->GetBounds(),
-      false, 1.0f, gfx::GpuFenceHandle(), gfx::OverlayPriorityHint::kNone,
-      gfx::RRectF(), absl::nullopt /* background_color */));
-  overlay_configs.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-      1, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, 1u, kDefaultScale,
-      gfx::RectF(window_->GetBounds()), gfx::RectF(), window_->GetBounds(),
-      false, 1.0f, gfx::GpuFenceHandle(), gfx::OverlayPriorityHint::kNone,
-      gfx::RRectF(), absl::nullopt /* background_color */));
+  std::vector<wl::WaylandOverlayConfig> overlay_configs;
+  overlay_configs.emplace_back(
+      CreateBasicWaylandOverlayConfig(0, 1u, window_->GetBounds()));
+  overlay_configs.emplace_back(
+      CreateBasicWaylandOverlayConfig(1, 1u, window_->GetBounds()));
 
   buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), 1u,
                                       std::move(overlay_configs));
@@ -556,13 +564,9 @@
     // Can't commit for bounds rect containing NaN
     SetTerminateCallbackExpectationAndDestroyChannel(&callback_, true /*fail*/);
 
-    std::vector<ui::ozone::mojom::WaylandOverlayConfigPtr> overlay_configs;
-    overlay_configs.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-        1u, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, 1u, kDefaultScale,
-        bounds_rect, gfx::RectF(), window_->GetBounds(), false, 1.0f,
-        gfx::GpuFenceHandle(), gfx::OverlayPriorityHint::kNone, gfx::RRectF(),
-        absl::nullopt /* background_color */));
-
+    std::vector<wl::WaylandOverlayConfig> overlay_configs;
+    overlay_configs.emplace_back(
+        CreateBasicWaylandOverlayConfig(1u, 1u, bounds_rect));
     buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), 1u,
                                         std::move(overlay_configs));
 
@@ -1780,22 +1784,13 @@
   EXPECT_CALL(*mock_surface, Frame(_)).Times(0);
   EXPECT_CALL(*mock_surface, Commit()).Times(0);
 
-  std::vector<ui::ozone::mojom::WaylandOverlayConfigPtr> overlay_configs;
-  overlay_configs.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-      INT32_MIN, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, kBufferId1,
-      kDefaultScale, gfx::RectF(bounds), gfx::RectF(), bounds, false, 1.0f,
-      gfx::GpuFenceHandle(), gfx::OverlayPriorityHint::kNone, gfx::RRectF(),
-      absl::nullopt /* background_color */));
-  overlay_configs.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-      0, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, kBufferId2,
-      kDefaultScale, gfx::RectF(bounds), gfx::RectF(), bounds, false, 1.0f,
-      gfx::GpuFenceHandle(), gfx::OverlayPriorityHint::kNone, gfx::RRectF(),
-      absl::nullopt /* background_color */));
-  overlay_configs.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-      1, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, kBufferId3,
-      kDefaultScale, gfx::RectF(bounds), gfx::RectF(), bounds, false, 1.0f,
-      gfx::GpuFenceHandle(), gfx::OverlayPriorityHint::kNone, gfx::RRectF(),
-      absl::nullopt /* background_color */));
+  std::vector<wl::WaylandOverlayConfig> overlay_configs;
+  overlay_configs.emplace_back(
+      CreateBasicWaylandOverlayConfig(INT32_MIN, kBufferId1, bounds));
+  overlay_configs.emplace_back(
+      CreateBasicWaylandOverlayConfig(0, kBufferId2, bounds));
+  overlay_configs.emplace_back(
+      CreateBasicWaylandOverlayConfig(1, kBufferId3, bounds));
   buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), 1u,
                                       std::move(overlay_configs));
   Sync();
@@ -2023,22 +2018,13 @@
 
   // Prepare a frame with one background buffer, one primary plane and one
   // additional overlay plane. This will simulate hw accelerated compositing.
-  std::vector<ui::ozone::mojom::WaylandOverlayConfigPtr> overlay_configs;
-  overlay_configs.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-      INT32_MIN, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, kBufferId1,
-      kDefaultScale, gfx::RectF(bounds), gfx::RectF(), bounds, false, 1.0f,
-      gfx::GpuFenceHandle(), gfx::OverlayPriorityHint::kNone, gfx::RRectF(),
-      absl::nullopt /* background_color */));
-  overlay_configs.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-      0, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, kBufferId2,
-      kDefaultScale, gfx::RectF(bounds), gfx::RectF(), bounds, false, 1.0f,
-      gfx::GpuFenceHandle(), gfx::OverlayPriorityHint::kNone, gfx::RRectF(),
-      absl::nullopt /* background_color */));
-  overlay_configs.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-      1, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, kBufferId3,
-      kDefaultScale, gfx::RectF(bounds), gfx::RectF(), bounds, false, 1.0f,
-      gfx::GpuFenceHandle(), gfx::OverlayPriorityHint::kNone, gfx::RRectF(),
-      absl::nullopt /* background_color */));
+  std::vector<wl::WaylandOverlayConfig> overlay_configs;
+  overlay_configs.emplace_back(
+      CreateBasicWaylandOverlayConfig(INT32_MIN, kBufferId1, bounds));
+  overlay_configs.emplace_back(
+      CreateBasicWaylandOverlayConfig(0, kBufferId2, bounds));
+  overlay_configs.emplace_back(
+      CreateBasicWaylandOverlayConfig(1, kBufferId3, bounds));
   buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), 1u,
                                       std::move(overlay_configs));
   Sync();
@@ -2094,13 +2080,9 @@
   ProcessCreatedBufferResourcesWithExpectation(1u /* expected size */,
                                                false /* fail */);
 
-  std::vector<ui::ozone::mojom::WaylandOverlayConfigPtr> overlay_configs2;
-  overlay_configs2.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-      INT32_MIN, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, kBufferId1,
-      kDefaultScale, gfx::RectF(bounds), gfx::RectF(), bounds, false, 1.0f,
-      gfx::GpuFenceHandle(), gfx::OverlayPriorityHint::kNone, gfx::RRectF(),
-      absl::nullopt /* background_color */));
-
+  std::vector<wl::WaylandOverlayConfig> overlay_configs2;
+  overlay_configs2.push_back(
+      CreateBasicWaylandOverlayConfig(INT32_MIN, kBufferId1, bounds));
   buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), 2u,
                                       std::move(overlay_configs2));
 
@@ -2176,14 +2158,11 @@
 
   uint32_t frame_id = 0u;
   for (const auto& priority : priorities) {
-    std::vector<ui::ozone::mojom::WaylandOverlayConfigPtr> overlay_configs;
+    std::vector<wl::WaylandOverlayConfig> overlay_configs;
     for (auto id : kBufferIds) {
-      overlay_configs.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-          id == 1 ? INT32_MIN : id,
-          gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, id, kDefaultScale,
-          gfx::RectF(window_->GetBounds()), gfx::RectF(), window_->GetBounds(),
-          false, 1.0f, gfx::GpuFenceHandle(), priority.first, gfx::RRectF(),
-          absl::nullopt /* background_color */));
+      overlay_configs.emplace_back(CreateBasicWaylandOverlayConfig(
+          id == 1 ? INT32_MIN : id, id, window_->GetBounds()));
+      overlay_configs.back().priority_hint = priority.first;
     }
 
     buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), ++frame_id,
@@ -2257,15 +2236,12 @@
     connection_->set_surface_submission_in_pixel_coordinates(is_in_px);
     for (auto scale_factor : scale_factors) {
       for (const auto& rounded_corners : rounded_corners_vec) {
-        std::vector<ui::ozone::mojom::WaylandOverlayConfigPtr> overlay_configs;
+        std::vector<wl::WaylandOverlayConfig> overlay_configs;
         for (auto id : kBufferIds) {
-          overlay_configs.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-              id == 1 ? INT32_MIN : id,
-              gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, id, scale_factor,
-              gfx::RectF(window_->GetBounds()), gfx::RectF(),
-              window_->GetBounds(), false, 1.0f, gfx::GpuFenceHandle(),
-              gfx::OverlayPriorityHint::kNone, rounded_corners,
-              absl::nullopt /* background_color */));
+          overlay_configs.emplace_back(CreateBasicWaylandOverlayConfig(
+              id == 1 ? INT32_MIN : id, id, window_->GetBounds()));
+          overlay_configs.back().surface_scale_factor = scale_factor;
+          overlay_configs.back().rounded_clip_bounds = rounded_corners;
         }
 
         buffer_manager_gpu_->CommitOverlays(window_->GetWidget(), ++frame_id,
@@ -2466,27 +2442,14 @@
 
     Sync();
 
-    std::vector<ui::ozone::mojom::WaylandOverlayConfigPtr> overlay_configs;
-    overlay_configs.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-        INT32_MIN, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, kBufferId1, 1,
-        gfx::RectF(temp_window->GetBounds()), gfx::RectF(),
-        temp_window->GetBounds(), false, 1.0f, gfx::GpuFenceHandle(),
-        gfx::OverlayPriorityHint::kNone, gfx::RRectF(),
-        absl::nullopt /* background_color */));
-
-    overlay_configs.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-        0, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, kBufferId1, 1,
-        gfx::RectF(window_->GetBounds()), gfx::RectF(),
-        temp_window->GetBounds(), false, 1.0f, gfx::GpuFenceHandle(),
-        gfx::OverlayPriorityHint::kNone, gfx::RRectF(),
-        absl::nullopt /* background_color */));
-
-    overlay_configs.push_back(ui::ozone::mojom::WaylandOverlayConfig::New(
-        1, gfx::OverlayTransform::OVERLAY_TRANSFORM_NONE, kBufferId1, 1,
-        bounds_rect, gfx::RectF(), temp_window->GetBounds(), false, 1.0f,
-        gfx::GpuFenceHandle(), gfx::OverlayPriorityHint::kNone, gfx::RRectF(),
-        absl::nullopt /* background_color */));
-
+    std::vector<wl::WaylandOverlayConfig> overlay_configs;
+    auto bounds = temp_window->GetBounds();
+    overlay_configs.emplace_back(
+        CreateBasicWaylandOverlayConfig(INT32_MIN, kBufferId1, bounds));
+    overlay_configs.emplace_back(
+        CreateBasicWaylandOverlayConfig(0, kBufferId1, bounds));
+    overlay_configs.emplace_back(
+        CreateBasicWaylandOverlayConfig(1, kBufferId1, bounds_rect));
     buffer_manager_gpu_->CommitOverlays(temp_window->GetWidget(), 1u,
                                         std::move(overlay_configs));
 
diff --git a/ui/views/bubble/bubble_dialog_model_host.cc b/ui/views/bubble/bubble_dialog_model_host.cc
index 1a12477..ccf9a61 100644
--- a/ui/views/bubble/bubble_dialog_model_host.cc
+++ b/ui/views/bubble/bubble_dialog_model_host.cc
@@ -50,6 +50,10 @@
       return BubbleDialogModelHost::FieldType::kControl;
     case ui::DialogModelField::kCombobox:
       return BubbleDialogModelHost::FieldType::kControl;
+    case ui::DialogModelField::kMenuItem:
+      // TODO(crbug.com/1324298): Implement.
+      NOTREACHED();
+      return BubbleDialogModelHost::FieldType::kMenuItem;
     case ui::DialogModelField::kSeparator:
       return BubbleDialogModelHost::FieldType::kMenuItem;
     case ui::DialogModelField::kCustom:
@@ -411,6 +415,10 @@
     case ui::DialogModelField::kCombobox:
       AddOrUpdateCombobox(field->AsCombobox(GetPassKey()));
       break;
+    case ui::DialogModelField::kMenuItem:
+      // TODO(crbug.com/1324298): Implement.
+      NOTREACHED();
+      break;
     case ui::DialogModelField::kSeparator:
       AddOrUpdateSeparator(field);
       break;
diff --git a/ui/webui/resources/css/BUILD.gn b/ui/webui/resources/css/BUILD.gn
index 5533d0c..321900f9 100644
--- a/ui/webui/resources/css/BUILD.gn
+++ b/ui/webui/resources/css/BUILD.gn
@@ -24,7 +24,6 @@
   out_manifest = "$target_gen_dir/$preprocess_src_manifest"
   in_files = [
     "action_link.css",
-    "apps/common.css",
     "butter_bar.css",
     "chrome_shared.css",
     "dialogs.css",